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