use of gov.sandia.n2a.ui.eq.tree.NodePart in project n2a by frothga.
the class PanelEquationTree method moveSelected.
public void moveSelected(int direction) {
if (container.locked)
return;
TreePath path = tree.getLeadSelectionPath();
if (path == null)
return;
NodeBase nodeBefore = (NodeBase) path.getLastPathComponent();
NodeBase parent = (NodeBase) nodeBefore.getParent();
if (// Only parts support $metadata.gui.order
parent instanceof NodePart) {
// First check if we can move in the filtered (visible) list.
int indexBefore = model.getIndexOfChild(parent, nodeBefore);
int indexAfter = indexBefore + direction;
if (indexAfter >= 0 && indexAfter < model.getChildCount(parent)) {
// Then convert to unfiltered indices.
NodeBase nodeAfter = (NodeBase) model.getChild(parent, indexAfter);
indexBefore = parent.getIndex(nodeBefore);
indexAfter = parent.getIndex(nodeAfter);
MainFrame.instance.undoManager.apply(new ChangeOrder((NodePart) parent, indexBefore, indexAfter));
}
}
}
use of gov.sandia.n2a.ui.eq.tree.NodePart in project n2a by frothga.
the class AddAnnotation method create.
public static NodeBase create(List<String> path, int index, String name, MNode createSubtree, boolean nameIsGenerated, boolean multi, boolean selectVariable, boolean touchesPin, boolean touchesCategory) {
NodeBase parent = NodeBase.locateNode(path);
if (parent == null) {
int last = path.size() - 1;
if (path.get(last).equals("$metadata"))
parent = NodeBase.locateNode(path.subList(0, last));
}
if (parent == null)
throw new CannotRedoException();
// Update database
MPart mparent = parent.source;
if (parent instanceof NodePart || parent instanceof NodeVariable)
mparent = (MPart) mparent.childOrCreate("$metadata");
else if (parent instanceof NodeAnnotation)
mparent = ((NodeAnnotation) parent).folded;
// else parent is a NodeAnnotations, so mparent is $metadata, which can be used directly.
// For a simple add, name has only one path element. However, if a ChangeAnnotation was
// merged into this, then the name may have several path elements.
String[] names = name.split("\\.");
MPart createdPart = (MPart) mparent.childOrCreate(names);
createdPart.merge(createSubtree);
// Update GUI
PanelEquationTree pet = parent.getTree();
FilteredTreeModel model = null;
if (pet != null)
model = (FilteredTreeModel) pet.tree.getModel();
NodeContainer container = (NodeContainer) parent;
if (// If this is a part, then display special block.
parent instanceof NodePart) {
container = (NodeContainer) parent.child("$metadata");
if (container == null) {
container = new NodeAnnotations(mparent);
if (model == null)
FilteredTreeModel.insertNodeIntoUnfilteredStatic(container, parent, index);
else
model.insertNodeIntoUnfiltered(container, parent, index);
// TODO: update order?
index = 0;
}
}
NodeBase createdNode;
if (// pure create, going into edit mode
nameIsGenerated) {
// The given name should be unique, so don't bother checking for an existing node.
createdNode = new NodeAnnotation(createdPart);
// For edit mode. This should only happen on first application of the create action, and should only be possible if visibility is already correct.
createdNode.setUserObject("");
if (model == null)
FilteredTreeModel.insertNodeIntoUnfilteredStatic(createdNode, container, index);
else
model.insertNodeIntoUnfiltered(createdNode, container, index);
} else // create was merged with change name/value
{
List<String> expanded = null;
if (model != null)
expanded = saveExpandedNodes(pet.tree, container);
container.build();
container.filter();
if (model != null && container.visible()) {
model.nodeStructureChanged(container);
restoreExpandedNodes(pet.tree, container, expanded);
}
createdNode = findClosest(container, names);
if (pet != null) {
TreeNode[] parentPath = parent.getPath();
TreeNode[] createdPath = createdNode.getPath();
TreePath createdTreePath = new TreePath(createdPath);
pet.tree.expandPath(createdTreePath);
if (selectVariable)
pet.updateVisibility(parentPath, -2, !multi);
else
pet.updateVisibility(createdPath, -2, !multi);
if (multi) {
if (selectVariable)
pet.tree.addSelectionPath(new TreePath(parentPath));
else
pet.tree.addSelectionPath(createdTreePath);
}
pet.animate();
}
}
update(parent, touchesPin, touchesCategory);
return createdNode;
}
use of gov.sandia.n2a.ui.eq.tree.NodePart in project n2a by frothga.
the class GraphEdge method updateShape.
/**
* Updates our cached shape information based on current state of graph nodes.
*/
public void updateShape(boolean updateOther) {
line = null;
head = null;
label = null;
textBoxTo = null;
// empty, so won't affect union(), and will return false from intersects()
bounds = new Rectangle(0, 0, -1, -1);
root = null;
// Distance from boundary of nodeTo to target the tip. Varies depending on arrow type.
int padTip = 0;
String headType = nodeFrom.node.source.get(alias, "$metadata", "gui", "arrow");
boolean straight = nodeFrom.node.source.getFlag(alias, "$metadata", "gui", "arrow", "straight");
switch(headType) {
case "circle":
case "circleFill":
padTip = (int) Math.round(arrowheadLength / 2);
}
Rectangle Cbounds = nodeFrom.getBounds();
Vector2 c = new Vector2(Cbounds.getCenterX(), Cbounds.getCenterY());
Rectangle Abounds = null;
Vector2 a = null;
double tipAngle = 0;
Vector2 tipAway = null;
FontMetrics fm = nodeFrom.getFontMetrics(nodeFrom.getFont());
int lineHeight = fm.getHeight() + 2 * padNameTop;
int boxSize = lineHeight / 2;
if (tipDrag) {
a = tip;
if (anchor != null)
c = new Vector2(Cbounds.x + anchor.x, Cbounds.y + anchor.y);
} else if (nodeTo != null) {
Abounds = nodeTo.getBounds();
if (pinKeyTo == null) {
a = new Vector2(Abounds.getCenterX(), Abounds.getCenterY());
Abounds.grow(padTip, padTip);
} else {
Abounds.grow(padTip, 0);
// Determine tip position and angle
// Determine vertical position down side of part.
// vertical center of first pin
int y = Abounds.y + GraphNode.border.t + boxSize;
// Determine horizontal position and tip angle
if (pinSideTo.equals("in")) {
y += lineHeight * nodeTo.node.pinIn.getInt(pinKeyTo, "order");
tip = new Vector2(Abounds.x - boxSize - 1, y);
tipAngle = Math.PI;
tipAway = new Vector2(-1, 0);
} else // out
{
y += lineHeight * nodeTo.node.pinOut.getInt(pinKeyTo, "order");
tip = new Vector2(Abounds.x + Abounds.width + boxSize, y);
tipAngle = 0;
tipAway = new Vector2(1, 0);
}
a = tip;
}
}
// Non-null for binary connections that also need a curve rather than straight line.
Vector2 ba = null;
Vector2 c2c = null;
if (// curve passing through a connection
!straight && nodeTo != null && edgeOther != null && edgeOther.nodeTo != null) {
Vector2 b;
// Similar role to tipAway, but for edgeOther.
double flip = 0;
if (edgeOther.tipDrag) {
b = edgeOther.tip;
} else {
Rectangle Bbounds = edgeOther.nodeTo.getBounds();
if (edgeOther.pinKeyTo == null) {
b = new Vector2(Bbounds.getCenterX(), Bbounds.getCenterY());
} else {
Bbounds.grow(padTip, 0);
int y = Bbounds.y + GraphNode.border.t + boxSize;
if (edgeOther.pinSideTo.equals("in")) {
y += lineHeight * edgeOther.nodeTo.node.pinIn.getInt(edgeOther.pinKeyTo, "order");
b = new Vector2(Bbounds.x - boxSize, y);
flip = -1;
} else {
y += lineHeight * edgeOther.nodeTo.node.pinOut.getInt(edgeOther.pinKeyTo, "order");
b = new Vector2(Bbounds.x + Bbounds.width + boxSize, y);
flip = 1;
}
}
}
// vector from b -> a
ba = a.subtract(b);
// average position
Vector2 avg = a.add(b).multiply(0.5);
c2c = c.subtract(avg);
double c2cLength = c2c.length();
double baLength = ba.length();
if (c2cLength > baLength) {
c2c = c2c.normalize();
if (baLength > 0) {
ba = ba.normalize();
} else // Both A and B nodes are at exactly the same place. Create vector perpendicular to c2c.
{
// Vector to left side of c2c.
ba = new Vector2(-c2c.y, c2c.x);
// The "A" edge goes to the right side of c2c, while "B" goes to the left.
if (nodeFrom.edgesOut.get(0) == this)
ba = ba.multiply(-1);
if (// We are an ordinary connection.
pinSideTo == null) {
// If other edge is a pin connection, go away from it.
if (ba.x * flip > 0)
ba = ba.multiply(-1);
} else // We connect to a pin.
{
// Follow our own pin direction.
if (ba.x * tipAway.x < 0)
ba = ba.multiply(-1);
}
}
} else // c2cLength <= baLength; That is, c is roughly between a and b.
{
if (// Nodes are too close to compute good angles.
baLength < 10 || c2cLength < 10) {
// Draw straight lines. In this case, neither ba nor c2c will be used.
ba = null;
} else {
c2c = c2c.normalize();
ba = ba.normalize();
// Interpolate between c2c and ac (vector from a to c).
double r = c2cLength / baLength;
Vector2 ac = c.subtract(a).normalize();
c2c = c2c.multiply(r).add(ac.multiply(1 - r)).normalize();
}
}
// However, if this is a self-connection, then we are our peer edge, so don't start an infinite loop.
if (updateOther)
edgeOther.updateShape(false);
}
if (topic.isEmpty())
text = alias;
else
text = alias + "(" + topic + ")";
double tw = fm.stringWidth(text);
double th = fm.getHeight();
double nodeAngle = Math.atan((double) Cbounds.height / Cbounds.width);
if (// External endpoint
nameTo != null) {
if (tipDrag) {
root = intersection(new Segment2(c, tip), Cbounds);
// tip is inside Cbounds
if (root == null)
return;
line = new Line2D.Double(c.x, c.y, tip.x, tip.y);
tipAngle = new Segment2(tip, c).angle();
} else {
// Determine offset
List<Integer> widths = new ArrayList<Integer>();
int totalWidth = 0;
int index = 0;
for (String key : nodeFrom.node.connectionBindings.keySet()) {
// Select only connection bindings that require resolution up to container.
// Ignore unconnected edges and edges to children of siblings.
NodePart p = nodeFrom.node.connectionBindings.get(key);
// not connected
if (p == null)
continue;
// detect siblings
boolean hasGraph = false;
while (p != null) {
if (p.graph != null) {
hasGraph = true;
break;
}
p = (NodePart) p.getParent();
}
if (hasGraph)
continue;
if (key.equals(alias))
index = widths.size();
NodeVariable v = (NodeVariable) nodeFrom.node.child(key);
int width = fm.stringWidth(v.source.get());
widths.add(width);
totalWidth += width;
}
double offset = 0;
int count = widths.size();
if (count > 1) {
// pad between each label
totalWidth += (count - 1) * padNameBetween;
// Add widths and padding for all labels that precede this one.
for (int i = 0; i < index; i++) offset += widths.get(i) + padNameBetween;
// Add half of this label, to reach its center.
offset += widths.get(index) / 2.0;
// Offset to center the whole thing over the node.
offset -= totalWidth / 2.0;
}
int x = (int) (c.x + offset);
// Determine text box. Need text height to locate arrowhead, so might as well calculate it all now.
double ew = fm.stringWidth(nameTo);
double eh = fm.getHeight();
labelTo = new Vector2(x - ew / 2, Cbounds.y - 3 * eh - 2 * arrowheadLength);
textBoxTo = new Rectangle();
textBoxTo.x = (int) labelTo.x - padNameSide;
textBoxTo.y = (int) labelTo.y - padNameTop;
textBoxTo.width = (int) Math.ceil(ew) + 2 * padNameSide;
textBoxTo.height = (int) Math.ceil(eh) + 2 * padNameTop;
bounds = bounds.union(textBoxTo);
labelTo.y += fm.getAscent();
tip = new Vector2(x, textBoxTo.y + textBoxTo.height + padTip);
Vector2 w1 = tip.add(new Vector2(0, Cbounds.y - tip.y));
Vector2 w2 = c.add(new Vector2(offset, 0));
line = new CubicCurve2D.Double(tip.x, tip.y, w1.x, w1.y, w2.x, w2.y, c.x, c.y);
Spline spline = new Spline((CubicCurve2D) line);
// on boundary of c
root = intersection(spline, Cbounds);
// This should never happen in current arrangement, since target is at fixed distance above us.
if (root == null)
return;
tipAngle = Math.PI / 2;
}
} else if (// pin-to-pin, output-to-pin, or dragging from pin
alias.isEmpty() && (pinKeyFrom != null || pinKeyTo != null)) {
if (pinKeyFrom == null) {
c = new Vector2(Cbounds.x + Cbounds.width, Cbounds.y + Cbounds.height / 2);
ba = new Vector2(1, 0);
} else {
int y = Cbounds.y + GraphNode.border.t + boxSize;
if (pinSideFrom.equals("in")) {
y += lineHeight * nodeFrom.node.pinIn.getInt(pinKeyFrom, "order");
c = new Vector2(Cbounds.x - boxSize, y);
ba = new Vector2(-1, 0);
} else // pinSideFrom is "out"
{
y += lineHeight * nodeFrom.node.pinOut.getInt(pinKeyFrom, "order");
c = new Vector2(Cbounds.x + Cbounds.width + boxSize, y);
ba = new Vector2(1, 0);
}
}
root = c;
if (// Usual case: single segment between two separate parts (or part to drag-tip).
tipDrag || nodeTo != nodeFrom) {
Vector2 tc = c.subtract(tip);
double length = tc.length() / 3;
if (tipDrag)
tipAway = tc.normalize();
Vector2 w1 = tip.add(tipAway.multiply(length));
Vector2 w2 = c.add(ba.multiply(length));
line = new CubicCurve2D.Double(tip.x, tip.y, w1.x, w1.y, w2.x, w2.y, c.x, c.y);
} else // Self connection, so create two segments to wrap around top of part.
{
// The central point will act as endpoint to both segments.
int order = nodeTo.node.pinIn.getInt(pinKeyTo, "order");
Rectangle bounds = nodeTo.getBounds();
int halfWidth = bounds.width / 2;
Vector2 m = new Vector2(bounds.x + halfWidth, bounds.y - (lineHeight * (order + 1)));
// The fudge factor at the end is to make the vertical run of the Bezier curves appear to have the same spacing as the horizontal run across the top. It is arbitrary and subjective.
double length = bounds.width / 3 + lineHeight * order * 1.5;
double length2 = halfWidth + boxSize + length;
Vector2 w1 = tip.add(tipAway.multiply(length));
Vector2 w2 = c.add(ba.multiply(length));
Vector2 m1 = new Vector2(m.x - length2, m.y);
Vector2 m2 = new Vector2(m.x + length2, m.y);
Path2D path = new Path2D.Double();
path.append(new CubicCurve2D.Double(tip.x, tip.y, w1.x, w1.y, m1.x, m1.y, m.x, m.y), false);
path.append(new CubicCurve2D.Double(m.x, m.y, m2.x, m2.y, w2.x, w2.y, c.x, c.y), false);
line = path;
}
} else if (// Unconnected endpoint
nodeTo == null) {
if (!tipDrag) {
// Distribute rays around node. This method is limited to 8 positions,
// but no sane person would have more than an 8-way connection.
int count = nodeFrom.edgesOut.size();
int index = nodeFrom.edgesOut.indexOf(this);
double angle = Math.PI + index * Math.PI * 2 / count;
tip = new Vector2(angle);
// Scale direction vector until it ends far enough from border
// There are really only two cases: 1) coming out the left or right, 2) coming out the top or bottom
// To decide, reduce angle to first quadrant and compare with node's own diagonal.
double absAngle = tip.absAngle();
double length;
if (// top or bottom
absAngle > nodeAngle) {
length = (Cbounds.height / 2 + th + arrowheadLength + strokeThickness) / Math.abs(tip.y);
} else // left or right
{
length = (Cbounds.width / 2 + tw + arrowheadLength + strokeThickness) / Math.abs(tip.x);
}
tip = tip.multiply(length).add(c);
}
root = intersection(new Segment2(c, tip), Cbounds);
// tip is inside Cbounds
if (root == null)
return;
line = new Line2D.Double(c.x, c.y, tip.x, tip.y);
tipAngle = new Segment2(tip, c).angle();
} else if (// from connection to pin. (If dragging, then handled by other cases below.)
pinKeyTo != null && !tipDrag) {
Vector2 ct = tip.subtract(c);
double length = ct.length() / 3;
tipAway = tipAway.multiply(length);
Vector2 w1 = tip.add(tipAway);
if (// Straight line from C to A
ba == null) {
Vector2 w2 = c.add(ct.divide(3));
line = new CubicCurve2D.Double(tip.x, tip.y, w1.x, w1.y, w2.x, w2.y, c.x, c.y);
Segment2 s = new Segment2(a, c);
// root can be null if a is inside Cbounds
root = intersection(s, Cbounds);
} else // Curve passing through C then toward A
{
Vector2 w2 = c.add(ba.multiply(length));
line = new CubicCurve2D.Double(tip.x, tip.y, w1.x, w1.y, w2.x, w2.y, c.x, c.y);
Spline spline = new Spline((CubicCurve2D) line);
// on boundary of c
root = intersection(spline, Cbounds);
}
if (root == null)
return;
} else if (// Draw straight line.
ba == null) {
Segment2 s = new Segment2(a, c);
// tip can be null if c is inside Abounds
if (!tipDrag)
tip = intersection(s, Abounds);
// root can be null if a is inside Cbounds
root = intersection(s, Cbounds);
if (tip == null || root == null)
return;
line = new Line2D.Double(c.x, c.y, tip.x, tip.y);
tipAngle = s.angle();
} else // Draw curve.
{
if (tipDrag) {
tipAngle = c2c.angle();
} else {
// far enough to get from center of endpoint to outside of bounds
double Alength = Abounds.getWidth() + Abounds.getHeight();
// "outside" point in the direction of the connection
Vector2 o = a.add(c2c.multiply(Alength));
// segment from center of endpoint to outside point
Segment2 s = new Segment2(a, o);
// point on the periphery of the endpoint
tip = intersection(s, Abounds);
tipAngle = s.angle();
}
double length = c.distance(tip) / 3;
Vector2 w1 = tip.add(c2c.multiply(length));
Vector2 w2 = c.add(ba.multiply(length));
line = new CubicCurve2D.Double(tip.x, tip.y, w1.x, w1.y, w2.x, w2.y, c.x, c.y);
Spline spline = new Spline((CubicCurve2D) line);
// on boundary of c
root = intersection(spline, Cbounds);
if (root == null)
return;
}
// Arrow head
// arrowhead half-width
double ah = arrowheadLength / 2;
switch(headType) {
case "arrow":
// Wrap shape in path object so we can extend it
Path2D path = new Path2D.Double(line);
Vector2 end = new Vector2(tip, tipAngle + arrowheadAngle, arrowheadLength);
path.append(new Line2D.Double(end.x, end.y, tip.x, tip.y), false);
end = new Vector2(tip, tipAngle - arrowheadAngle, arrowheadLength);
path.append(new Line2D.Double(tip.x, tip.y, end.x, end.y), false);
line = path;
break;
case "circle":
head = new Ellipse2D.Double(tip.x - ah, tip.y - ah, arrowheadLength, arrowheadLength);
headFill = false;
break;
case "circleFill":
head = new Ellipse2D.Double(tip.x - ah, tip.y - ah, arrowheadLength, arrowheadLength);
headFill = true;
break;
}
bounds = bounds.union(line.getBounds());
if (head != null)
bounds = bounds.union(head.getBounds());
int t = (int) Math.ceil(strokeThickness / 2);
bounds.grow(t, t);
// Name
if (!text.isEmpty()) {
label = new Vector2(0, 0);
double absAngle = root.subtract(c).absAngle();
if (// top or bottom
absAngle > nodeAngle) {
label.x = root.x - tw / 2;
if (root.y < c.y)
label.y = root.y - th - padNameTop;
else
label.y = root.y + padNameTop;
} else // left or right
{
if (root.x < c.x)
label.x = root.x - tw - padNameSide;
else
label.x = root.x + padNameSide;
label.y = root.y - th / 2;
}
textBox = new Rectangle();
textBox.x = (int) label.x - padNameSide;
textBox.y = (int) label.y - padNameTop;
textBox.width = (int) Math.ceil(tw) + 2 * padNameSide;
textBox.height = (int) Math.ceil(th) + 2 * padNameTop;
bounds = bounds.union(textBox);
label.y += fm.getAscent();
}
}
use of gov.sandia.n2a.ui.eq.tree.NodePart in project n2a by frothga.
the class AddInherit method destroy.
public static void destroy(List<String> path, boolean canceled) {
NodePart parent = (NodePart) NodeBase.locateNode(path);
if (parent == null)
throw new CannotUndoException();
NodePart grandparent = (NodePart) parent.getTrueParent();
NodeBase node = parent.child("$inherit");
TreeNode[] nodePath = node.getPath();
int index = parent.getIndexFiltered(node);
if (canceled)
index--;
PanelEquations pe = PanelModel.instance.panelEquations;
PanelEquationTree pet = parent.getTree();
FilteredTreeModel model = (FilteredTreeModel) pet.tree.getModel();
PanelEquationGraph peg = pe.panelEquationGraph;
MPart mparent = parent.source;
// Complex restructuring happens here.
mparent.clear("$inherit");
// Handles all cases (complete deletion or exposed hidden node)
parent.build();
if (grandparent == null)
parent.findConnections();
else
grandparent.findConnections();
parent.rebuildPins();
parent.filter();
if (parent == pe.part) {
// safely disconnects old nodes, even though parent has been rebuilt with new nodes
peg.reloadPart();
// Ensure that parts are not visible in parent panel.
parent.filter();
}
// Presumably, part node is still visible. Is there any harm in doing this if it is not?
model.nodeStructureChanged(parent);
pet.updateOrder(nodePath);
pet.updateVisibility(nodePath, index);
pet.animate();
if (parent != pe.part) {
peg.updatePins();
peg.reconnect();
peg.repaint();
}
if (// root node, so update categories in search list
parent.getTrueParent() == null) {
PanelModel.instance.panelSearch.search();
}
}
use of gov.sandia.n2a.ui.eq.tree.NodePart in project n2a by frothga.
the class AddReference method create.
public static NodeBase create(List<String> path, int index, String name, String value, boolean multi) {
NodeBase parent = NodeBase.locateNode(path);
if (parent == null)
throw new CannotRedoException();
MPart block = (MPart) parent.source.childOrCreate("$reference");
PanelEquationTree pet = parent.getTree();
FilteredTreeModel model = (FilteredTreeModel) pet.tree.getModel();
// If this is a variable, then mix metadata with equations and references
NodeBase container = parent;
if (// If this is a part, then display special block
parent instanceof NodePart) {
if (// empty implies the node is absent
block.size() == 0) {
container = new NodeReferences(block);
model.insertNodeIntoUnfiltered(container, parent, index);
index = 0;
} else // the node is present, so retrieve it
{
container = parent.child("$reference");
}
}
NodeBase createdNode = container.child(name);
boolean alreadyExists = createdNode != null;
MPart createdPart = (MPart) block.set(value, name);
if (!alreadyExists)
createdNode = new NodeReference(createdPart);
// pure create, so about to go into edit mode. This should only happen on first application of the create action, and should only be possible if visibility is already correct.
if (value == null)
createdNode.setUserObject("");
if (!alreadyExists)
model.insertNodeIntoUnfiltered(createdNode, container, index);
if (// create was merged with change name/value
value != null) {
container.invalidateColumns(null);
TreeNode[] createdPath = createdNode.getPath();
pet.updateOrder(createdPath);
pet.updateVisibility(createdPath, -2, !multi);
if (multi)
pet.tree.addSelectionPath(new TreePath(createdPath));
container.allNodesChanged(model);
pet.animate();
}
return createdNode;
}
Aggregations