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.router;
29  
30  import demo.view.DemoBase;
31  import y.base.Edge;
32  import y.base.EdgeCursor;
33  import y.base.GraphEvent;
34  import y.base.GraphListener;
35  import y.base.Node;
36  import y.base.NodeList;
37  import y.geom.YPoint;
38  import y.util.DataProviderAdapter;
39  import y.util.DataProviders;
40  import y.view.Arrow;
41  import y.view.BridgeCalculator;
42  import y.view.DefaultGraph2DRenderer;
43  import y.view.EdgeRealizer;
44  import y.view.EditMode;
45  import y.view.GenericNodeRealizer;
46  import y.view.Graph2D;
47  import y.view.Graph2DView;
48  import y.view.Graph2DViewActions;
49  import y.view.HotSpotMode;
50  import y.view.MovePortMode;
51  import y.view.MoveSelectionMode;
52  import y.view.NodePort;
53  import y.view.NodeRealizer;
54  import y.view.SelectionBoxMode;
55  import y.view.ShapeNodePainter;
56  import y.view.ViewMode;
57  
58  import javax.swing.AbstractAction;
59  import javax.swing.Action;
60  import javax.swing.ActionMap;
61  import javax.swing.JComponent;
62  import javax.swing.JMenu;
63  import javax.swing.JMenuBar;
64  import javax.swing.JPanel;
65  import javax.swing.JRootPane;
66  import javax.swing.JSplitPane;
67  import javax.swing.JToolBar;
68  import java.awt.BorderLayout;
69  import java.awt.Color;
70  import java.awt.EventQueue;
71  import java.awt.event.ActionEvent;
72  import java.net.URL;
73  import java.util.Locale;
74  import java.util.Map;
75  
76  /**
77   * Shows the capabilities of the yFiles {@link y.layout.router.BusRouter} and demonstrates specific <em>hub</em> nodes
78   * to ease the usage in an interactive environment.
79   * <p/>
80   * Typically, in a bus, every member node is connected to every other member which results in a large number of edges in
81   * the graph. To disburden users from entering all these edges manually, this application introduces a specific type of
82   * nodes, so-called <em>hubs</em>, which act as interchange points of the bus. A bus consists of all its interconnected
83   * hubs, and all edges and regular nodes connected to them. For convenience, all connectors and edges of the same bus
84   * are drawn in a common color.
85   * <p/>
86   * Regular nodes, hubs and edges can be interactively added and deleted, and snap lines are provided to ease the
87   * editing, see the related help page.
88   * <p/>
89   * There are a number of classes related for this demo:
90   * <ul>
91   *   <li>{@link BusDyer} governs the coloring of the buses</li>
92   *   <li>{@link BusRouterDemoModule} is used as a means to configure the router. If grid is enabled, the router
93   *   calculates grid routes and the view highlights the grid points.</li>
94   *   <li>{@link BusRouterDemoTools} governs the left-side option panel</li>
95   *   <li>{@link HubRoutingSupport} extends the BusRouter for graphs in hub representation</li>
96   * </ul>
97   *
98   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/orthogonal_bus_router" target="_blank">Section Orthogonal Bus-style Edge Routing</a> in the yFiles for Java Developer's Guide
99   */
100 public class BusRouterDemo extends DemoBase {
101   static final String HUB_CONFIGURATION = "BusHub";
102   static final Object HUB_MARKER_DPKEY = "demo.layout.router.BusRouterDemo.HUB_MARKER_DPKEY";
103 
104   static final int MODE_ALL = 0;
105   static final int MODE_SELECTED = 1;
106   static final int MODE_PARTIAL = 2;
107 
108   private final BusDyer busDyer;
109   private JComponent glassPane;
110   private NodeRealizer hubRealizer;
111   private HubRoutingSupport hubRoutingSupport;
112 
113   /**
114    * Creates a new instance of this demo.
115    */
116   public BusRouterDemo() {
117     this(null);
118   }
119 
120   /**
121    * Creates a new instance of this demo and adds a help pane for the specified file.
122    */
123   public BusRouterDemo(final String helpFilePath) {
124     // create support for colored buses
125     busDyer = createBusDyer();
126     view.getGraph2D().addGraphListener(busDyer);
127 
128     hubRoutingSupport = createHubRoutingSupport();
129 
130     // add and prepare the tool pane
131     BusRouterDemoTools demoTools = new BusRouterDemoTools();
132     demoTools.setViewAndRouter(view, hubRoutingSupport.getModule());
133     demoTools.updateGrid();
134     demoTools.updateSnapping();
135 
136     JSplitPane mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, demoTools.createOptionComponent(), view);
137     mainSplitPane.setBorder(null);
138     contentPane.add(mainSplitPane, BorderLayout.CENTER);
139 
140     addHelpPane(helpFilePath);
141 
142     EventQueue.invokeLater(new Runnable() {
143       public void run() {
144         loadInitialGraph();
145       }
146     });
147   }
148 
149   protected void loadInitialGraph() {
150     loadGraph("resource/threeBuses.graphml");
151   }
152 
153   protected boolean isClipboardEnabled() {
154     return false;
155   }
156 
157   /**
158    * Does the bus routing by delegating to {@link HubRoutingSupport#doLayout(y.view.Graph2D, int)}.
159    */
160   public void doLayout(final int mode) {
161     final Graph2D graph = view.getGraph2D();
162     final NodeRealizer oldNodeRealizer = graph.getDefaultNodeRealizer();
163 
164     if (glassPane == null) {
165       // creates a glass pane to lock the GUI during layout
166       glassPane = new JPanel();
167       final JRootPane rootPane = view.getRootPane();
168       rootPane.setGlassPane(glassPane);
169     }
170     glassPane.setEnabled(true);
171 
172     graph.firePreEvent();
173     try {
174       // backup the graph state for undo
175       graph.backupRealizers();
176 
177       // disable bridges during the layout animation and set the hub realizer for the new hub node
178       setBridgeCalculatorEnabled(false);
179       graph.setDefaultNodeRealizer(getHubRealizer());
180 
181       hubRoutingSupport.doLayout(graph, mode);
182       busDyer.colorize(null);
183     } finally {
184       graph.setDefaultNodeRealizer(oldNodeRealizer);
185       setBridgeCalculatorEnabled(true);
186 
187       glassPane.setEnabled(false);
188       graph.updateViews();
189       graph.firePostEvent();
190     }
191   }
192 
193   /**
194    * Initialize this demo.
195    */
196   protected void initialize() {
197     super.initialize();
198     hubRealizer = createHubRealizer();
199 
200     setBridgeCalculatorEnabled(true);
201 
202     // create a data provider that specifies whether a node is a hub
203     view.getGraph2D().addDataProvider(HUB_MARKER_DPKEY, new DataProviderAdapter() {
204       public boolean getBool(Object dataHolder) {
205         return dataHolder instanceof Node && isHub((Node) dataHolder);
206       }
207     });
208 
209     // create the data provider needed for the Orthogonal Mode
210     view.getGraph2D().addDataProvider(EditMode.ORTHOGONAL_ROUTING_DPKEY,
211         DataProviders.createConstantDataProvider(Boolean.TRUE));
212   }
213 
214   /**
215    * Registers the default actions, and replaces the delete and select all actions with this class's custom
216    * implementations.
217    */
218   protected void registerViewActions() {
219     super.registerViewActions();
220     // register keyboard actions
221     ActionMap amap = view.getCanvasComponent().getActionMap();
222     if (amap != null) {
223       if (isDeletionEnabled()) {
224         amap.put(Graph2DViewActions.DELETE_SELECTION, createDeleteSelectionAction());
225       }
226 
227       // Prevents the selection of hubs by the select all action
228       amap.put(Graph2DViewActions.SELECT_ALL, new Graph2DViewActions.SelectAllAction(view) {
229         protected void setSelected(Graph2D graph, Node node, boolean flag) {
230           if (!isHub(node)) {
231             super.setSelected(graph, node, flag);
232           }
233         }
234       });
235     }
236   }
237 
238   /**
239    * Creates a delete action which deletes selected elements and remaining stub bus parts.
240    */
241   protected Action createDeleteSelectionAction() {
242     final Action oldAction = super.createDeleteSelectionAction();
243     final Action newAction = new HubDeleteSelectionAction();
244     newAction.putValue(Action.SHORT_DESCRIPTION, oldAction.getValue(Action.SHORT_DESCRIPTION));
245     newAction.putValue(Action.SMALL_ICON, oldAction.getValue(Action.SMALL_ICON));
246     return newAction;
247   }
248 
249   /**
250    * Creates the default toolbar and adds the routing actions.
251    */
252   protected JToolBar createToolBar() {
253     JToolBar toolBar = super.createToolBar();
254     toolBar.setFloatable(false);
255 
256     toolBar.addSeparator();
257 
258     // add bus router to toolbar
259     Action routeAllAction = new AbstractAction("Route All") {
260       public void actionPerformed(ActionEvent e) {
261         doLayout(MODE_ALL);
262       }
263     };
264     routeAllAction.putValue(Action.SHORT_DESCRIPTION, "Route all buses");
265     routeAllAction.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);
266     toolBar.add(createActionControl(routeAllAction));
267 
268     // add bus router to toolbar
269     Action routeSelectedAction = new AbstractAction("Route Selected") {
270       public void actionPerformed(ActionEvent e) {
271         doLayout(MODE_SELECTED);
272       }
273     };
274     routeSelectedAction.putValue(Action.SHORT_DESCRIPTION, "Route selected buses");
275     routeSelectedAction.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);
276     toolBar.add(createActionControl(routeSelectedAction, true));
277 
278     // add settings for bus router to toolbar
279     Action propertiesAction = new AbstractAction("Settings...") {
280       public void actionPerformed(ActionEvent e) {
281         OptionSupport.showDialog(hubRoutingSupport.getModule(), view.getGraph2D(), false, view.getFrame());
282       }
283     };
284     propertiesAction.putValue(Action.SHORT_DESCRIPTION, "Configure the bus router");
285     propertiesAction.putValue(Action.SMALL_ICON, getIconResource("resource/properties.png"));
286     toolBar.add(createActionControl(propertiesAction));
287 
288     return toolBar;
289   }
290 
291   /**
292    * Creates the default menu bar and adds an additional menu of examples graphs.
293    */
294   protected JMenuBar createMenuBar() {
295     final JMenuBar menuBar = super.createMenuBar();
296     JMenu menu = new JMenu("Sample Graphs");
297     menuBar.add(menu);
298 
299     menu.add(new EmptyGraphAction("Empty Graph"));
300 
301     menu.add(new AbstractAction("One Bus") {
302       public void actionPerformed(ActionEvent e) {
303         loadGraph("resource/oneBus.graphml");
304       }
305     });
306 
307     menu.add(new AbstractAction("Three Buses") {
308       public void actionPerformed(ActionEvent e) {
309         loadGraph("resource/threeBuses.graphml");
310       }
311     });
312 
313     return menuBar;
314   }
315 
316   /**
317    * Creates a modified edit mode for this demo.
318    */
319   protected EditMode createEditMode() {
320     EditMode editMode = new HubEditMode();
321 
322     // copied from DemoBase
323     if (editMode.getMovePortMode() instanceof MovePortMode) {
324       ((MovePortMode) editMode.getMovePortMode()).setIndicatingTargetNode(true);
325     }
326 
327     //allow moving view port with right drag gesture
328     editMode.allowMovingWithPopup(true);
329     return editMode;
330   }
331 
332   /**
333    * Creates the BusDyer used by this demo.
334    */
335   protected BusDyer createBusDyer() {
336     return new BusDyer(view.getGraph2D());
337   }
338 
339   /**
340    * Creates the HubRoutingSupport used by this demo.
341    */
342   protected HubRoutingSupport createHubRoutingSupport() {
343     return new HubRoutingSupport();
344   }
345 
346   /**
347    * Creates the default realizers but with thick edges which do not display arrow heads.
348    */
349   protected void configureDefaultRealizers() {
350     super.configureDefaultRealizers();
351 
352     EdgeRealizer er = view.getGraph2D().getDefaultEdgeRealizer();
353     er.setTargetArrow(Arrow.NONE);
354     view.getGraph2D().setDefaultEdgeRealizer(er);
355   }
356 
357   /**
358    * Call-back for loading a graph. Overwritten to reset the undo queue.
359    */
360   protected void loadGraph(URL resource) {
361     super.loadGraph(resource);
362 
363     EventQueue.invokeLater(new Runnable() {
364       public void run() {
365         doLayout(MODE_ALL);
366       }
367     });
368   }
369 
370   /**
371    * Specifies whether bridges are shown and configures a {@link y.view.BridgeCalculator} accordingly.
372    */
373   private void setBridgeCalculatorEnabled(boolean enable) {
374     if (enable) {
375       // create the BridgeCalculator
376       BridgeCalculator bridgeCalculator = new BridgeCalculator();
377       ((DefaultGraph2DRenderer) view.getGraph2DRenderer()).setBridgeCalculator(bridgeCalculator);
378       bridgeCalculator.setCrossingMode(BridgeCalculator.CROSSING_MODE_ORDER_INDUCED);
379       bridgeCalculator.setCrossingStyle(BridgeCalculator.CROSSING_STYLE_ARC);
380       bridgeCalculator.setOrientationStyle(BridgeCalculator.ORIENTATION_STYLE_UP);
381     } else {
382       ((DefaultGraph2DRenderer) view.getGraph2DRenderer()).setBridgeCalculator(null);
383     }
384   }
385 
386   NodeRealizer getHubRealizer() {
387     return hubRealizer;
388   }
389 
390   /**
391    * Returns whether the specified node is a hub. A node is a hub if its realizer is the one of hubs.
392    */
393   static boolean isHub(final Node node) {
394     final NodeRealizer realizer = ((Graph2D) node.getGraph()).getRealizer(node);
395     return realizer instanceof GenericNodeRealizer
396         && HUB_CONFIGURATION.equals(((GenericNodeRealizer) realizer).getConfiguration());
397   }
398 
399   /**
400    * Creates the realizer which is used for hubs.
401    */
402   static NodeRealizer createHubRealizer() {
403     final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
404     final Map map = GenericNodeRealizer.getFactory().createDefaultConfigurationMap();
405 
406     map.put(GenericNodeRealizer.Painter.class, new ShapeNodePainter(ShapeNodePainter.RECT));
407     factory.addConfiguration(HUB_CONFIGURATION, map);
408 
409     NodeRealizer nr = new GenericNodeRealizer(HUB_CONFIGURATION);
410     nr.setFillColor(Color.BLACK);
411     nr.setLineColor(null);
412     nr.setSize(5.0, 5.0);
413     nr.removeLabel(nr.getLabel(0));
414     return nr;
415   }
416 
417   /**
418    * A modified edit mode for this demo. After each node movement and node resizing, the connections of the affected
419    * nodes to their buses are rerouted while the other parts remain fixed. If the source of an edge creation is a hub,
420    * the edge is colored in the bus' color. If a new edge is a singleton bus, that is, it connects two regular nodes, it
421    * is instantly routed.
422    */
423   protected class HubEditMode extends EditMode {
424     protected HubEditMode() {
425       setCreateEdgeMode(new AutoRoutingCreateEdgeMode(getHubRealizer()));
426     }
427 
428     /**
429      * After each node movement, the connections of the affected nodes to their buses are rerouted while the other parts
430      * remain fixed.
431      */
432     protected ViewMode createMoveSelectionMode() {
433       return new MoveSelectionMode() {
434         protected void selectionMovedAction(double dx, double dy, double x, double y) {
435           super.selectionMovedAction(dx, dy, x, y);
436           doLayout(MODE_PARTIAL);
437         }
438       };
439     }
440 
441     protected ViewMode createCreateEdgeMode() {
442       return null;
443     }
444 
445     protected ViewMode createMovePortMode() {
446       return null;
447     }
448 
449     /**
450      * Does a partial layout after resizing of nodes.
451      */
452     protected ViewMode createHotSpotMode() {
453       return new HotSpotMode() {
454         private boolean dirty;
455 
456         public void mousePressedLeft(double x, double y) {
457           dirty = false;
458           super.mousePressedLeft(x, y);
459         }
460 
461         public void mouseReleasedLeft(double x, double y) {
462           super.mouseReleasedLeft(x, y);
463           if (dirty) {
464             doLayout(MODE_PARTIAL);
465           }
466           dirty = false;
467         }
468 
469         protected void updateNodeRealizerBounds(NodeRealizer vr, double x, double y, double w, double h) {
470           super.updateNodeRealizerBounds(vr, x, y, w, h);
471           dirty = true;
472         }
473       };
474     }
475 
476     /**
477      * Prevents the selection of hubs by selection boxes.
478      */
479     protected ViewMode createSelectionBoxMode() {
480       return new SelectionBoxMode() {
481         protected void setSelected(Graph2D graph, Node n, boolean state) {
482           if (!isHub(n)) {
483             super.setSelected(graph, n, state);
484           }
485         }
486       };
487     }
488 
489     /**
490      * Prevents the selection of hubs by left mouse clicks.
491      */
492     protected void setSelected(Graph2D graph, Node node, boolean state) {
493       if (!isHub(node)) {
494         super.setSelected(graph, node, state);
495       }
496     }
497 
498     /**
499      * Selects on a click on a hub its edges.
500      */
501     protected void nodeClicked(Graph2D graph, Node node, boolean wasSelected, double x, double y,
502                                boolean modifierSet) {
503       if (isHub(node)) {
504         if (!modifierSet) {
505           graph.unselectAll();
506         }
507 
508         if (!modifierSet || graph.isSelectionEmpty() || graph.selectedEdges().ok()) {
509           for (EdgeCursor edgeCursor = node.edges(); edgeCursor.ok(); edgeCursor.next()) {
510             final Edge edge = edgeCursor.edge();
511             setSelected(graph, edge, true);
512           }
513         }
514         graph.updateViews();
515       } else {
516         super.nodeClicked(graph, node, wasSelected, x, y, modifierSet);
517       }
518     }
519 
520     /**
521      * Enables edge creation for modifier left click on an edge.
522      */
523     public void mouseDraggedLeft(double x, double y) {
524       if (isModifierPressed(lastPressEvent)) {
525         double px = translateX(lastPressEvent.getX());
526         double py = translateY(lastPressEvent.getY());
527         Edge edge = getHitInfo(px, py).getHitEdge();
528         if (edge != null) {
529           setChild(getCreateEdgeMode(), lastPressEvent, lastDragEvent);
530           return;
531         }
532       }
533       super.mouseDraggedLeft(x, y);
534     }
535 
536   }
537 
538   /**
539    * Routes each new edge automatically. If it connects two previously independent buses, the whole resulting bus is
540    * routed anew. If it connects a new node to an existing bus, the existing bus is kept fixed and only the new edge is
541    * routed. If it establishes a bus of its own, that is, it connects two regular nodes which are not associated with a
542    * bus, it is routed as single-edge bus.
543    * <p/>
544    * Additionally, takes care to split a new edge which connects two regular nodes since this demo requires each bus
545    * edge to connect to at least on hub.
546    */
547   protected class AutoRoutingCreateEdgeMode extends HubCreateEdgeMode {
548 
549     protected AutoRoutingCreateEdgeMode(NodeRealizer hubRealizer) {
550       super(hubRealizer);
551     }
552 
553     protected Edge createEdge(Graph2D graph, Node startNode, Node targetNode, EdgeRealizer realizer) {
554       // fire event to mark start of edge creation for undo/redo
555       graph.firePreEvent();
556       return super.createEdge(graph, startNode, targetNode, realizer);
557     }
558 
559     protected void edgeCreated(final Edge edge) {
560       super.edgeCreated(edge);
561       //noinspection IfStatementWithIdenticalBranches
562       if (!isHub(edge.source()) && !isHub(edge.target())) {
563         // both end nodes are regular nodes -> this is a single-edge bus
564         final Edge edge2 = splitSingleBusEdge(edge);
565         EventQueue.invokeLater(new Runnable() {
566           public void run() {
567             getGraph2D().unselectAll();
568             getGraph2D().setSelected(edge, true);
569             getGraph2D().setSelected(edge2, true);
570             doLayout(MODE_SELECTED);
571 
572             // fire event to mark start of edge creation for undo/redo
573             getGraph2D().firePostEvent();
574           }
575         });
576       } else if (isHub(edge.source()) && isHub(edge.target())) {
577         // both end nodes are hubs -> route the complete bus since partial mode can be used for end-edges only
578         EventQueue.invokeLater(new Runnable() {
579           public void run() {
580             getGraph2D().unselectAll();
581             getGraph2D().setSelected(edge, true);
582             doLayout(MODE_SELECTED);
583 
584             // fire event to mark start of edge creation for undo/redo
585             getGraph2D().firePostEvent();
586           }
587         });
588       } else {
589         // exactly one end node is a hub -> route the new edge and keep the existing bus fixed
590         EventQueue.invokeLater(new Runnable() {
591           public void run() {
592             getGraph2D().unselectAll();
593             getGraph2D().setSelected(edge, true);
594             doLayout(MODE_PARTIAL);
595 
596             // fire event to mark end of edge creation for undo/redo
597             getGraph2D().firePostEvent();
598           }
599         });
600       }
601     }
602 
603     /**
604      * In this demo, every bus edge should connect to at least one hub. Therefore, we split an edge which connect two
605      * regular nodes into two parts by adding a new hub.
606      *
607      * @param edge the edge to split. This edge is changed to connect its source to the new hub.
608      * @return the new edge which connects the new hub two the original edge's target.
609      */
610     protected Edge splitSingleBusEdge(Edge edge) {
611       final Graph2D graph = getGraph2D();
612       final Node oldSource = edge.source();
613       final Node oldTarget = edge.target();
614 
615       graph.firePreEvent();
616       try {
617         // Note: the calculates a reasonable middle point only if the edge is straight-line;
618         //       otherwise we have to split its path and distribute the bends correctly
619 
620         final Node hub = graph.createNode(getHubRealizer().createCopy());
621         graph.setCenter(hub, YPoint.midPoint(graph.getSourcePointAbs(edge), graph.getTargetPointAbs(edge)));
622 
623         graph.changeEdge(edge, oldSource, hub);
624         graph.setTargetPointRel(edge, YPoint.ORIGIN);
625 
626         final EdgeRealizer edgeRealizer = graph.getRealizer(edge);
627         final EdgeRealizer edgeRealizer2 = edgeRealizer.createCopy();
628         final Edge edge2 = graph.createEdge(hub, oldTarget, edgeRealizer2);
629         graph.setSourcePointRel(edge2, YPoint.ORIGIN);
630 
631         final NodePort targetPort = NodePort.getTargetPort(edgeRealizer);
632         if (isNodePortAware() && targetPort != null) {
633           NodePort.bindTargetPort(targetPort, edgeRealizer2);
634         }
635 
636         return edge2;
637       } finally {
638         graph.firePostEvent();
639       }
640     }
641   }
642 
643   /**
644    * Deletes selected graph elements and, additionally, all remaining bus stubs. A bus stub is a part of a bus which is
645    * connected to only one regular node after the deletion of the selected elements.
646    */
647   protected class HubDeleteSelectionAction extends Graph2DViewActions.DeleteSelectionAction {
648 
649     protected HubDeleteSelectionAction() {
650       super(BusRouterDemo.this.view);
651     }
652 
653     public void delete(Graph2DView view) {
654       final Graph2D graph = view.getGraph2D();
655       try {
656         graph.firePreEvent();
657         deleteImpl(view);
658       } finally {
659         graph.firePostEvent();
660       }
661     }
662 
663     /**
664      * Does the deletion. First, deletes the selected elements. Then, deletes iteratively each neighbor of any deleted
665      * edge if it is a hub and has degree lesser than 2. These are exactly the hubs of bus stubs.
666      */
667     private void deleteImpl(Graph2DView view) {
668       final NodeList hubsToDelete = new NodeList();
669 
670       GraphListener listener = new GraphListener() {
671         public void onGraphEvent(GraphEvent e) {
672           if (e.getType() == GraphEvent.PRE_EDGE_REMOVAL) {
673             final Edge edge = (Edge) e.getData();
674             if (isHubToDelete(edge.source())) {
675               hubsToDelete.add(edge.source());
676             }
677             if (isHubToDelete(edge.target())) {
678               hubsToDelete.add(edge.target());
679             }
680           }
681         }
682 
683         private boolean isHubToDelete(Node node) {
684           // edge not deleted yet, therefore test for degree < 3
685           return isHub(node) && node.degree() < 3;
686         }
687       };
688 
689       final Graph2D graph = view.getGraph2D();
690       try {
691         graph.addGraphListener(listener);
692         super.delete(view);
693 
694         while (!hubsToDelete.isEmpty()) {
695           final Node node = hubsToDelete.popNode();
696           if (node.getGraph() != null) {
697             graph.removeNode(node);
698           }
699         }
700       } finally {
701         graph.removeGraphListener(listener);
702       }
703     }
704   }
705 
706   /**
707    * Clears the graph and its undo queue, and sets the view to the initial zoom factor.
708    */
709   protected class EmptyGraphAction extends AbstractAction {
710 
711     protected EmptyGraphAction(String name) {
712       super(name);
713     }
714 
715     public void actionPerformed(ActionEvent e) {
716       view.getGraph2D().clear();
717       view.getGraph2D().setURL(null);
718       view.fitContent();
719       view.updateView();
720       getUndoManager().resetQueue();
721     }
722   }
723 
724   /**
725    * Runs this demo.
726    *
727    * @param args unused
728    */
729   public static void main(String[] args) {
730     EventQueue.invokeLater(new Runnable() {
731       public void run() {
732         Locale.setDefault(Locale.ENGLISH);
733         initLnF();
734         new BusRouterDemo("resource/busrouterhelp.html").start("Bus Router Demo");
735       }
736     });
737   }
738 }
739