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.hierarchic;
29  
30  import demo.view.DemoBase;
31  import org.w3c.dom.Element;
32  import y.base.DataProvider;
33  import y.base.Edge;
34  import y.base.EdgeCursor;
35  import y.base.EdgeMap;
36  import y.base.Graph;
37  import y.base.Node;
38  import y.base.NodeCursor;
39  import y.base.NodeMap;
40  import y.io.GraphMLIOHandler;
41  import y.io.graphml.KeyScope;
42  import y.io.graphml.graph2d.PortConstraintInputHandler;
43  import y.io.graphml.graph2d.PortConstraintOutputHandler;
44  import y.io.graphml.input.GraphMLParseException;
45  import y.io.graphml.input.InputHandlerProvider;
46  import y.io.graphml.input.QueryInputHandlersEvent;
47  import y.io.graphml.output.GraphMLWriteException;
48  import y.io.graphml.output.OutputHandlerProvider;
49  import y.io.graphml.output.QueryOutputHandlersEvent;
50  import y.layout.LabelLayoutConstants;
51  import y.layout.PortConstraint;
52  import y.layout.PortConstraintConfigurator;
53  import y.layout.PortConstraintKeys;
54  import y.layout.PreferredPlacementDescriptor;
55  import y.layout.hierarchic.ClassicLayerSequencer;
56  import demo.layout.module.HierarchicLayoutModule;
57  import y.util.GraphCopier;
58  import y.util.Maps;
59  import y.view.Arrow;
60  import y.view.EdgeLabel;
61  import y.view.EdgeRealizer;
62  import y.view.EditMode;
63  import y.view.Graph2D;
64  import y.view.Graph2DClipboard;
65  import y.view.Graph2DSelectionEvent;
66  import y.view.Graph2DSelectionListener;
67  import y.view.PortAssignmentMoveSelectionMode;
68  import y.view.SmartEdgeLabelModel;
69  
70  import javax.swing.AbstractAction;
71  import javax.swing.Action;
72  import javax.swing.BorderFactory;
73  import javax.swing.ButtonGroup;
74  import javax.swing.ButtonModel;
75  import javax.swing.JButton;
76  import javax.swing.JCheckBox;
77  import javax.swing.JComboBox;
78  import javax.swing.JPanel;
79  import javax.swing.JRadioButton;
80  import javax.swing.JScrollPane;
81  import javax.swing.JToolBar;
82  import java.awt.BorderLayout;
83  import java.awt.Color;
84  import java.awt.EventQueue;
85  import java.awt.GridBagConstraints;
86  import java.awt.GridBagLayout;
87  import java.awt.event.ActionEvent;
88  import java.awt.event.ActionListener;
89  import java.util.HashMap;
90  import java.util.Locale;
91  import java.util.Map;
92  import java.util.WeakHashMap;
93  
94  /**
95   * This Demo shows how HierarchicLayouter can handle port constraints,
96   * how it can take edge labels into consideration when laying out
97   * a graph and how to specify groups of nodes, which will be placed next to
98   * each other on each layer.
99   * <br>
100  * <br>
101  * <b>Usage:</b>
102  * <br>
103  * After starting the demo create a graph manually in the graph editor
104  * pane. Now click on the "Layout" Button in the toolbar to 
105  * start the hierarchic layouter. 
106  * <br>
107  * Additional port constraints can be specified for HierarchicLayouter.
108  * A port constraint expresses on what side of a node the source and/or 
109  * target point of an edge should connect. To add a port constraint to an edge 
110  * first select the edge by clicking on it. Then choose the desired source and target 
111  * port constraint for the selected edge with the radio button panel on the left of
112  * the graph pane. Selecting "South" from the "Source Port" choice for example 
113  * means the edge selected edge should connect to the bottom side of the source node.
114  * Alternatively once can simply move or create the first or last bend of an edge.
115  * A visual clue will appear that determines the PortConstraint.
116  * <br>
117  * A port constraint that is marked as "strong", means that the associated 
118  * port coordinate given to the layouter will not be modified by the layouter.  
119  * <br>
120  * After the port constraints have been set up it is time to press the 
121  * "layout" button again. Now the resulting layout is a hierarchic layout
122  * that obeys the additional port constraints.
123  * <br>
124  * To activate the edge labeling feature check the box named "Label Edges" just below
125  * the port constraint selector pane.
126  * If this feature is turned on then on there will be edge labels visible that
127  * display the type of port constraint activated for the source and target port
128  * of each edge. Now by pressing the "Layout" button again the resulting layout 
129  * will consider these edge labels as well. One can see that none of the labels 
130  * overlap and that the size of the layout has increased.
131  * <br>
132  * The toolbar button "Option..." allows to specify diverse layout parameters
133  * for the layouter. Not all of these options are important to this demo.
134  * Noteworthy options for this demo are "Edge Routing: Orthogonal" and in tab 
135  * "Node Rank" the ranking policy "From Sketch".
136  * <br>
137  * "Edge Routing: Orthogonal" has the effect of routing all edges orthogonally, 
138  * i.e. by using only horizontal and vertical line segments.
139  * <br>
140  * "Ranking Policy: From Sketch" has the effect that the layer partitions will be
141  * established by the given drawing heuristically (looking at the y-coordinates 
142  * of the nodes). By this option it is possible to put nodes in the same layer that
143  * are connected by an edge.
144  * <br>
145  * <br>
146  * Node Groups:
147  * By clicking on one of the colored buttons in the left bar, the currently selected
148  * nodes will be assigned to the corresponding group. This grouping will be
149  * indicated by the node color. When being laid out, nodes on the same layer
150  * having the same group (color) will be placed next to each other as a compound
151  * group.
152  * <br>
153  * <br>
154  * API usage:
155  * <br>
156  * Each port constraint is expressed
157  * by an object of type PortConstraint. The side of the port can be specified by
158  * one of the side specifiers NORTH, SOUTH, EAST, WEST of class PortConstraint.
159  * PortConstraint data gets passed to HierarchicLayouter by binding named DataProviders
160  * to the input graph. Use PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY to register
161  * a data provider that returns a PortConstraint object for each source port of an edge
162  * and use PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY to register
163  * a data provider that returns a PortConstraint object for each target port of an edge.
164  * <br>
165  * The internal hierarchic labeling gets activated by associating an array of 
166  * type LabelLayoutData to each edge of the graph. This is done by registering
167  * a DataProvider with the key LabelLayoutData.EDGE_LABEL_LAYOUT_KEY to the
168  * input graph. Alternatively one can use the layout stage
169  * y.layout.LabelLayoutTranslator as the LabelLayouter of HierarchicLayouter.
170  * In the latter case the initial label information will be automatically
171  * read from the EdgeLabelLayout information of the input graph 
172  * ( getEdgeLabelLayout(Edge e) ). It will also be written back to the 
173  * EdgeLabelLayout of the graph.  
174  * <br>
175  * Groupings of nodes are expressed through the means of integer values.
176  * Each node *can* be assigned an integer value, which determines the group
177  * to which it belongs. The layouter will query the ClassicalLayerSequencer.GROUP_KEY
178  * NodeDataProvider from the graph and will layout the nodes 
179  */
180 
181 public class HierarchicLayouterDemo extends DemoBase {
182   private static final String EDGE_LABELING = "EDGE_LABELING";
183   private static final String HIERARCHIC = "HIERARCHIC";
184   private static final String NONE = "NONE";
185 
186   private PortSpec sourceSpec, targetSpec;
187   private EdgeMap sourceGroupIdMap, targetGroupIdMap;
188   private JCheckBox labelBox;
189   private EdgeMap sourcePortMap;
190   private EdgeMap targetPortMap;
191 
192   private HierarchicLayoutModule layoutModule;
193 
194   private NodeMap groupMap;
195   private PortAssignmentMoveSelectionMode paMode;
196   
197   public HierarchicLayouterDemo() {
198     this (null);
199   }
200 
201   public HierarchicLayouterDemo(String helpFilePath) {
202     final Graph2D graph = view.getGraph2D();
203     groupMap = graph.createNodeMap();
204     graph.addDataProvider(ClassicLayerSequencer.GROUP_KEY, groupMap);
205     EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
206     defaultER.setArrow(Arrow.STANDARD);
207 
208     sourcePortMap = graph.createEdgeMap();
209     targetPortMap = graph.createEdgeMap();
210     sourceGroupIdMap = graph.createEdgeMap();
211     targetGroupIdMap = graph.createEdgeMap();
212     graph.addDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, sourceGroupIdMap);
213     graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, targetGroupIdMap);
214     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, sourcePortMap);
215     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, targetPortMap);
216 
217     paMode.setSpc(sourcePortMap);
218     paMode.setTpc(targetPortMap);
219 
220 
221     JPanel left = new JPanel(new GridBagLayout());
222     GridBagConstraints gbc = new GridBagConstraints();
223 
224     JPanel edgeSpec = new JPanel(new GridBagLayout());
225     edgeSpec.setBorder(BorderFactory.createTitledBorder("Edge Settings"));
226     gbc.fill = GridBagConstraints.BOTH;
227     gbc.anchor = GridBagConstraints.NORTHWEST;
228     gbc.gridx = 0;
229 
230     edgeSpec.add(new JButton(new AbstractAction("Parse Ports") {
231       public void actionPerformed(ActionEvent ae) {
232         new PortConstraintConfigurator()
233             .createPortConstraintsFromSketch(graph, sourcePortMap, targetPortMap);
234       }
235     }), gbc);
236 
237     gbc.gridx = 1;
238 
239     labelBox = new JCheckBox("Label Edges");
240     labelBox.addActionListener(new ActionListener() {
241       public void actionPerformed(ActionEvent ev) {
242         setLabelEdges(labelBox.isSelected());
243       }
244     });
245 
246     edgeSpec.add(labelBox);
247 
248     sourceSpec = new PortSpec("Source Port", sourcePortMap, sourceGroupIdMap);
249     targetSpec = new PortSpec("Target Port", targetPortMap, targetGroupIdMap);
250     gbc.gridx = 0;
251     gbc.gridy = 1;
252     edgeSpec.add(sourceSpec, gbc);
253     gbc.gridx = 1;
254     edgeSpec.add(targetSpec, gbc);
255     gbc.gridx = 0;
256     gbc.gridy = 0;
257     left.add(edgeSpec, gbc);
258     gbc.gridy = GridBagConstraints.RELATIVE;
259 
260     JPanel groupSpec = new JPanel(new GridBagLayout());
261     groupSpec.setBorder(BorderFactory.createTitledBorder("Node Groups"));
262 
263     // build the grouping mechanism
264     Color[] groupColors = new Color[]{null, Color.blue, Color.yellow, Color.red};
265 
266     for (int i = 0; i < groupColors.length; i++) {
267       JButton groupButton = new GroupButton(groupColors[i], i);
268       groupSpec.add(groupButton, gbc);
269     }
270     gbc.weightx = gbc.weighty = 1;
271     groupSpec.add(new JPanel(), gbc);
272     gbc.weightx = gbc.weighty = 0;
273     left.add(groupSpec, gbc);
274 
275     gbc.weighty = 1.0d;
276     left.add(new JPanel(), gbc);
277 
278     contentPane.add(new JScrollPane(left), BorderLayout.WEST);
279 
280     graph.addGraph2DSelectionListener(new Graph2DSelectionListener() {
281       public void onGraph2DSelectionEvent(Graph2DSelectionEvent ev) {
282         if (ev.getSubject() instanceof Edge) {
283           Edge e = (Edge) ev.getSubject();
284           if (ev.getGraph2D().isSelected(e)) {
285             PortConstraint pc = getSPC(e);
286             sourceSpec.setSide(pc.getSide());
287             sourceSpec.setStrong(pc.isStrong());
288             sourceSpec.setGroupId(sourceGroupIdMap.get(e));
289             pc = getTPC(e);
290             targetSpec.setSide(pc.getSide());
291             targetSpec.setStrong(pc.isStrong());
292             targetSpec.setGroupId(targetGroupIdMap.get(e));
293           }
294         }
295       }
296     });
297     
298     addHelpPane(helpFilePath);
299 
300     layoutModule = new HierarchicLayoutModule();
301     loadGraph("resource/portConstraints.graphml");
302   }
303 
304   protected JToolBar createToolBar() {
305     final Action layoutAction = new AbstractAction(
306             "Layout", SHARED_LAYOUT_ICON) {
307       public void actionPerformed(ActionEvent e) {
308         layoutModule.start(view.getGraph2D());
309       }
310     };
311     final Action preferencesAction = new AbstractAction(
312             "Settings...", getIconResource("resource/properties.png")) {
313       public void actionPerformed(ActionEvent e) {
314         OptionSupport.showDialog(layoutModule, view.getGraph2D(), false, view.getFrame());
315       }
316     };
317 
318     JToolBar toolBar = super.createToolBar();
319     toolBar.addSeparator();
320     toolBar.add(createActionControl(layoutAction));
321     toolBar.add(createActionControl(preferencesAction));
322     return toolBar;
323   }
324 
325   /**
326    * Overwritten to decorate the clipboard's copy factory with an {@link NodeGroupGraphCopyFactory} that also
327    * handles copying the group ids.
328    */
329   protected Graph2DClipboard getClipboard() {
330     final Graph2DClipboard clipboard = super.getClipboard();
331     clipboard.setCopyFactory(new NodeGroupGraphCopyFactory(clipboard.getCopyFactory()));
332     return clipboard;
333   }
334 
335   /**
336    * this method assigns the group id and the corresponding color hint
337    * to the currently selected nodes
338    */
339   protected void assignGroup(Color color, int index) {
340     Graph2D graph = view.getGraph2D();
341     graph.firePreEvent();
342     graph.backupRealizers(graph.selectedNodes());
343     try {
344       for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
345         Node n = nc.node();
346         if (color == null) {
347           color = graph.getDefaultNodeRealizer().getFillColor();
348           // unset the actual group index
349           groupMap.set(n, null);
350         } else {
351           // set the actual group index
352           groupMap.setInt(n, index);
353         }
354         // set the color hint
355         graph.getRealizer(n).setFillColor(color);
356 
357       }
358       graph.updateViews();
359     } finally {
360       graph.firePostEvent();
361     }
362   }
363 
364   // helper class
365   class GroupButton extends JButton implements ActionListener {
366     Color color;
367     int index;
368 
369     GroupButton(Color color, int index) {
370       super("");
371       setText(index > 0 ? "Group " + index : "No Group");
372       this.color = color;
373       this.index = index;
374       setBackground(color);
375       this.addActionListener(this);
376     }
377 
378     public void actionPerformed(ActionEvent e) {
379       HierarchicLayouterDemo.this.assignGroup(color, index);
380     }
381   }
382 
383 
384   PortConstraint getSPC(Edge e) {
385     PortConstraint pc = (e != null) ? (PortConstraint) sourcePortMap.get(e) : null;
386 
387     if (pc == null) {
388       pc = PortConstraint.create(PortConstraint.ANY_SIDE);
389     }
390 
391     return pc;
392   }
393 
394   PortConstraint getTPC(Edge e) {
395     PortConstraint pc = (e != null) ? (PortConstraint) targetPortMap.get(e) : null;
396 
397     if (pc == null) {
398       pc = PortConstraint.create(PortConstraint.ANY_SIDE);
399     }
400 
401     return pc;
402   }
403 
404   void setPC(EdgeMap portMap, EdgeMap groupMap, byte side, boolean strong, Object groupId) {
405     Graph2D graph = view.getGraph2D();
406     PortConstraint pc = PortConstraint.create(side, strong);
407     String preText = pc.toString();
408     if (groupId != null) {
409       preText = preText + '[' + groupId + ']';
410     }
411     for (EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next()) {
412       Edge e = ec.edge();
413       portMap.set(e, pc);
414       getSPC(e);
415       getTPC(e);
416       groupMap.set(e, groupId);
417       if (graph.getRealizer(e).labelCount() >= 2) {
418         String text = portMap == sourcePortMap ? "source " + preText : "target" + preText;
419         graph.getRealizer(e).getLabel(portMap == sourcePortMap ? 0 : 1).setText(text);
420       }
421     }
422     graph.updateViews();
423   }
424 
425   /**
426    * Launches this demo.
427    */
428   public static void main(String[] args) {
429     EventQueue.invokeLater(new Runnable() {
430       public void run() {
431         Locale.setDefault(Locale.ENGLISH);
432         initLnF();
433         (new HierarchicLayouterDemo("resource/hierarchiclayouterdemohelp.html")).start("Hierarchic Layouter Demo");
434       }
435     });
436   }
437 
438 
439   protected GraphMLIOHandler createGraphMLIOHandler() {
440     GraphMLIOHandler ioh = super.createGraphMLIOHandler();
441     ioh.getGraphMLHandler().addOutputHandlerProvider(new OutputHandlerProvider() {
442       public void onQueryOutputHandler(QueryOutputHandlersEvent event) throws GraphMLWriteException {
443         boolean isRegistered = false;
444         Graph g = event.getContext().getGraph();
445         Object[] keys = g.getDataProviderKeys();
446         for (int i = 0; i < keys.length; i++) {
447           Object key = keys[i];
448           if(PortConstraintKeys.SOURCE_GROUPID_KEY.equals(key)  ||
449               PortConstraintKeys.TARGET_GROUPID_KEY.equals(key) ||
450               PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY.equals(key) ||
451               PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY.equals(key)) {
452             isRegistered = true;
453             break;
454           }
455         }
456         if (isRegistered) {
457           event.addOutputHandler(new PortConstraintOutputHandler(), KeyScope.EDGE);
458         }
459       }
460     });
461 
462     ioh.getGraphMLHandler().addInputHandlerProvider(new InputHandlerProvider() {
463       public void onQueryInputHandler(QueryInputHandlersEvent event) throws GraphMLParseException {
464         Element keyDefinition = event.getKeyDefinition();
465         PortConstraintInputHandler handler = new PortConstraintInputHandler();
466 
467         if (handler.acceptKey(keyDefinition)) {
468           initDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, event.getContext().getGraph());
469           initDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, event.getContext().getGraph());
470           initDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, event.getContext().getGraph());
471           initDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, event.getContext().getGraph());
472           event.addInputHandler(handler);
473         }
474       }
475 
476       private void initDataProvider(Object key, Graph graph) {
477         DataProvider dp = graph.getDataProvider(key);
478         if (dp == null || !(dp instanceof EdgeMap)) {
479           dp = Maps.createEdgeMap(new WeakHashMap());
480           graph.addDataProvider(key, dp);
481         }
482       }
483     });
484     return ioh;
485   }
486 
487   class PortSpec extends JPanel {
488     JRadioButton anySideB, northB, southB, eastB, westB;
489     JCheckBox cb;
490     JComboBox box;
491     EdgeMap portMap;
492     EdgeMap groupMap;
493 
494     Object[] items = new Object[]{"no Group", new Integer(1), new Integer(2), new Integer(3), new Integer(4),
495         new Integer(5), new Integer(6), new Integer(7)};
496 
497     PortSpec(String title, EdgeMap portMap, EdgeMap groupMap) {
498       super(new GridBagLayout());
499       GridBagConstraints gbc = new GridBagConstraints();
500       gbc.fill = GridBagConstraints.BOTH;
501       gbc.gridx = 0;
502       gbc.anchor = GridBagConstraints.NORTHWEST;
503       this.setBorder(BorderFactory.createTitledBorder(title));
504       this.portMap = portMap;
505       this.groupMap = groupMap;
506       final ButtonGroup bg = new ButtonGroup();
507 
508 
509       anySideB = new JRadioButton("ANY_SIDE");
510       northB = new JRadioButton("NORTH");
511       southB = new JRadioButton("SOUTH");
512       eastB = new JRadioButton("EAST");
513       westB = new JRadioButton("WEST");
514       anySideB.setSelected(true);
515 
516       cb = new JCheckBox("Strong");
517 
518 
519       box = new JComboBox(items);
520 
521       ActionListener rl = new ActionListener() {
522         public void actionPerformed(ActionEvent ev) {
523           ButtonModel bm = bg.getSelection();
524           byte side = PortConstraint.ANY_SIDE;
525           if (bm == southB.getModel()) {
526             side = PortConstraint.SOUTH;
527           } else if (bm == northB.getModel()) {
528             side = PortConstraint.NORTH;
529           } else if (bm == eastB.getModel()) {
530             side = PortConstraint.EAST;
531           } else if (bm == westB.getModel()) {
532             side = PortConstraint.WEST;
533           }
534           boolean strong = cb.isSelected();
535           Object groupId = box.getSelectedItem();
536           if (groupId instanceof String) {
537             groupId = null;
538           }
539           setPC(PortSpec.this.portMap, PortSpec.this.groupMap, side, strong, groupId);
540         }
541       };
542 
543       addButton(anySideB, bg, rl, gbc);
544       addButton(northB, bg, rl, gbc);
545       addButton(southB, bg, rl, gbc);
546       addButton(eastB, bg, rl, gbc);
547       addButton(westB, bg, rl, gbc);
548       this.add(cb, gbc);
549       this.add(box, gbc);
550       box.addActionListener(rl);
551       cb.addActionListener(rl);
552     }
553 
554     void addButton(JRadioButton b, ButtonGroup bg, ActionListener rl, GridBagConstraints gbc) {
555       bg.add(b);
556       this.add(b, gbc);
557       b.addActionListener(rl);
558     }
559 
560     void setSide(byte side) {
561       switch (side) {
562         case PortConstraint.ANY_SIDE:
563           anySideB.setSelected(true);
564           break;
565         case PortConstraint.NORTH:
566           northB.setSelected(true);
567           break;
568         case PortConstraint.SOUTH:
569           southB.setSelected(true);
570           break;
571         case PortConstraint.WEST:
572           westB.setSelected(true);
573           break;
574         case PortConstraint.EAST:
575           eastB.setSelected(true);
576           break;
577       }
578     }
579 
580     void setStrong(boolean strong) {
581       cb.setSelected(strong);
582     }
583 
584     void setGroupId(Object id) {
585       if (id == null) {
586         box.setSelectedIndex(0);
587       } else {
588         box.setSelectedItem(id);
589       }
590     }
591   }
592 
593   void setLabelEdges(boolean labelEdges) {
594     Graph2D graph = view.getGraph2D();
595 
596     if (labelEdges) {
597       addLabels(graph.getDefaultEdgeRealizer());
598       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
599         addLabels(graph.getRealizer(ec.edge()));
600       }
601       layoutModule.getOptionHandler().set(EDGE_LABELING, HIERARCHIC);
602     } else {
603       removeLabels(graph.getDefaultEdgeRealizer());
604       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
605         removeLabels(graph.getRealizer(ec.edge()));
606       }
607       layoutModule.getOptionHandler().set(EDGE_LABELING, NONE);
608     }
609 
610     view.updateView();
611 
612   }
613 
614 
615   void addLabels(EdgeRealizer er) {
616     removeLabels(er);
617 
618     addLabel(
619       er, getSPC(er.getEdge()).toString(),
620       SmartEdgeLabelModel.POSITION_SOURCE_CENTER,
621       LabelLayoutConstants.PLACE_AT_SOURCE);
622     addLabel(
623       er, getTPC(er.getEdge()).toString(),
624       SmartEdgeLabelModel.POSITION_TARGET_CENTER,
625       LabelLayoutConstants.PLACE_AT_TARGET);
626     addLabel(
627       er, "Center",
628       SmartEdgeLabelModel.POSITION_CENTER,
629       LabelLayoutConstants.PLACE_AT_CENTER);
630   }
631 
632   static void addLabel(
633           final EdgeRealizer er,
634           final String text,
635           final int position,
636           final byte placement
637   ) {
638     final EdgeLabel el = new EdgeLabel(text);
639     final SmartEdgeLabelModel model = new SmartEdgeLabelModel();
640     el.setLabelModel(model, model.createDiscreteModelParameter(position));
641     el.setPreferredPlacementDescriptor(
642             PreferredPlacementDescriptor.newSharedInstance(placement));
643     er.addLabel(el);
644   }
645 
646   static void removeLabels(EdgeRealizer er) {
647     for (int i = er.labelCount(); i --> 0;) {
648       er.removeLabel(i);
649     }
650   }
651 
652   protected void registerViewModes() {
653     EditMode mode = new EditMode();
654     mode.setMoveSelectionMode(paMode = new PortAssignmentMoveSelectionMode(null, null));
655     view.addViewMode(mode);
656   }
657 
658   /**
659    * This {@link GraphCopier.CopyFactory} handles group nodes for cut/copy/paste.
660    */
661   private class NodeGroupGraphCopyFactory implements GraphCopier.CopyFactory {
662     private final GraphCopier.CopyFactory copyFactory;
663     private final HashMap node2group;
664 
665     public NodeGroupGraphCopyFactory(GraphCopier.CopyFactory copyFactory) {
666       this.copyFactory = copyFactory;
667       node2group = new HashMap();
668     }
669 
670     public Node copyNode(Graph targetGraph, Node originalNode) {
671       return copyFactory.copyNode(targetGraph, originalNode);
672     }
673 
674     public Edge copyEdge(Graph targetGraph, Node newSource, Node newTarget, Edge originalEdge) {
675       return copyFactory.copyEdge(targetGraph, newSource, newTarget, originalEdge);
676     }
677 
678     public Graph createGraph() {
679       return copyFactory.createGraph();
680     }
681 
682     public void preCopyGraphData(Graph sourceGraph, Graph targetGraph) {
683       copyFactory.preCopyGraphData(sourceGraph, targetGraph);
684     }
685 
686     /**
687      * After copying the (sub-)graph, also the node group ids need to be stored/updated. That way, copies of nodes that
688      * belong to a certain group will belong to the same group.
689      */
690     public void postCopyGraphData(Graph sourceGraph, Graph targetGraph, Map nodeMap, Map edgeMap) {
691       copyFactory.postCopyGraphData(sourceGraph, targetGraph, nodeMap, edgeMap);
692 
693       // check if the source graph is the graph in the current view to see if it is a cut/copy or paste action
694       if (sourceGraph == view.getGraph2D()) {
695         // cut/copy
696         // store group information from the source nodes for the nodes in the copied subgraph
697         node2group.clear();
698         for (NodeCursor nodeCursor = sourceGraph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
699           final Node sourceNode = nodeCursor.node();
700           final Node targetNode = (Node) nodeMap.get(sourceNode);
701           if (targetNode != null) {
702             node2group.put(targetNode, groupMap.get(sourceNode));
703           }
704         }
705       } else {
706         // paste
707         // store group ids of the source nodes for the nodes in the copied subgraph
708         for (NodeCursor nodeCursor = sourceGraph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
709           final Node sourceNode = nodeCursor.node();
710           final Node targetNode = (Node) nodeMap.get(sourceNode);
711           groupMap.set(targetNode, node2group.get(sourceNode));
712         }
713       }
714     }
715   }
716 }
717