1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.6. 
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-2008 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.layout.tree;
15  
16  import demo.view.DemoBase;
17  import y.algo.Bfs;
18  import y.base.Edge;
19  import y.base.EdgeMap;
20  import y.base.Node;
21  import y.base.NodeCursor;
22  import y.base.NodeList;
23  import y.base.NodeMap;
24  import y.geom.YPoint;
25  import y.layout.BufferedLayouter;
26  import y.layout.GraphLayout;
27  import y.layout.PortConstraint;
28  import y.layout.PortConstraintKeys;
29  import y.layout.tree.DefaultNodePlacer;
30  import y.layout.tree.GenericTreeLayouter;
31  import y.layout.tree.NodePlacer;
32  import y.util.DataProviderAdapter;
33  import y.view.Arrow;
34  import y.view.CreateChildEdgeMode;
35  import y.view.EdgeRealizer;
36  import y.view.EditMode;
37  import y.view.Graph2D;
38  import y.view.Graph2DSelectionEvent;
39  import y.view.Graph2DSelectionListener;
40  import y.view.HotSpotMode;
41  import y.view.LayoutMorpher;
42  import y.view.LineType;
43  import y.view.NodeRealizer;
44  import y.view.PolyLineEdgeRealizer;
45  import y.view.PopupMode;
46  import y.view.PortAssignmentMoveSelectionMode;
47  
48  import javax.swing.AbstractAction;
49  import javax.swing.JButton;
50  import javax.swing.JLabel;
51  import javax.swing.JMenu;
52  import javax.swing.JPanel;
53  import javax.swing.JPopupMenu;
54  import javax.swing.JSpinner;
55  import javax.swing.JSplitPane;
56  import javax.swing.SpinnerNumberModel;
57  import javax.swing.event.ChangeEvent;
58  import javax.swing.event.ChangeListener;
59  import java.awt.BorderLayout;
60  import java.awt.Color;
61  import java.awt.Cursor;
62  import java.awt.FlowLayout;
63  import java.awt.event.ActionEvent;
64  import java.util.ArrayList;
65  import java.util.List;
66  
67  /**
68   * This demo shows how GenericTreeLayouter can handle port constraints and multiple 
69   * different NodePlacer instances and implementations at the same time. 
70   * <br>
71   * On another note, it also demonstrates how different ViewModes can be subclassed 
72   * or replaced to achieve a completely different application feel. 
73   */
74  public class IncrementalTreeLayouterDemo extends DemoBase
75  {
76    private EdgeMap sourcePortMap;
77    private EdgeMap targetPortMap;
78    private NodeMap portAssignmentMap;
79    private NodeMap nodePlacerMap;
80    private PortAssignmentMoveSelectionMode paMode;
81  
82    private double hDistance = 40;
83    private double vDistance = 40;
84  
85    private GenericTreeLayouter treeLayouter;
86  
87    private DefaultNodePlacerConfigPanel configPanel;
88  
89    private Color[] layerColors = new Color[]{Color.red, Color.orange, Color.yellow, Color.cyan, Color.green, Color.blue};
90  
91    private List layerStyles = new ArrayList();
92    {
93      layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_MEDIAN, 40, 40));
94      layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT, DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET, 20, 40));
95      layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET, DefaultNodePlacer.ROUTING_FORK_AT_ROOT, 10, 20));
96      layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_MEDIAN, 40, 40));
97    }
98  
99    public IncrementalTreeLayouterDemo()
100   {
101     final Graph2D graph = view.getGraph2D();
102     EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
103     defaultER.setArrow(Arrow.STANDARD);
104     ((PolyLineEdgeRealizer)defaultER).setSmoothedBends(true);
105     defaultER.setLineType(LineType.LINE_2);
106 
107     sourcePortMap = graph.createEdgeMap();
108     targetPortMap = graph.createEdgeMap();
109     portAssignmentMap = graph.createNodeMap();
110     nodePlacerMap = graph.createNodeMap();
111     graph.addDataProvider(GenericTreeLayouter.NODE_PLACER_DPKEY, nodePlacerMap);
112     graph.addDataProvider(GenericTreeLayouter.PORT_ASSIGNMENT_DPKEY, portAssignmentMap);
113     graph.addDataProvider(GenericTreeLayouter.CHILD_COMPARATOR_DPKEY, new ChildEdgeComparatorProvider());
114     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, sourcePortMap);
115     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, targetPortMap);
116 
117 
118     paMode.setSpc(sourcePortMap);
119     paMode.setTpc(targetPortMap);
120 
121     treeLayouter = new GenericTreeLayouter();
122 
123     configPanel = new DefaultNodePlacerConfigPanel();
124     configPanel.adoptPlacerValues((NodePlacer)layerStyles.get(0));
125 
126     JPanel layerChooserPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
127     layerChooserPanel.add(new JLabel("Layer: "));
128     final JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1));
129     spinner.addChangeListener(new ChangeListener()
130       {
131         public void stateChanged(ChangeEvent ce){
132           final int layer = ((Number) spinner.getValue()).intValue() - 1;
133           while (layer >= layerStyles.size()){
134             layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, 40, 40));
135           }
136           NodePlacer placer = (NodePlacer) layerStyles.get(layer);
137           configPanel.adoptPlacerValues(placer);
138         }
139       }
140     );
141     layerChooserPanel.add(spinner);
142 
143     configPanel.addChangeListener(new ChangeListener()
144     {
145       public void stateChanged(ChangeEvent ce){
146         final int layer = ((Number) spinner.getValue()).intValue() - 1;
147         while (layer >= layerStyles.size()){
148           layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, 40, 40));
149         }
150         layerStyles.set(layer, configPanel.createPlacerCopy());
151       }
152     }
153     );
154 
155     JButton button = new JButton(new AbstractAction("Apply"){
156       public void actionPerformed(ActionEvent ae){
157         final int layer = ((Number) spinner.getValue()).intValue() - 1;
158         NodePlacer placer = (NodePlacer) layerStyles.get(layer);
159         NodeList[] layers = Bfs.getLayers(graph, new NodeList(graph.firstNode()));
160         if (layer < layers.length){
161           for (NodeCursor nc = layers[layer].nodes(); nc.ok(); nc.next()){
162             nodePlacerMap.set(nc.node(), placer);
163           }
164           calcLayout();
165         }
166       }
167     });
168 
169     graph.addGraph2DSelectionListener(new Graph2DSelectionListener()
170     {
171       public void onGraph2DSelectionEvent(Graph2DSelectionEvent ev){
172         if (ev.isNodeSelection() && graph.isSelectionSingleton()){
173           Node n = (Node) ev.getSubject();
174           int depth = 1;
175           while (n.inDegree() > 0){
176             n = n.firstInEdge().source();
177             depth++;
178           }
179           spinner.setValue(new Integer(depth));
180         }
181       }
182     }
183     );
184     layerChooserPanel.add(button);
185 
186     JPanel rightPanel = new JPanel(new BorderLayout());
187     rightPanel.add(configPanel, BorderLayout.CENTER);
188     rightPanel.add(layerChooserPanel, BorderLayout.NORTH);
189 
190     JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, rightPanel, view);
191     sp.setOneTouchExpandable(true);
192     sp.setContinuousLayout(false);
193     contentPane.add(sp, BorderLayout.CENTER);
194     createSampleGraph(graph);
195   }
196 
197   final class ChildEdgeComparatorProvider extends DataProviderAdapter {
198     public Object get(Object forRootNode){
199       NodePlacer placer = (NodePlacer) nodePlacerMap.get(forRootNode);
200       if (placer instanceof DefaultNodePlacer){
201         return ((DefaultNodePlacer)placer).createComparator();
202       }
203       return null;
204     }
205   }
206 
207   private void createSampleGraph(Graph2D graph){
208     graph.clear();
209     Node root = graph.createNode();
210     graph.getRealizer(root).setFillColor(layerColors[0]);
211     nodePlacerMap.set(root, layerStyles.get(0));
212     createChildren(graph, root, 4, 1, 2);
213     calcLayout();
214   }
215 
216   private void createChildren(Graph2D graph, Node root, int children, int layer, int layers){
217     for (int i = 0; i < children; i++){
218       Node child = graph.createNode();
219       graph.createEdge(root, child);
220       graph.getRealizer(child).setFillColor(layerColors[layer % layerColors.length]);
221       if (layerStyles.size() > layer){
222         nodePlacerMap.set(child, layerStyles.get(layer));
223       }
224       if (layers > 0){
225         createChildren(graph, child, children, layer+1, layers-1);
226       }
227     }
228   }
229 
230   protected boolean isDeletionEnabled(){
231     return false;
232   }
233 
234   protected void registerViewModes() {
235     EditMode editMode = new TreeCreateEditMode();
236     view.addViewMode( editMode );
237   }
238 
239 
240   public void calcLayout(){
241     if (!view.getGraph2D().isEmpty()){
242       if (true) {
243         Cursor oldCursor = view.getCanvasComponent().getCursor();
244         try {
245           view.getCanvasComponent().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
246           GraphLayout result = new BufferedLayouter(treeLayouter).calcLayout(view.getGraph2D());
247           LayoutMorpher morpher = new LayoutMorpher(view, result);
248           morpher.setSmoothViewTransform(true);
249           morpher.setPreferredDuration(300);
250           morpher.execute();
251         } finally {
252           view.getCanvasComponent().setCursor(oldCursor);
253         }
254       } else {
255         new BufferedLayouter(treeLayouter).doLayout(view.getGraph2D());
256       }
257     }
258     view.fitContent();
259     view.updateView();
260   }
261 
262   final class TreeCreateChildEdgeMode extends CreateChildEdgeMode {
263     protected void edgeCreated(Edge e){
264       int depth = 1;
265       for (Node n = e.source(); n.inDegree() > 0; n = n.firstInEdge().source()){
266         depth++;
267       }
268       Graph2D g = getGraph2D();
269       g.getRealizer(e.target()).setFillColor(layerColors[depth % layerColors.length]);
270       EdgeRealizer er = g.getRealizer(e);
271       if (nodePlacerMap.get(e.source()) == null){
272         parseNodePlaceMent(g, e, er);
273       }
274       if (layerStyles.size() > depth){
275         nodePlacerMap.set(e.target(), layerStyles.get(depth));
276       }
277       parseTargetPort(g, e, er);
278       g.unselectAll();
279       calcLayout();
280       g.setSelected(e.target(), true);
281     }
282 
283     private void parseNodePlaceMent(Graph2D g, Edge e, EdgeRealizer er){
284       YPoint firstPoint = er.bendCount() > 0 ? new YPoint(er.firstBend().getX(), er.firstBend().getY()) :
285       g.getTargetPointAbs(e);
286       NodeRealizer source = g.getRealizer(e.source());
287       double dx = firstPoint.x - source.getCenterX();
288       double dy = firstPoint.y - source.getCenterY();
289       final byte placement;
290       final byte alignment = DefaultNodePlacer.ALIGNMENT_MEDIAN;
291       final byte routing = DefaultNodePlacer.ROUTING_FORK;
292       if (Math.abs(dx) > Math.abs(dy)){
293         if (dx > 0){
294           placement = DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT;
295         } else {
296           placement = DefaultNodePlacer.PLACEMENT_VERTICAL_TO_LEFT;
297         }
298       } else {
299         if (dy > 0){
300           placement = DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD;
301         } else {
302           placement = DefaultNodePlacer.PLACEMENT_HORIZONTAL_UPWARD;
303         }
304       }
305       nodePlacerMap.set(e.source(), new DefaultNodePlacer(placement, alignment, routing, hDistance, vDistance));
306     }
307 
308     private void parseTargetPort(Graph2D g, Edge e, EdgeRealizer er){
309       if (er.bendCount() > 0){
310         YPoint lastPoint = new YPoint(er.lastBend().getX(), er.lastBend().getY());
311         NodeRealizer target = g.getRealizer(e.target());
312         double dx = lastPoint.x - target.getCenterX();
313         double dy = lastPoint.y - target.getCenterY();
314         byte side = PortConstraint.ANY_SIDE;
315         if (Math.abs(dx) > Math.abs(dy)){
316           if (dx > 0){
317             side = PortConstraint.EAST;
318           } else {
319             side = PortConstraint.WEST;
320           }
321         } else {
322           if (dy > 0){
323             side = PortConstraint.SOUTH;
324           } else {
325             side = PortConstraint.NORTH;
326           }
327         }
328         targetPortMap.set(e, PortConstraint.create(side));
329       }
330     }
331 
332     protected NodeRealizer createChildNodeRealizer()
333     {
334       NodeRealizer retValue;
335       retValue = super.createChildNodeRealizer();
336       retValue.setLabelText("");
337       return retValue;
338     }
339 
340   }
341 
342 
343   final class TreeLayouterPopupMode extends PopupMode {
344     private JPopupMenu nodePlacementMenu;
345 
346     public TreeLayouterPopupMode(){
347       nodePlacementMenu = new JPopupMenu();
348       JMenu alignment = new JMenu("Root node Alignment");
349       JMenu placement = new JMenu("Child Placement");
350       JMenu routing = new JMenu("Routing Style");
351 
352       nodePlacementMenu.add(placement);
353       nodePlacementMenu.add(alignment);
354       nodePlacementMenu.add(routing);
355 
356       placement.add(new PlacementAction("Horizontally Downwards", DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD));
357       placement.add(new PlacementAction("Horizontally Upwards", DefaultNodePlacer.PLACEMENT_HORIZONTAL_UPWARD));
358       placement.add(new PlacementAction("Vertically to Left", DefaultNodePlacer.PLACEMENT_VERTICAL_TO_LEFT));
359       placement.add(new PlacementAction("Vertically to right", DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT));
360 
361       alignment.add(new AlignmentAction("Offset Leading", DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET));
362       alignment.add(new AlignmentAction("Leading", DefaultNodePlacer.ALIGNMENT_LEADING));
363       alignment.add(new AlignmentAction("Centered", DefaultNodePlacer.ALIGNMENT_CENTER));
364       alignment.add(new AlignmentAction("Median", DefaultNodePlacer.ALIGNMENT_MEDIAN));
365       alignment.add(new AlignmentAction("Trailing", DefaultNodePlacer.ALIGNMENT_TRAILING));
366       alignment.add(new AlignmentAction("Offset Trailing", DefaultNodePlacer.ALIGNMENT_TRAILING_OFFSET));
367 
368       routing.add(new RoutingAction("Fork", DefaultNodePlacer.ROUTING_FORK));
369       routing.add(new RoutingAction("Fork at Root", DefaultNodePlacer.ROUTING_FORK_AT_ROOT));
370       routing.add(new RoutingAction("Poly Line", DefaultNodePlacer.ROUTING_POLY_LINE));
371       routing.add(new RoutingAction("Straight", DefaultNodePlacer.ROUTING_STRAIGHT));
372     }
373 
374     public JPopupMenu getNodePopup(final Node v)
375     {
376       return nodePlacementMenu;
377     }
378 
379     public JPopupMenu getSelectionPopup(double x, double y)
380     {
381       if (getGraph2D().selectedNodes().ok()){
382         return nodePlacementMenu;
383       } else {
384         return null;
385       }
386     }
387 
388   }
389 
390   abstract class AssignLayouterAction extends AbstractAction {
391 
392     public AssignLayouterAction(String name){
393       super(name);
394     }
395 
396     public void actionPerformed(ActionEvent e)
397     {
398       NodeList selectedNodes = new NodeList(IncrementalTreeLayouterDemo.this.view.getGraph2D().selectedNodes());
399 
400       NodePlacer placer = (NodePlacer) nodePlacerMap.get(selectedNodes.firstNode());
401       placer = getPlacer(placer);
402 
403       DefaultNodePlacer dnp = (DefaultNodePlacer) placer;
404       for (NodeCursor nc = selectedNodes.nodes(); nc.ok(); nc.next()){
405         nodePlacerMap.set(nc.node(), new DefaultNodePlacer(
406           dnp.getChildPlacement(),
407           dnp.getRootAlignment(),
408           dnp.getRoutingStyle(),
409           dnp.getHorizontalDistance(),
410           dnp.getVerticalDistance()));
411       }
412       calcLayout();
413     }
414 
415     protected abstract NodePlacer getPlacer(NodePlacer placer);
416   }
417 
418   final class PlacementAction extends AssignLayouterAction {
419 
420     private byte newPlacement;
421     private Node node;
422 
423     public PlacementAction(String name, byte newPlacement){
424       super(name);
425       this.newPlacement = newPlacement;
426     }
427 
428     protected NodePlacer getPlacer(NodePlacer placer)
429     {
430       if (placer instanceof DefaultNodePlacer){
431         placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
432         ((DefaultNodePlacer)placer).setChildPlacement(newPlacement);
433       } else {
434         placer = new DefaultNodePlacer(newPlacement, DefaultNodePlacer.ALIGNMENT_MEDIAN, hDistance, vDistance);
435       }
436       return placer;
437     }
438   }
439 
440   final class AlignmentAction extends AssignLayouterAction {
441     private byte newAlignment;
442 
443     public AlignmentAction(String name, byte newAlignment){
444       super(name);
445       this.newAlignment = newAlignment;
446     }
447 
448     protected NodePlacer getPlacer(NodePlacer placer)
449     {
450       if (placer instanceof DefaultNodePlacer){
451         placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
452         ((DefaultNodePlacer)placer).setRootAlignment(newAlignment);
453       } else {
454         placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, newAlignment, hDistance, vDistance);
455       }
456       return placer;
457     }
458   }
459 
460   final class RoutingAction extends AssignLayouterAction {
461     private byte newRouting;
462 
463     public RoutingAction(String name, byte newRouting){
464       super(name);
465       this.newRouting = newRouting;
466     }
467 
468     protected NodePlacer getPlacer(NodePlacer placer)
469     {
470       if (placer instanceof DefaultNodePlacer){
471         placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
472         ((DefaultNodePlacer)placer).setRoutingStyle(newRouting);
473       } else {
474         placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_CENTER, newRouting, hDistance, vDistance);
475       }
476       return placer;
477     }
478   }
479 
480   final class TreeHotSpotMode extends HotSpotMode {
481     public void mouseReleasedLeft(double x, double y)
482     {
483       super.mouseReleasedLeft(x, y);
484       calcLayout();
485     }
486   }
487 
488   final class TreeCreateEditMode extends EditMode {
489     TreeCreateEditMode(){
490       super();
491       setMoveSelectionMode(paMode = new TreePortAssignmentMode());
492       setCreateEdgeMode(new TreeCreateChildEdgeMode());
493       setHotSpotMode(new TreeHotSpotMode());
494       setPopupMode(new TreeLayouterPopupMode());
495     }
496 
497     public boolean doAllowNodeCreation()
498     {
499       return getGraph2D().N() == 0;
500     }
501 
502     protected void nodeCreated(Node v)
503     {
504       super.nodeCreated(v);
505       nodePlacerMap.set(v, configPanel.createPlacerCopy());
506     }
507 
508   }
509 
510   final class TreePortAssignmentMode extends PortAssignmentMoveSelectionMode {
511     TreePortAssignmentMode(){
512       super(null, null);
513     }
514 
515     protected boolean isPortReassignmentAllowed(Edge edge, boolean source)
516     {
517       return !source;
518     }
519 
520 //    protected void portConstraintsUpdated(Edge onEdge)
521 //    {
522 //      calcLayout();
523 //    }
524 
525     protected void selectionMovedAction(double dx, double dy, double x, double y)
526     {
527       super.selectionMovedAction(dx, dy, x, y);
528       calcLayout();
529     }
530 
531   }
532 
533   /**
534    * Launches this demo.
535    */
536   public static void main(String args[])
537   {
538     initLnF();
539     IncrementalTreeLayouterDemo demo = new IncrementalTreeLayouterDemo();
540     demo.start("Incremental Tree Layouter Demo");
541   }
542 }
543