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