1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.layout.tree;
29  
30  import y.base.Node;
31  import y.base.NodeCursor;
32  import y.layout.tree.AbstractRotatableNodePlacer;
33  import y.layout.tree.AbstractRotatableNodePlacer.Matrix;
34  import y.layout.tree.AbstractRotatableNodePlacer.RootAlignment;
35  import y.layout.tree.BusPlacer;
36  import y.layout.tree.DoubleLinePlacer;
37  import y.layout.tree.GridNodePlacer;
38  import y.layout.tree.LeftRightPlacer;
39  import y.layout.tree.NodePlacer;
40  import y.layout.tree.SimpleNodePlacer;
41  import y.util.DataProviderAdapter;
42  import y.view.Arrow;
43  import y.view.EdgeRealizer;
44  import y.view.Graph2D;
45  import y.view.Graph2DSelectionEvent;
46  import y.view.Graph2DSelectionListener;
47  import y.view.LineType;
48  import y.view.PolyLineEdgeRealizer;
49  
50  import javax.swing.DefaultComboBoxModel;
51  import javax.swing.DefaultListCellRenderer;
52  import javax.swing.JButton;
53  import javax.swing.JComboBox;
54  import javax.swing.JLabel;
55  import javax.swing.JList;
56  import javax.swing.JPanel;
57  import javax.swing.JSplitPane;
58  import java.awt.BorderLayout;
59  import java.awt.Component;
60  import java.awt.EventQueue;
61  import java.awt.FlowLayout;
62  import java.awt.GridBagConstraints;
63  import java.awt.GridBagLayout;
64  import java.awt.Insets;
65  import java.awt.event.ActionEvent;
66  import java.awt.event.ActionListener;
67  import java.awt.event.ItemEvent;
68  import java.awt.event.ItemListener;
69  import java.util.Locale;
70  
71  /**
72   * This demo presents GenericTreeLayouter in conjunction with {@link NodePlacer}s that support
73   * subtree rotation. The NodePlacers, rotations and root alignments for the selected nodes may
74   * be changed using the panel on the left side.
75   *
76   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/cls_GenericTreeLayouter" target="_blank">Section Generic Tree Layout</a> in the yFiles for Java Developer's Guide
77   **/
78  public class RotatableNodePlacersDemo extends AbstractTreeDemo {
79    private JComboBox nodePlacerCombo;
80    private JComboBox rootAlignmentCombo;
81    private JButton rotLeftButton;
82    private JButton rotRightButton;
83    private JButton mirHorButton;
84    private JButton mirVertButton;
85  
86    public static void main(String[] args) {
87      EventQueue.invokeLater(new Runnable() {
88        public void run() {
89          Locale.setDefault(Locale.ENGLISH);
90          initLnF();
91          (new RotatableNodePlacersDemo()).start();
92        }
93      });
94    }
95  
96    public RotatableNodePlacersDemo() {
97      Graph2D graph = view.getGraph2D();
98  
99      /**
100      * Listener for the graph that either updates the panel on the left when at least one node is selected or disables all panel items otherwise.
101      */
102     graph.addGraph2DSelectionListener(new Graph2DSelectionListener() {
103       public void onGraph2DSelectionEvent(Graph2DSelectionEvent e) {
104         if (view.getGraph2D().selectedNodes().ok()) {
105           readComboValues();
106         } else {
107           setEnabled(false);
108         }
109       }
110     });
111 
112     //Realizers
113     EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
114     defaultER.setArrow(Arrow.STANDARD);
115     ((PolyLineEdgeRealizer) defaultER).setSmoothedBends(true);
116     defaultER.setLineType(LineType.LINE_2);
117 
118 
119     JPanel configPanel = new JPanel(new GridBagLayout());
120     GridBagConstraints constraints = new GridBagConstraints();
121 
122     constraints.fill = GridBagConstraints.HORIZONTAL;
123     constraints.insets = new Insets(10, 10, 10, 10);
124     configPanel.add(new JLabel("Settings for actual selection"), constraints);
125 
126     constraints.gridy = 1;
127     constraints.insets = new Insets(5, 5, 0, 0);
128     configPanel.add(new JLabel("NodePlacer:"), constraints);
129 
130     constraints.gridy = 2;
131     constraints.insets = new Insets(0, 0, 0, 0);
132     nodePlacerCombo = new JComboBox();
133     nodePlacerCombo.setModel(new DefaultComboBoxModel(new String[]{"SimpleNodePlacer", "DoubleLinePlacer", "BusPlacer", "LeftRightPlacer", "GridNodePlacer"}));
134     nodePlacerCombo.addItemListener(new ItemListener() {
135       public void itemStateChanged(ItemEvent e) {
136         if (e.getStateChange() == ItemEvent.SELECTED) {
137           changeNodePlacersForSelection();
138         }
139       }
140     });
141     configPanel.add(nodePlacerCombo, constraints);
142 
143     constraints.gridy = 3;
144     constraints.insets = new Insets(5, 5, 0, 0);
145     configPanel.add(new JLabel("Rotation:"), constraints);
146 
147     constraints.gridy = 4;
148     constraints.insets = new Insets(0, 0, 0, 0);
149 
150     JPanel rotationPanel = new JPanel();
151     configPanel.add(rotationPanel, constraints);
152     rotationPanel.setLayout(new FlowLayout());
153     rotLeftButton = new JButton("Left");
154     rotLeftButton.addActionListener(new ActionListener() {
155       public void actionPerformed(ActionEvent e) {
156         rotate(Matrix.ROT90);
157       }
158     });
159     rotationPanel.add(rotLeftButton);
160     rotRightButton = new JButton("Right");
161     rotRightButton.addActionListener(new ActionListener() {
162       public void actionPerformed(ActionEvent e) {
163         rotate(Matrix.ROT270);
164       }
165     });
166     rotationPanel.add(rotRightButton);
167 
168     constraints.gridy = 6;
169     rotationPanel = new JPanel();
170     configPanel.add(rotationPanel, constraints);
171     rotationPanel.setLayout(new FlowLayout());
172     mirHorButton = new JButton("Mir Hor");
173     mirHorButton.addActionListener(new ActionListener() {
174       public void actionPerformed(ActionEvent e) {
175         rotate(Matrix.MIR_HOR);
176       }
177     });
178     rotationPanel.add(mirHorButton);
179     mirVertButton = new JButton("Mir Vert");
180     mirVertButton.addActionListener(new ActionListener() {
181       public void actionPerformed(ActionEvent e) {
182         rotate(Matrix.MIR_VERT);
183       }
184     });
185     rotationPanel.add(mirVertButton);
186 
187 
188     constraints.gridy = 7;
189     constraints.insets = new Insets(5, 5, 0, 0);
190     configPanel.add(new JLabel("Root Alignment:"), constraints);
191 
192     constraints.gridy = 8;
193     constraints.insets = new Insets(0, 0, 0, 0);
194     rootAlignmentCombo = new JComboBox();
195 
196     rootAlignmentCombo.addItem(AbstractRotatableNodePlacer.RootAlignment.CENTER);
197     rootAlignmentCombo.addItem(AbstractRotatableNodePlacer.RootAlignment.CENTER_OVER_CHILDREN);
198     rootAlignmentCombo.addItem(AbstractRotatableNodePlacer.RootAlignment.MEDIAN);
199     rootAlignmentCombo.addItem(AbstractRotatableNodePlacer.RootAlignment.LEADING);
200     rootAlignmentCombo.addItem(AbstractRotatableNodePlacer.RootAlignment.LEFT);
201     rootAlignmentCombo.addItem(AbstractRotatableNodePlacer.RootAlignment.RIGHT);
202     rootAlignmentCombo.addItem(AbstractRotatableNodePlacer.RootAlignment.TRAILING);
203     rootAlignmentCombo.addItemListener(new ItemListener() {
204       public void itemStateChanged(ItemEvent e) {
205         if (e.getStateChange() == ItemEvent.SELECTED) {
206           changeNodePlacersForSelection();
207         }
208       }
209     });
210     rootAlignmentCombo.setRenderer(new DefaultListCellRenderer() {
211       public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
212                                                     boolean cellHasFocus) {
213         JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
214         RootAlignment rootAlignment = (RootAlignment) value;
215         if (rootAlignment == RootAlignment.CENTER) {
216           label.setText("Center");
217         }
218         if (rootAlignment == RootAlignment.LEADING) {
219           label.setText("Leading");
220         }
221         if (rootAlignment == RootAlignment.LEFT) {
222           label.setText("Left");
223         }
224         if (rootAlignment == RootAlignment.RIGHT) {
225           label.setText("Right");
226         }
227         if (rootAlignment == RootAlignment.TRAILING) {
228           label.setText("Trailing");
229         }
230         if (rootAlignment == RootAlignment.MEDIAN) {
231           label.setText("Median");
232         }
233         if (rootAlignment == RootAlignment.CENTER_OVER_CHILDREN) {
234           label.setText("Center over children");
235         }
236         return label;
237       }
238     });
239     configPanel.add(rootAlignmentCombo, constraints);
240 
241     JPanel left = new JPanel(new BorderLayout());
242     left.add(configPanel, BorderLayout.NORTH);
243 
244     JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, view);
245     sp.setOneTouchExpandable(true);
246     sp.setContinuousLayout(false);
247     contentPane.add(sp, BorderLayout.CENTER);
248     createSampleGraph(graph);
249 
250     // provide meta information for grid placer
251     graph.addDataProvider(GridNodePlacer.GRID_DPKEY, new DataProviderAdapter() {
252       public int getInt( final Object dataHolder ) {
253         final int idx = indexOf((Node) dataHolder);
254         if (idx < 0) {
255           return 0;
256         } else {
257           return idx / 5;
258         }
259       }
260 
261       private int indexOf( final Node child ) {
262         int i = 0;
263         final Node parent = child.firstInEdge().source();
264         for (NodeCursor nc = parent.successors(); nc.ok(); nc.next()) {
265           if (nc.node() == child) {
266             return i;
267           }
268           ++i;
269         }
270         return -1;
271       }
272     });
273 
274     setEnabled(false);
275 
276     createSampleGraph(view.getGraph2D());
277     calcLayout();
278 
279     // provide meta information for left right placer. Placed after the calcLayout method call to achieve a nice pre-layout of the left right placer.
280     graph.addDataProvider(LeftRightPlacer.LEFT_RIGHT_DPKEY, new LeftRightPlacer.LeftRightDataProvider(nodePlacerMap));
281   }
282 
283   /**
284    * Enables or disables all panel items.
285    * @param enabled whether or not all items in the panel should be enabled.
286    */
287   private void setEnabled(boolean enabled) {
288     rootAlignmentCombo.setEnabled(enabled);
289     rotLeftButton.setEnabled(enabled);
290     rotRightButton.setEnabled(enabled);
291     mirHorButton.setEnabled(enabled);
292     mirVertButton.setEnabled(enabled);
293     nodePlacerCombo.setEnabled(enabled);
294   }
295 
296   /**
297    * Rotates all selected nodes by the given rotation matrix.
298    * @param rotation the matrix to rotate the nodes.
299    */
300   private void rotate(Matrix rotation) {
301     for (NodeCursor nodeCursor = view.getGraph2D().selectedNodes(); nodeCursor.ok(); nodeCursor.next()) {
302       Node node = nodeCursor.node();
303       AbstractRotatableNodePlacer oldPlacer = (AbstractRotatableNodePlacer) nodePlacerMap.get(node);
304       Matrix matrix = oldPlacer == null ? rotation.multiply(Matrix.DEFAULT) : rotation.multiply(oldPlacer.getModificationMatrix());
305 
306       AbstractRotatableNodePlacer placer = createPlacerFromComboBox(matrix);
307       nodePlacerMap.set(node, placer);
308     }
309     calcLayout();
310   }
311 
312   /**
313    * Mutex lock simulation to prevent settings from being read while they are updated.
314    */
315   private boolean blockLayout;
316 
317   /**
318    * Reads the settings of the first selected node and updates the values in the panel accordingly.
319    */
320   private void readComboValues() {
321     blockLayout = true;
322 
323     setEnabled(true);
324 
325     NodeCursor nodeCursor = view.getGraph2D().selectedNodes();
326     if (!nodeCursor.ok()) {
327       setEnabled(false);
328     } else {
329       Node node = nodeCursor.node();
330 
331       AbstractRotatableNodePlacer nodePlacer = (AbstractRotatableNodePlacer) nodePlacerMap.get(node);
332       if (nodePlacer == null) {
333         return;
334       }
335 
336       if (nodePlacer instanceof SimpleNodePlacer) {
337         rootAlignmentCombo.setEnabled(true);
338         rootAlignmentCombo.setSelectedItem(((SimpleNodePlacer) nodePlacer).getRootAlignment());
339         nodePlacerCombo.setSelectedIndex(0);
340       } else if (nodePlacer instanceof DoubleLinePlacer) {
341         rootAlignmentCombo.setEnabled(true);
342         rootAlignmentCombo.setSelectedItem(((DoubleLinePlacer) nodePlacer).getRootAlignment());
343         nodePlacerCombo.setSelectedIndex(1);
344       } else if (nodePlacer instanceof BusPlacer){
345         rootAlignmentCombo.setEnabled(false);
346         nodePlacerCombo.setSelectedIndex(2);
347       } else if (nodePlacer instanceof LeftRightPlacer){
348         rootAlignmentCombo.setEnabled(false);
349         nodePlacerCombo.setSelectedIndex(3);
350       }else if (nodePlacer instanceof GridNodePlacer){
351         rootAlignmentCombo.setEnabled(true);
352         rootAlignmentCombo.setSelectedItem(((GridNodePlacer) nodePlacer).getRootAlignment());
353         nodePlacerCombo.setSelectedIndex(4);
354       }
355     }
356 
357     blockLayout = false;
358   }
359 
360   /**
361    * Applies all settings from the panel to the selected nodes und updates the layout.
362    */
363   private void changeNodePlacersForSelection() {
364     if (blockLayout) {
365       return;
366     }
367 
368     for (NodeCursor nodeCursor = view.getGraph2D().selectedNodes(); nodeCursor.ok(); nodeCursor.next()) {
369       Node node = nodeCursor.node();
370       AbstractRotatableNodePlacer oldPlacer = (AbstractRotatableNodePlacer) nodePlacerMap.get(node);
371       Matrix matrix = oldPlacer != null ? oldPlacer.getModificationMatrix() : AbstractRotatableNodePlacer.Matrix.DEFAULT;
372 
373       AbstractRotatableNodePlacer placer = createPlacerFromComboBox(matrix);
374       nodePlacerMap.set(node, placer);
375     }
376     calcLayout();
377   }
378 
379   /**
380    * Creates an {@link AbstractRotatableNodePlacer} according to the selections in the panel with the given matrix.
381    * If the created placer doesn't allow custom root alignment, the rootAlignmentCombo is disabled (and enabled otherwise).
382    * @param modificationMatrix the modification matrix for the placer.
383    * @return an {@link AbstractRotatableNodePlacer} according to the selections in the panel with the given matrix.
384    */
385   private AbstractRotatableNodePlacer createPlacerFromComboBox(Matrix modificationMatrix ) {
386     RootAlignment rootAlignment = (RootAlignment) rootAlignmentCombo.getSelectedItem();
387     AbstractRotatableNodePlacer placer = null;
388     int selection = nodePlacerCombo.getSelectedIndex();
389     switch (selection){
390       case 0:
391         placer =new SimpleNodePlacer(modificationMatrix);
392         ((SimpleNodePlacer) placer).setRootAlignment(rootAlignment);
393         rootAlignmentCombo.setEnabled(true);
394         break;
395       case 1:
396         placer = new DoubleLinePlacer(modificationMatrix);
397         ((DoubleLinePlacer) placer).setRootAlignment(rootAlignment);
398         rootAlignmentCombo.setEnabled(true);
399         break;
400       case 2:
401         placer = new BusPlacer(modificationMatrix);
402         rootAlignmentCombo.setEnabled(false);
403         break;
404       case 3:
405         placer = new LeftRightPlacer(modificationMatrix);
406         rootAlignmentCombo.setEnabled(false);
407         break;
408       case 4:
409         placer = new GridNodePlacer(modificationMatrix, rootAlignment);
410         rootAlignmentCombo.setEnabled(true);
411         break;
412     }
413     return placer;
414   }
415 
416   /**
417    * For this demo, deletion of nodes is disabled.
418    * @return false
419    */
420   protected boolean isDeletionEnabled() {
421     return false;
422   }
423 
424   /**
425    * For this demo, clipboard is disabled.
426    * @return false
427    */
428   protected boolean isClipboardEnabled() {
429     return false;
430   }
431 
432   /**
433    * Creates the initial graph. The graph consists of a root and 5 children with a SimpleNodePlacer. Each child of the root has another
434    * branch, one for each node placer. Some nodes are rotated and/or mirrored for demonstration.
435    */
436   private void createSampleGraph(Graph2D graph) {
437     graph.clear();
438     Node root = graph.createNode();
439     graph.getRealizer(root).setFillColor(layerColors[0]);
440     nodePlacerMap.set(root, new SimpleNodePlacer());
441     createChildren(graph, root);
442     calcLayout();
443   }
444 
445   private void createChildren(Graph2D graph, Node root) {
446     for (int i = 0; i < 5; i++) {
447       Node child = graph.createNode();
448       graph.createEdge(root, child);
449       graph.getRealizer(child).setFillColor(layerColors[1]);
450       SimpleNodePlacer nodePlacer = new SimpleNodePlacer(Matrix.MIR_VERT_ROT90);
451       nodePlacer.setRootAlignment(RootAlignment.LEADING);
452       nodePlacerMap.set(child, nodePlacer);
453       switch (i){
454         case 0: createSimpleNodePlacerBranch(graph, child); break;
455         case 1: createDoubleLinePlacerBranch(graph, child); break;
456         case 2: createBusPlacerBranch(graph, child); break;
457         case 3: createLeftRightPlacerBranch(graph, child); break;
458         case 4: createGridNodePlacerBranch(graph, child); break;
459       }
460     }
461   }
462 
463   private void createLeafs(Graph2D graph, Node root, int number){
464     for (int i = 0; i < number; i++) {
465       Node child = graph.createNode();
466       graph.createEdge(root, child);
467       graph.getRealizer(child).setFillColor(layerColors[3]);
468       SimpleNodePlacer nodePlacer = new SimpleNodePlacer( Matrix.DEFAULT);
469       nodePlacerMap.set(child, nodePlacer);
470     }
471   }
472 
473   private void createSimpleNodePlacerBranch(Graph2D graph, Node root){
474     for (int i = 0; i < 4; i++) {
475       Node child = graph.createNode();
476       graph.createEdge(root, child);
477       graph.getRealizer(child).setFillColor(layerColors[2]);
478       Matrix rotation = Matrix.DEFAULT;
479       switch (i){
480         case 1:
481           rotation = Matrix.MIR_HOR; break;
482         case 2:
483           rotation = Matrix.ROT180; break;
484         case 3:
485           rotation = Matrix.MIR_HOR; break;
486       }
487       SimpleNodePlacer nodePlacer = new SimpleNodePlacer(rotation);
488       nodePlacer.setRootAlignment(RootAlignment.LEADING);
489       nodePlacerMap.set(child, nodePlacer);
490       createLeafs(graph, child, 3);
491     }
492   }
493 
494   private void createDoubleLinePlacerBranch(Graph2D graph, Node root){
495     for (int i = 0; i < 3; i++) {
496       Node child = graph.createNode();
497       graph.createEdge(root, child);
498       graph.getRealizer(child).setFillColor(layerColors[2]);
499       Matrix rotation = Matrix.DEFAULT;
500       switch (i){
501         case 1:
502           rotation = Matrix.ROT90; break;
503         case 2:
504           rotation = Matrix.MIR_VERT_ROT90; break;
505       }
506       DoubleLinePlacer nodePlacer = new DoubleLinePlacer(rotation);
507       nodePlacer.setRootAlignment(RootAlignment.LEADING);
508       nodePlacerMap.set(child, nodePlacer);
509       createLeafs(graph, child, 5);
510     }
511   }
512 
513   private void createBusPlacerBranch(Graph2D graph, Node root){
514     for (int i = 0; i < 3; i++) {
515       Node child = graph.createNode();
516       graph.createEdge(root, child);
517       graph.getRealizer(child).setFillColor(layerColors[2]);
518       Matrix rotation = Matrix.DEFAULT;
519       switch (i){
520         case 1:
521           rotation = Matrix.MIR_HOR; break;
522         case 2:
523           rotation = Matrix.ROT180; break;
524       }
525       BusPlacer nodePlacer = new BusPlacer(rotation);
526       nodePlacerMap.set(child, nodePlacer);
527       createLeafs(graph, child, 5);
528     }
529   }
530 
531   private void createLeftRightPlacerBranch(Graph2D graph, Node root){
532     for (int i = 0; i < 2; i++) {
533       Node child = graph.createNode();
534       graph.createEdge(root, child);
535       graph.getRealizer(child).setFillColor(layerColors[2]);
536       Matrix rotation = Matrix.DEFAULT;
537       switch (i){
538         case 1:
539           rotation = Matrix.ROT90; break;
540       }
541       LeftRightPlacer nodePlacer = new LeftRightPlacer(rotation);
542       nodePlacerMap.set(child, nodePlacer);
543       createLeafs(graph, child, 5);
544     }
545   }
546 
547   private void createGridNodePlacerBranch(Graph2D graph, Node root){
548     Node child = graph.createNode();
549     graph.createEdge(root, child);
550     graph.getRealizer(child).setFillColor(layerColors[2]);
551     GridNodePlacer nodePlacer = new GridNodePlacer();
552     nodePlacer.setRootAlignment(RootAlignment.LEADING);
553     nodePlacerMap.set(child, nodePlacer);
554     createLeafs(graph, child, 20);
555   }
556 }
557