1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.layout.hierarchic;
15  
16  import demo.view.DemoBase;
17  import y.base.DataMap;
18  import y.base.Edge;
19  import y.base.EdgeCursor;
20  import y.base.EdgeList;
21  import y.base.EdgeMap;
22  import y.base.GraphEvent;
23  import y.base.GraphListener;
24  import y.base.Node;
25  import y.base.NodeCursor;
26  import y.base.NodeList;
27  import y.base.NodeMap;
28  import y.geom.YPoint;
29  import y.layout.NodeLayout;
30  import y.layout.PortConstraint;
31  import y.layout.PortConstraintKeys;
32  import y.layout.hierarchic.GivenLayersLayerer;
33  import y.layout.hierarchic.IncrementalHierarchicLayouter;
34  import y.layout.hierarchic.AsIsLayerer;
35  import y.layout.hierarchic.incremental.IncrementalHintsFactory;
36  import y.layout.hierarchic.incremental.IntValueHolderAdapter;
37  import y.util.Maps;
38  import y.view.Arrow;
39  import y.view.Bend;
40  import y.view.BendCursor;
41  import y.view.BendList;
42  import y.view.BridgeCalculator;
43  import y.view.CreateEdgeMode;
44  import y.view.DefaultGraph2DRenderer;
45  import y.view.Drawable;
46  import y.view.EdgeRealizer;
47  import y.view.EditMode;
48  import y.view.Graph2D;
49  import y.view.HitInfo;
50  import y.view.HotSpotMode;
51  import y.view.LineType;
52  import y.view.NodeRealizer;
53  import y.view.PopupMode;
54  import y.view.PortAssignmentMoveSelectionMode;
55  
56  import javax.swing.AbstractAction;
57  import javax.swing.Action;
58  import javax.swing.JMenu;
59  import javax.swing.JPopupMenu;
60  import javax.swing.JToolBar;
61  import java.awt.Color;
62  import java.awt.Cursor;
63  import java.awt.Graphics2D;
64  import java.awt.Rectangle;
65  import java.awt.Shape;
66  import java.awt.Stroke;
67  import java.awt.EventQueue;
68  import java.awt.event.ActionEvent;
69  import java.awt.geom.Rectangle2D;
70  import java.net.URL;
71  import java.util.ArrayList;
72  import java.util.HashSet;
73  import java.util.List;
74  import java.util.Locale;
75  import java.util.Set;
76  
77  /**
78   * This demo shows how to use the {@link y.layout.hierarchic.IncrementalHierarchicLayouter} together
79   * with sophisticated customized {@link y.view.ViewMode}s.
80   * The application will automatically perform a new layout whenever the user
81   * makes changes to the graph. The layout does not change the layer assignment of the
82   * nodes. Moreover, the sequence of nodes within each layer is preserved.<br/>
83   * It demonstrates how to use a predetermined layering and how the application
84   * can retrieve the layering computed during the layout. The layering information
85   * is visualized using a {@link y.view.Drawable} in the canvas. <br/>
86   * For a simpler demo that depicts the basics of {@link y.layout.hierarchic.IncrementalHierarchicLayouter}
87   * see {@link SimpleIncrementalHierarchicLayouterDemo}. <br/>
88   * The incremental layout aligns the centers of the nodes within a layer. Thus moving a
89   * node up or down a little bit such that its layer assignment does not change is normally
90   * immediately reverted by the following incremental layout. Similarly, moving a node
91   * a little bit to the left or right such that the sequence within its layer does not change
92   * also gets reverted by the following incremental layout.
93   * <p>
94   *   Things to try:
95   * </p>
96   * <ul>
97   *   <li>
98   *     Create new nodes. The mouse location is used to determine the layer for the new node.
99   *     Nodes created far away from the existing nodes are added to a new bottom layer.
100  *   </li>
101  *   <li>
102  *     Move existing nodes. This is treated like creating new nodes. Additionally the target
103  *     layer for the moved node is visually indicated.
104  *   </li>
105  *   <li>
106  *     Create new edges.
107  *   </li>
108  *   <li>
109  *     Move the first/last bend of an edge. This creates a port constraint (PC) which is
110  *     visually indicated.
111  *   </li>
112  *   <li>
113  *     Select a node and choose "Optimize Node". This triggers a layout which tries
114  *     to improve the layout by ignoring the current layer assignment and sequence
115  *     information for the selected node.
116  *   </li>
117  *   <li>
118  *     The context menu for a set of selected nodes provides actions for fixing the layer
119  *     or sequence coordinates of the selected nodes. Fixing the layer coordinates of at least
120  *     two nodes within a layer means that they can be moved such that their centers are at
121  *     different y coordinates and that the next incremental layout will preserve the difference
122  *     of the y coordinates. Similarly, fixing the sequence coordinates of at least two nodes
123  *     within a layer allows to control their distances in the direction of the x axis. Fixing
124  *     the coordinates means fixing layer coordinates as well as sequence coordinates.
125  *   </li>
126  * </ul>
127  * @see y.layout.hierarchic.IncrementalHierarchicLayouter
128  *
129  * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/incremental_hierarchical_layouter.html#incremental_hierarchical_layouter">Section Hierarchical Layout Style</a> in the yFiles for Java Developer's Guide
130  * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/mvc_controller.html#cls_ViewMode">Section ViewMode Workings</a> in the yFiles for Java Developer's Guide
131  */
132 public class IncrementalHierarchicLayouterDemo extends DemoBase {
133   private EdgeMap sourcePortMap;
134   private EdgeMap targetPortMap;
135   private LayerDrawable layerDrawable;
136   private NodeMap layerIdMap;
137   private DataMap hintMap;
138 
139   private PortAssignmentMoveSelectionMode paMode;
140 
141   private IncrementalHierarchicLayouter hierarchicLayouter;
142   private IncrementalHintsFactory hintsFactory;
143   private GivenLayersLayerer gll;
144 
145   private boolean loaded;
146   private LayoutAction layoutAction;
147 
148   public IncrementalHierarchicLayouterDemo() {
149     final Graph2D graph = view.getGraph2D();
150 
151     // make it look nice
152     EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
153     defaultER.setArrow(Arrow.STANDARD);
154 
155     // enable bridges for PolyLineEdgeRealizer
156     BridgeCalculator bridgeCalculator = new BridgeCalculator();
157     bridgeCalculator.setCrossingMode(BridgeCalculator.CROSSING_MODE_HORIZONTAL_CROSSES_VERTICAL);
158     ((DefaultGraph2DRenderer) view.getGraph2DRenderer()).setBridgeCalculator(bridgeCalculator);
159 
160     // allocate a couple of maps
161     layerIdMap = graph.createNodeMap();
162     sourcePortMap = graph.createEdgeMap();
163     targetPortMap = graph.createEdgeMap();
164     hintMap = Maps.createHashedDataMap();
165 
166     // register them with the graph
167     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, sourcePortMap);
168     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, targetPortMap);
169     graph.addDataProvider(GivenLayersLayerer.LAYER_ID_KEY, layerIdMap);
170     graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, hintMap);
171     graph.addDataProvider(IncrementalHierarchicLayouter.LAYER_VALUE_HOLDER_DPKEY,
172         new IntValueHolderAdapter(layerIdMap));
173 
174     // create a drawable that displays layers
175     this.layerDrawable = new LayerDrawable(graph, layerIdMap);
176     view.addBackgroundDrawable(layerDrawable);
177 
178     // create and configure the layout algorithm
179     hierarchicLayouter = new IncrementalHierarchicLayouter();
180     hierarchicLayouter.setFixedElementsLayerer(gll = new GivenLayersLayerer());
181     hintsFactory = hierarchicLayouter.createIncrementalHintsFactory();
182     hierarchicLayouter.setComponentLayouterEnabled(false);
183     hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
184 
185     hierarchicLayouter.getEdgeLayoutDescriptor().setSourcePortOptimizationEnabled(true);
186     hierarchicLayouter.getEdgeLayoutDescriptor().setTargetPortOptimizationEnabled(true);
187     hierarchicLayouter.setOrthogonallyRouted(true);
188 
189     // deferred since the mode is created in the super class's constructor
190     paMode.setSpc(sourcePortMap);
191     paMode.setTpc(targetPortMap);
192 
193     initGraph(graph);
194   }
195 
196   protected void initialize() {
197     super.initialize();
198 
199     // register graph listener to enable the layout button
200     // after the removal of graph elements
201     view.getGraph2D().addGraphListener(new GraphListener() {
202       public void onGraphEvent(GraphEvent e) {
203         final byte eventType = e.getType();
204         if(eventType == GraphEvent.POST_EDGE_REMOVAL
205             || eventType == GraphEvent.POST_NODE_REMOVAL) {
206           layoutAction.setEnabled(true);
207         }
208       }
209     });
210   }
211 
212   private void initGraph(Graph2D graph) {
213     Node n1 = graph.createNode();
214     layerIdMap.setInt(n1, 0);
215     Node n2 = graph.createNode(100.0, 0.0);
216     layerIdMap.setInt(n2, 1);
217     Node n3 = graph.createNode();
218     layerIdMap.setInt(n3, 2);
219     Node n4 = graph.createNode();
220     layerIdMap.setInt(n4, 2);
221     graph.createEdge(n1, n2);
222     graph.createEdge(n2, n4);
223     graph.createEdge(n1, n3);
224     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
225       Node n = nc.node();
226       graph.getRealizer(n).setLabelText(Integer.toString(n.index() + 1));
227     }
228     calcLayout();
229   }
230 
231   protected void registerViewModes() {
232     EditMode editMode = new IncrementalEditMode();
233     editMode.setMoveSelectionMode(paMode = new IncrementalMoveSelectionMode());
234     editMode.setPopupMode(new IncrementalPopupMode());
235     editMode.setCreateEdgeMode(new IncrementalEdgeCreateMode());
236     editMode.setHotSpotMode(new IncrementalHotSpotMode());
237     view.addViewMode(editMode);
238   }
239 
240   protected JToolBar createToolBar() {
241     JToolBar bar = super.createToolBar();
242     bar.addSeparator();
243     layoutAction = new LayoutAction();
244     bar.add(createActionControl(layoutAction, true));
245     return bar;
246   }
247 
248   protected void loadGraph(URL resource) {
249     loaded = true;
250     layoutAction.setEnabled(true);
251     if (layerDrawable != null) {
252       layerDrawable.clearLayers();
253     }
254     super.loadGraph(resource);
255   }
256 
257   /**
258    * Simple Layout action (incremental)
259    */
260   final class LayoutAction extends AbstractAction {
261     LayoutAction() {
262       super("Layout");
263       this.putValue(Action.SMALL_ICON, getIconResource("resource/layout.png"));
264       this.putValue(Action.SHORT_DESCRIPTION, "Apply first layout when a new graph is loaded.");
265     }
266 
267     public void actionPerformed(ActionEvent ev) {
268       calcLayout();
269     }
270   }
271 
272   /**
273    * Simple Layout action (from scratch)
274    */
275   final class FreshLayoutAction extends AbstractAction {
276     boolean resetPCs;
277 
278     FreshLayoutAction(String name, boolean resetPCs) {
279       super(name);
280       this.resetPCs = resetPCs;
281     }
282 
283     public void actionPerformed(ActionEvent ev) {
284       if (resetPCs) {
285         for (EdgeCursor ec = view.getGraph2D().edges(); ec.ok(); ec.next()) {
286           sourcePortMap.set(ec.edge(), null);
287           targetPortMap.set(ec.edge(), null);
288         }
289       }
290       byte oldMode = hierarchicLayouter.getLayoutMode();
291       hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_FROM_SCRATCH);
292       try {
293         calcLayout();
294       } finally {
295         hierarchicLayouter.setLayoutMode(oldMode);
296       }
297     }
298   }
299 
300   /**
301    * Optimizes nodes (inserts or recalculates layouts incrementally)
302    */
303   final class OptimizeNodesAction extends AbstractAction {
304     private NodeCursor nc;
305     private boolean resetPCs;
306 
307     public OptimizeNodesAction(String name, NodeCursor nodes, boolean resetPCs) {
308       super(name);
309       this.nc = nodes;
310       this.resetPCs = resetPCs;
311     }
312 
313     public void actionPerformed(ActionEvent ae) {
314       for (nc.toFirst(); nc.ok(); nc.next()) {
315         Node v = nc.node();
316         hintMap.set(v, hintsFactory.createLayerIncrementallyHint(v));
317         if (resetPCs) {
318           for (EdgeCursor ec = v.edges(); ec.ok(); ec.next()) {
319             if (ec.edge().source() == v) {
320               sourcePortMap.set(ec.edge(), null);
321             } else {
322               targetPortMap.set(ec.edge(), null);
323             }
324           }
325         }
326       }
327       calcLayout();
328       for (nc.toFirst(); nc.ok(); nc.next()) {
329         Node v = nc.node();
330         hintMap.set(v, null);
331       }
332     }
333   }
334 
335   /**
336    * Fixes nodes (inserts or recalculates layouts incrementally)
337    */
338   final class FixNodesAction extends AbstractAction {
339     private final NodeCursor nc;
340     private final boolean layer;
341     private final boolean sequence;
342 
343     public FixNodesAction(String name, NodeCursor nodes, boolean layer, boolean sequence) {
344       super(name);
345       this.nc = nodes;
346       this.layer = layer;
347       this.sequence = sequence;
348     }
349 
350     public void actionPerformed(ActionEvent ae) {
351       for (nc.toFirst(); nc.ok(); nc.next()) {
352         Node v = nc.node();
353         if (layer && sequence) {
354           hintMap.set(v, hintsFactory.createUseExactCoordinatesHint(v));
355           NodeRealizer realizer = view.getGraph2D().getRealizer(v);
356           realizer.setFillColor(Color.red);
357           realizer.repaint();
358         } else if (layer) {
359           hintMap.set(v, hintsFactory.createUseExactLayerCoordinatesHint(v));
360           NodeRealizer realizer = view.getGraph2D().getRealizer(v);
361           realizer.setFillColor(Color.red.darker());
362           realizer.repaint();
363         } else if (sequence) {
364           hintMap.set(v, hintsFactory.createUseExactSequenceCoordinatesHint(v));
365           NodeRealizer realizer = view.getGraph2D().getRealizer(v);
366           realizer.setFillColor(Color.red.darker().darker());
367           realizer.repaint();
368         } else {
369           hintMap.set(v, null);
370           NodeRealizer realizer = view.getGraph2D().getRealizer(v);
371           realizer.setFillColor(view.getGraph2D().getDefaultNodeRealizer().getFillColor());
372           realizer.repaint();
373         }
374       }
375     }
376   }
377 
378   /**
379    * Optimizes edges (inserts or recalculates layouts incrementally)
380    */
381   final class OptimizeEdgesAction extends AbstractAction {
382     private EdgeCursor ec;
383     private boolean resetPCs;
384 
385     public OptimizeEdgesAction(String name, EdgeCursor edges, boolean resetPCs) {
386       super(name);
387       this.ec = edges;
388       this.resetPCs = resetPCs;
389     }
390 
391     public void actionPerformed(ActionEvent ae) {
392       for (ec.toFirst(); ec.ok(); ec.next()) {
393         final Edge edge = ec.edge();
394         hintMap.set(edge, hintsFactory.createSequenceIncrementallyHint(edge));
395         if (resetPCs) {
396           sourcePortMap.set(edge, null);
397           targetPortMap.set(edge, null);
398         }
399       }
400       calcLayout();
401       for (ec.toFirst(); ec.ok(); ec.next()) {
402         Edge e = ec.edge();
403         hintMap.set(e, null);
404       }
405     }
406   }
407 
408   /**
409    * Drawable implementation and utility functions
410    */
411   static final class LayerDrawable implements Drawable {
412 
413     private static final Color[] colors = {new Color(150,200,255,128), new Color(220,240,240,128)};
414 
415     private List layers = new ArrayList(20);
416     private Rectangle bounds = new Rectangle(20, 20, 200, 200);
417 
418     private Graph2D graph;
419     private NodeMap layerIdMap;
420 
421     LayerDrawable(Graph2D graph, NodeMap layerIdMap) {
422       this.graph = graph;
423       this.layerIdMap = layerIdMap;
424     }
425 
426     public Rectangle getBounds() {
427       return bounds;
428     }
429 
430     public void clearLayers() {
431       layers.clear();
432     }
433 
434     public void updateLayers() {
435       final double spacing = 20.0d;
436       layers.clear();
437       if (graph.N() < 1) {
438         return;
439       }
440       double minX = Double.MAX_VALUE, maxX = -Double.MAX_VALUE;
441       for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
442         final Node node = nc.node();
443         final int layer = layerIdMap.getInt(node);
444         if (layer < 0) {
445           continue;
446         }
447         while (layers.size() - 1 < layer) {
448           layers.add(new Rectangle2D.Double(0.0, 0.0, -1.0, -1.0));
449         }
450         Rectangle2D.Double layerRect = (Rectangle2D.Double) layers.get(layer);
451         final NodeLayout nl = graph.getNodeLayout(node);
452         if (layerRect.width < 0.0) {
453           layerRect.setFrame(nl.getX(), nl.getY(), nl.getWidth(), nl.getHeight());
454         } else {
455           layerRect.add(nl.getX(), nl.getY());
456           layerRect.add(nl.getX() + nl.getWidth(), nl.getY() + nl.getHeight());
457         }
458         minX = Math.min(nl.getX(), minX);
459         maxX = Math.max(nl.getX() + nl.getWidth(), maxX);
460       }
461 
462       double minY = Double.MAX_VALUE;
463       double maxY = -Double.MAX_VALUE;
464       for (int i = 0; i < layers.size(); i++) {
465         Rectangle2D.Double rect = (Rectangle2D.Double) layers.get(i);
466         rect.x = minX - spacing;
467         rect.width = maxX - minX + spacing * 2.0;
468         if (i == 0) {
469           rect.y -= spacing;
470           rect.height += spacing;
471           minY = rect.y;
472         }
473         if (i == layers.size() - 1) {
474           rect.height += spacing;
475           maxY = rect.height + rect.y;
476         } else if (i < layers.size() - 1) {
477           Rectangle2D.Double nextRect = (Rectangle2D.Double) layers.get(i + 1);
478           final double mid = (rect.getY() + rect.getHeight() + nextRect.getY()) * 0.5d;
479           rect.height += mid - (rect.y + rect.height);
480           final double nextDelta = mid - nextRect.y;
481           nextRect.y += nextDelta;
482           nextRect.height -= nextDelta;
483         }
484       }
485       bounds.setFrame(minX - spacing, minY, maxX - minX + 2.0 * spacing, maxY - minY);
486       graph.updateViews();
487     }
488 
489     public final int inset = 8;
490 
491     public int getLayerId(double x, double y) {
492       if (x < bounds.x - outerInsets || x > bounds.x + bounds.width + outerInsets) {
493         return Integer.MAX_VALUE;
494       }
495       if (y < bounds.y + inset) {
496         return -1;
497       }
498       if (y > bounds.y + bounds.height - inset) {
499         return layers.size();
500       }
501       for (int i = 0; i < layers.size(); i++) {
502         final Rectangle2D.Double rect = (Rectangle2D.Double) layers.get(i);
503         if (y >= rect.y + inset && y <= rect.y + rect.height - inset) {
504           return i;
505         } else if (y < rect.y + inset) {
506           return -(i + 1);
507         }
508       }
509       return Integer.MAX_VALUE;
510     }
511 
512     public static final double outerInsets = 40.0;
513 
514     public Rectangle2D getLayerBounds(int layer) {
515       if (layer >= 0 && layer < layers.size()) {
516         Rectangle2D.Double rect = (Rectangle2D.Double) layers.get(layer);
517         rect = new Rectangle2D.Double(rect.x, rect.y + inset, rect.width, rect.height - (double) (2 * inset));
518         return rect;
519       }
520       if (layer == -1) {
521         return new Rectangle2D.Double(bounds.x, bounds.y - outerInsets, bounds.width, outerInsets + inset);
522       }
523       if (layer >= layers.size() && (layer != Integer.MAX_VALUE)) {
524         return new Rectangle2D.Double(bounds.x, bounds.y + bounds.height - inset, bounds.width, outerInsets);
525       }
526       if (layer < 0) {
527         int beforeLayer = -(layer + 1);
528         if (beforeLayer < layers.size()) {
529           Rectangle2D.Double rect = (Rectangle2D.Double) layers.get(beforeLayer);
530           rect = new Rectangle2D.Double(rect.x, rect.y - inset, rect.width, (double) (2 * inset));
531           return rect;
532         }
533       }
534       return new Rectangle2D.Double(bounds.x - 2 * outerInsets, bounds.y - 2 * outerInsets,
535           bounds.width + 4.0 * outerInsets, bounds.height + outerInsets * 4.0);
536     }
537 
538     public void paint(Graphics2D g) {
539       for (int i = 0; i < layers.size(); i++) {
540         Color color = colors[i % colors.length];
541         g.setColor(color);
542         g.fill((Shape) layers.get(i));
543       }
544     }
545   }
546 
547   /**
548    * Animated layout assignment
549    */
550   public void calcLayout() {
551     if (loaded) {
552       hierarchicLayouter.setFixedElementsLayerer(new AsIsLayerer());
553     } else {
554       hierarchicLayouter.setFixedElementsLayerer(gll);
555     }
556     loaded = false;
557     layoutAction.setEnabled(false);
558 
559     if (!view.getGraph2D().isEmpty()) {
560       gll.normalize(view.getGraph2D(), layerIdMap, layerIdMap);
561       Cursor oldCursor = view.getViewCursor();
562       try {
563         view.setViewCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
564         view.applyLayoutAnimated(hierarchicLayouter);
565       } finally {
566         view.setViewCursor(oldCursor);
567       }
568     }
569     layerDrawable.updateLayers();
570     view.updateView();
571   }
572 
573   /**
574    * Recalculate layout after resizing.
575    */
576   final class IncrementalHotSpotMode extends HotSpotMode {
577     public void mouseReleasedLeft(double x, double y) {
578       super.mouseReleasedLeft(x, y);
579       calcLayout();
580     }
581   }
582 
583   /**
584    * Recalculate layout after edge creation
585    */
586   final class IncrementalEdgeCreateMode extends CreateEdgeMode {
587     protected void edgeCreated(Edge edge) {
588       super.edgeCreated(edge);
589       EdgeRealizer er = view.getGraph2D().getRealizer(edge);
590       if (er.bendCount() > 0) {
591         parseBend(er.getBend(0));
592       }
593       if (er.bendCount() > 1) {
594         parseBend(er.getBend(er.bendCount() - 1));
595       }
596       if (er.bendCount() == 0) {
597         hintMap.set(edge, hintsFactory.createSequenceIncrementallyHint(edge));
598       }
599       calcLayout();
600       hintMap.set(edge, null);
601     }
602   }
603 
604   /**
605    * Recalculate layout after node creation
606    */
607   final class IncrementalEditMode extends EditMode {
608     protected void nodeCreated(Node v) {
609       super.nodeCreated(v);
610       final YPoint center = view.getGraph2D().getCenter(v);
611       int layerId = layerDrawable.getLayerId(center.x, center.y);
612       setLayers(new NodeList(v).nodes(), layerId, Integer.MAX_VALUE);
613       if (lastReleaseEvent.isControlDown()) { // fix the nodes position
614         hintMap.set(v, hintsFactory.createUseExactCoordinatesHint(v));
615         view.getGraph2D().getRealizer(v).setFillColor(Color.red);
616       }
617       calcLayout();
618     }
619   }
620 
621   /**
622    * Utility method to assign nodes to a new layer.
623    */
624   protected void setLayers(NodeCursor nodes, int newLayer, int previousLayer) {
625     if (!nodes.ok()) {
626       return;
627     }
628     //calculate number of layers to insert
629     int lesserLayers = 0;
630     int greaterLayers = 0;
631     final Set nodeSet = new HashSet();
632     for (nodes.toFirst(); nodes.ok(); nodes.next()) {
633       nodeSet.add(nodes.node());
634     }
635     if (previousLayer != Integer.MAX_VALUE) {
636       for (nodes.toFirst(); nodes.ok(); nodes.next()) {
637         int pLayer = layerIdMap.getInt(nodes.node());
638         if (pLayer < previousLayer) {
639           lesserLayers = Math.max(lesserLayers, previousLayer - pLayer);
640         }
641         if (pLayer > previousLayer) {
642           greaterLayers = Math.max(greaterLayers, pLayer - previousLayer);
643         }
644       }
645     } else {
646       previousLayer = 0;
647     }
648     final int newLayerCount = lesserLayers + greaterLayers + 1;
649     if (newLayer < 0) {
650       int beforeLayer = -(newLayer + 1);
651       for (NodeCursor nc = view.getGraph2D().nodes(); nc.ok(); nc.next()) {
652         if (!nodeSet.contains(nc.node())) {
653           int oldLayer = layerIdMap.getInt(nc.node());
654           if (oldLayer >= beforeLayer) {
655             layerIdMap.setInt(nc.node(), oldLayer + newLayerCount);
656           }
657         }
658       }
659       for (nodes.toFirst(); nodes.ok(); nodes.next()) {
660         int oldLayer = layerIdMap.getInt(nodes.node());
661         layerIdMap.setInt(nodes.node(), beforeLayer + lesserLayers + oldLayer - previousLayer);
662       }
663     } else {
664       if (newLayer == Integer.MAX_VALUE) {
665         int maxLayer = -1;
666         for (NodeCursor nc = view.getGraph2D().nodes(); nc.ok(); nc.next()) {
667           if (!nodeSet.contains(nc.node())) {
668             int layer = layerIdMap.getInt(nc.node());
669             maxLayer = Math.max(layer, maxLayer);
670           }
671         }
672         newLayer = maxLayer + 1;
673       }
674       if (lesserLayers > 0 || greaterLayers > 0) {
675         for (NodeCursor nc = view.getGraph2D().nodes(); nc.ok(); nc.next()) {
676           if (!nodeSet.contains(nc.node())) {
677             int layer = layerIdMap.getInt(nc.node());
678             if (layer == newLayer) {
679               layerIdMap.setInt(nc.node(), layer + lesserLayers);
680             } else if (layer > newLayer) {
681               layerIdMap.setInt(nc.node(), layer + newLayerCount);
682             }
683           }
684         }
685       }
686       for (nodes.toFirst(); nodes.ok(); nodes.next()) {
687         int oldLayer = layerIdMap.getInt(nodes.node());
688         layerIdMap.setInt(nodes.node(), newLayer + lesserLayers + oldLayer - previousLayer);
689       }
690     }
691   }
692 
693   /**
694    * Utility method to assign PCs from the sketch
695    */
696   public void parseBend(Bend b) {
697     Edge e = b.getEdge();
698     EdgeRealizer er = view.getGraph2D().getRealizer(e);
699     if (b == er.getBend(0)) {
700       YPoint center = view.getGraph2D().getCenter(e.source());
701       sourcePortMap.set(e, getPortConstraint(b.getX() - center.x, b.getY() - center.y));
702     }
703     if (b == er.getBend(er.bendCount() - 1)) {
704       YPoint center = view.getGraph2D().getCenter(e.target());
705       targetPortMap.set(e, getPortConstraint(b.getX() - center.x, b.getY() - center.y));
706     }
707   }
708 
709   /**
710    * Helper method to assign PCs from the sketch
711    */
712   private static PortConstraint getPortConstraint(final double bdx, final double bdy) {
713     if (Math.abs(bdx) > Math.abs(bdy)) {
714       return PortConstraint.create(bdx > 0.0 ? PortConstraint.EAST : PortConstraint.WEST);
715     } else {
716       return PortConstraint.create(bdy > 0.0 ? PortConstraint.SOUTH : PortConstraint.NORTH);
717     }
718   }
719 
720   /**
721    * Provides popup menus for all kinds of actions
722    */
723   final class IncrementalPopupMode extends PopupMode {
724 
725     public JPopupMenu getNodePopup(final Node v) {
726       JPopupMenu pm = new JPopupMenu();
727       NodeCursor node = new NodeList(v).nodes();
728       addNodeActions(pm, node);
729       return pm;
730     }
731 
732     private void addNodeActions(JPopupMenu pm, NodeCursor node) {
733       pm.add(new OptimizeNodesAction("Optimize Node", node, false));
734       pm.add(new OptimizeNodesAction("Optimize Node and Reset PCs", node, true));
735       JMenu fixNodesMenu = new JMenu("Fix nodes");
736       pm.add(fixNodesMenu);
737       fixNodesMenu.add(new FixNodesAction("Fix Coordinates", node, true, true));
738       fixNodesMenu.add(new FixNodesAction("Fix Layer Coordinates", node, true, false));
739       fixNodesMenu.add(new FixNodesAction("Fix Sequence Coordinates", node, false, true));
740       fixNodesMenu.add(new FixNodesAction("Unfix Coordinates", node, false, false));
741     }
742 
743     public JPopupMenu getEdgePopup(final Edge e) {
744       JPopupMenu pm = new JPopupMenu();
745       addEdgeActions(pm, new EdgeList(e).edges());
746       return pm;
747     }
748 
749     public JPopupMenu getSelectionPopup(double x, double y) {
750       JPopupMenu pm = new JPopupMenu();
751       final NodeCursor snc = getGraph2D().selectedNodes();
752       if (snc.ok()) {
753         addNodeActions(pm, snc);
754       } else {
755         final EdgeCursor sec = getGraph2D().selectedEdges();
756         if (sec.ok()) {
757           addEdgeActions(pm, sec);
758         } else {
759           return null;
760         }
761       }
762       return pm;
763     }
764 
765     private void addEdgeActions(JPopupMenu pm, EdgeCursor sec) {
766       pm.add(new OptimizeEdgesAction("Optimize Edges", sec, false));
767       pm.add(new OptimizeEdgesAction("Optimize Edges and Reset PCs", sec, true));
768     }
769 
770     public JPopupMenu getPaperPopup(double x, double y) {
771       if (getGraph2D().isEmpty()) {
772         return null;
773       }
774       JPopupMenu pm = new JPopupMenu();
775       pm.add(new FreshLayoutAction("Fresh Layout", false));
776       pm.add(new FreshLayoutAction("Fresh Layout and Reset PCs", true));
777       if (getGraph2D().E() > 0) {
778         addEdgeActions(pm, getGraph2D().edges());
779       }
780       return pm;
781     }
782   }
783 
784   /**
785    * Recalculate layout after selection move
786    */
787   final class IncrementalMoveSelectionMode extends PortAssignmentMoveSelectionMode {
788     private boolean firstTime = true;
789     private MoveSelectionDrawable drawable;
790     private NodeList selectedNodes;
791     private BendList selectedBends;
792 
793     IncrementalMoveSelectionMode() {
794       super(null, null);
795     }
796 
797     protected void selectionMovedAction(double dx, double dy, double x, double y) {
798       super.selectionMovedAction(dx, dy, x, y);
799       if (selectedNodes != null) {
800         view.removeBackgroundDrawable(drawable);
801         drawable = null;
802         int newLayer = layerDrawable.getLayerId(x, y);
803         HitInfo hi = this.getLastHitInfo();
804         Node movedNode = hi.getHitNode();
805         int originalLayer = movedNode != null ? layerIdMap.getInt(movedNode) : Integer.MAX_VALUE;
806         if (newLayer != originalLayer) {
807           setLayers(selectedNodes.nodes(), newLayer, originalLayer);
808         }
809         if (newLayer != Integer.MAX_VALUE) {
810           List hints = new ArrayList(128);
811           for (NodeCursor nc = selectedNodes.nodes(); nc.ok(); nc.next()) {
812             for (EdgeCursor edges = nc.node().edges(); edges.ok(); edges.next()) {
813               hints.add(edges.edge());
814               hintMap.set(edges.edge(), hintsFactory.createSequenceIncrementallyHint(edges.edge()));
815             }
816           }
817           calcLayout();
818           for (int i = 0; i < hints.size(); i++) {
819             hintMap.set(hints.get(i), null);
820           }
821         } else {
822           List hints = new ArrayList(128);
823           for (NodeCursor nc = selectedNodes.nodes(); nc.ok(); nc.next()) {
824             hints.add(nc.node());
825             hintMap.set(nc.node(), hintsFactory.createLayerIncrementallyHint(nc.node()));
826           }
827           calcLayout();
828           for (int i = 0; i < hints.size(); i++) {
829             Node node = (Node) hints.get(i);
830             hintMap.set(node, null);
831           }
832           layerDrawable.updateLayers();
833         }
834         selectedNodes = null;
835       } else if (selectedBends != null) {
836         calcLayout();
837       }
838       selectedBends = null;
839       selectedNodes = null;
840       firstTime = true;
841     }
842 
843     protected void selectionOnMove(double dx, double dy, double x, double y) {
844       if (firstTime) {
845         firstTime = false;
846         Graph2D g = getGraph2D();
847         NodeCursor nc = g.selectedNodes();
848         selectedBends = null;
849         selectedNodes = null;
850         if (nc.ok()) {
851           selectedNodes = new NodeList(nc);
852           drawable = new MoveSelectionDrawable();
853           view.addBackgroundDrawable(drawable);
854         }
855         BendCursor bc = g.selectedBends();
856         if (selectedNodes == null && bc.ok()) {
857           selectedBends = new BendList(bc);
858         }
859       }
860       super.selectionOnMove(dx, dy, x, y);
861       if (selectedNodes != null) {
862         int layer = layerDrawable.getLayerId(x, y);
863         drawable.layer = layer;
864         drawable.layerCount = layerDrawable.layers.size();
865         drawable.drawable = layerDrawable.getLayerBounds(layer);
866       }
867     }
868 
869     final class MoveSelectionDrawable implements Drawable {
870       Shape drawable;
871       int layer;
872       int layerCount;
873       Color color = Color.red;
874       Color color2 = Color.orange;
875       Color color3 = Color.red.darker();
876 
877       public Rectangle getBounds() {
878         return drawable.getBounds();
879       }
880 
881       public void paint(Graphics2D g) {
882         Stroke s = g.getStroke();
883         if (layer == Integer.MAX_VALUE) {
884           g.setColor(color3);
885           g.setStroke(LineType.DOTTED_3);
886           g.draw(drawable);
887         } else {
888           if (layer >= 0 && layer < layerCount) {
889             g.setColor(color);
890             g.setStroke(LineType.LINE_3);
891             g.draw(drawable);
892           } else {
893             g.setColor(color2);
894             g.fill(drawable);
895           }
896         }
897         g.setStroke(s);
898       }
899     }
900   }
901 
902   /**
903    * Launches this demo.
904    */
905   public static void main(String[] args) {
906     EventQueue.invokeLater(new Runnable() {
907       public void run() {
908         Locale.setDefault(Locale.ENGLISH);
909         initLnF();
910         (new IncrementalHierarchicLayouterDemo()).start("Incremental Hierarchic Layouter Demo");
911       }
912     });
913   }
914 }
915