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.router;
15  
16  import demo.view.DemoBase;
17  import demo.view.DemoDefaults;
18  
19  import y.base.DataProvider;
20  import y.base.Edge;
21  import y.base.EdgeMap;
22  import y.base.Node;
23  import y.base.NodeList;
24  import y.io.IOHandler;
25  import y.layout.LayoutGraph;
26  import y.layout.Layouter;
27  import y.layout.PortConstraintConfigurator;
28  import y.layout.PortConstraintKeys;
29  import y.layout.router.ChannelEdgeRouter;
30  import y.layout.router.OrthogonalEdgeRouter;
31  import y.module.ChannelEdgeRouterModule;
32  import y.module.OrthogonalEdgeRouterModule;
33  import y.module.YModule;
34  import y.util.DataProviderAdapter;
35  import y.view.CreateEdgeMode;
36  import y.view.DefaultGraph2DRenderer;
37  import y.view.Drawable;
38  import y.view.EditMode;
39  import y.view.Graph2D;
40  import y.view.HotSpotMode;
41  import y.view.MoveSelectionMode;
42  import y.view.Selections;
43  import y.view.Graph2DView;
44  
45  import javax.swing.AbstractAction;
46  import javax.swing.JComboBox;
47  import javax.swing.JToolBar;
48  import java.awt.EventQueue;
49  import java.awt.Graphics2D;
50  import java.awt.Rectangle;
51  import java.awt.event.ActionEvent;
52  import java.awt.event.ActionListener;
53  import java.io.IOException;
54  import java.util.Locale;
55  
56  /**
57   * A demo that shows how Orthogonal Edge Router and Channel Edge Router can be used to find routes through a maze.
58   * Not only will it find a way but also one with fewest possible changes in direction.
59   * <br>
60   * The following aspects of using the edge routers are demonstrated.
61   * <ol>
62   * <li>How to use OrthogonalEdgeRouterModule and ChannelEdgeRouterModules respectively as
63   *     a convenient means to launch and
64   *     configure the edge routers.</li>
65   * <li>How to modify the yFiles EditMode in order to trigger the
66   *     orthogonal edge router whenever
67   *     <ul>
68   *     <li>new edges get created</li>
69   *     <li>nodes get resized</li>
70   *     <li>selected nodes will be moved</li>
71   *     </ul></li>
72   * </ol>
73   * Additionally this demo shows how non-editable background-layer graphs can be displayed inside
74   * the graph view.
75   * <br/>
76   * Usage: Create nodes and edges. The edges will be routed immediately. To reroute all edges use
77   * the toolbar button "Route Edges".
78   *
79   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/orthogonal_edge_router.html">Section Orthogonal Edge Routing</a> in the yFiles for Java Developer's Guide
80   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/channel_edge_router.html">Section Channel Edge Routing</a> in the yFiles for Java Developer's Guide
81   */
82  public class MazeRouterDemo extends DemoBase {
83    private RouterStrategy strategy;
84    private Graph2D mazeG;
85    private Drawable mazeD;
86    private NodeList mazeNodes;
87    private OrthogonalEdgeRouterStrategy orthogonalEdgeRouterStrategy;
88    private ChannelEdgeRouterStrategy channelEdgeRouterStrategy;
89  
90    public MazeRouterDemo() {
91      initializeMaze();
92  
93      initializeGraph();
94    }
95  
96    protected void initialize() {
97      channelEdgeRouterStrategy = new ChannelEdgeRouterStrategy();
98      orthogonalEdgeRouterStrategy = new OrthogonalEdgeRouterStrategy();
99      view.setContentPolicy(Graph2DView.CONTENT_POLICY_BACKGROUND_DRAWABLES);
100   }
101 
102   protected void configureDefaultRealizers() {
103     super.configureDefaultRealizers();
104     view.getGraph2D().getDefaultNodeRealizer().setSize(30, 30);
105   }
106 
107   /**
108    * Returns ViewActionDemo toolbar plus actions to trigger some layout algorithms
109    */
110   protected JToolBar createToolBar() {
111     final JComboBox comboBox = new JComboBox(new Object[]{"Orthogonal Edge Router", "Channel Edge Router"});
112     comboBox.setMaximumSize(comboBox.getPreferredSize());
113     comboBox.addActionListener(new ActionListener() {
114       public void actionPerformed(ActionEvent e) {
115         strategy = comboBox.getSelectedIndex() == 0 ?
116             (RouterStrategy) orthogonalEdgeRouterStrategy :
117             channelEdgeRouterStrategy;
118         doLayout();
119       }
120     });
121     strategy = orthogonalEdgeRouterStrategy;
122 
123     JToolBar toolBar = super.createToolBar();
124     toolBar.addSeparator();
125     toolBar.add(createActionControl(new LayoutAction()));
126     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
127     toolBar.add(comboBox);
128     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
129     toolBar.add(createActionControl(new OptionAction()));
130 
131     return toolBar;
132   }
133 
134   /**
135    * Provides configuration options for the edge router.
136    */
137   class OptionAction extends AbstractAction {
138     OptionAction() {
139       super("Settings...", getIconResource("resource/properties.png"));
140     }
141 
142     public void actionPerformed(ActionEvent e) {
143       final ActionListener listener = new ActionListener() {
144         public void actionPerformed(ActionEvent e) {
145           doLayout();
146         }
147       };
148       OptionSupport.showDialog(strategy.getModule().getOptionHandler(), listener, false, view.getFrame());
149     }
150   }
151 
152   /**
153    * Launches the Orthogonal Edge Router.
154    */
155   class LayoutAction extends AbstractAction {
156     LayoutAction() {
157       super("Route Edges", SHARED_LAYOUT_ICON);
158     }
159 
160     public void actionPerformed(ActionEvent e) {
161       doLayout();
162     }
163   }
164 
165   /**
166    * Modified action to fit the content nicely inside the view.
167    */
168   class FitContent extends AbstractAction {
169     FitContent() {
170       super("Fit Content");
171     }
172 
173     public void actionPerformed(ActionEvent e) {
174       Graph2D graph = view.getGraph2D();
175 
176       Rectangle r = graph.getBoundingBox();
177       r.add(mazeD.getBounds());
178       view.fitRectangle(r);
179       graph.updateViews();
180     }
181   }
182 
183   void doLayout() {
184     Graph2D graph = view.getGraph2D();
185     addMazeGraph();
186     // Start the module.
187     strategy.getModule().start(graph);
188     subtractMazeGraph();
189   }
190 
191   /**
192    * Adds a specially configured EditMode that will automatically route all
193    * newly created edges orthogonally. The orthogonal edge router will also
194    * be activated on some edges, when nodes get resized or a node selection gets
195    * moved.
196    */
197   protected void registerViewModes() {
198     EditMode mode = new EditMode();
199     view.addViewMode(mode);
200 
201     mode.setMoveSelectionMode(new MyMoveSelectionMode());
202     mode.setCreateEdgeMode(new MyCreateEdgeMode());
203     mode.setHotSpotMode(new MyHotSpotMode());
204   }
205 
206   /**
207    * A special mode for creating edges.
208    */
209   class MyCreateEdgeMode extends CreateEdgeMode {
210     private Node source;
211 
212     protected boolean acceptSourceNode(Node s, double x, double y) {
213       source = s;
214       return true;
215     }
216 
217     protected boolean acceptTargetNode(Node t, double x, double y) {
218       return (source != t);
219     }
220 
221     protected void edgeCreated(final Edge e) {
222       routeNewEdge(e);
223     }
224   }
225 
226   void routeNewEdge(Edge e) {
227     final Graph2D graph = view.getGraph2D();
228     addMazeGraph();
229     strategy.routeNewEdge(e);
230     subtractMazeGraph();
231     graph.updateViews();
232   }
233 
234   /**
235    * A special mode for resizing nodes.
236    */
237   class MyHotSpotMode extends HotSpotMode {
238     public void mouseReleasedLeft(double x, double y) {
239       super.mouseReleasedLeft(x, y);
240 
241       final Graph2D graph = view.getGraph2D();
242 
243       DataProvider selectedNodes = Selections.createSelectionDataProvider(graph);
244       addMazeGraph();
245       strategy.rerouteAdjacentEdges(selectedNodes, graph);
246       subtractMazeGraph();
247       graph.updateViews();
248     }
249   }
250 
251   /**
252    * A special mode for moving a selection of the graph.
253    */
254   class MyMoveSelectionMode extends MoveSelectionMode {
255     private static final boolean ROUTE_EDGES_ON_MOVE = false;
256 
257     protected void selectionOnMove(double dx, double dy, double x, double y) {
258       if (ROUTE_EDGES_ON_MOVE) {
259         routeEdgesToSelection();
260       }
261     }
262 
263     protected void selectionMovedAction(double dx, double dy, double x, double y) {
264       routeEdgesToSelection();
265     }
266 
267     void routeEdgesToSelection() {
268       final Graph2D graph = view.getGraph2D();
269 
270       if (graph.selectedNodes().ok()) {
271         addMazeGraph();
272         strategy.routeEdgesToSelection(graph);
273         subtractMazeGraph();
274         graph.updateViews();
275       }
276     }
277   }
278 
279   /**
280    * Adds the maze to the user-given graph, so that the edge router can lay
281    * Ariadne's thread...
282    */
283   private void addMazeGraph() {
284     mazeNodes = new NodeList(mazeG.nodes());
285     mazeG.moveSubGraph(mazeNodes, view.getGraph2D());
286   }
287 
288   /**
289    * The maze gets removed from the user-given graph again.
290    **/
291   private void subtractMazeGraph() {
292     view.getGraph2D().moveSubGraph(mazeNodes, mazeG);
293   }
294 
295   /**
296    * Initializes the maze the first time.
297    */
298   private void initializeMaze() {
299     mazeG = new Graph2D();
300     try {
301       IOHandler ioHandler = createGraphMLIOHandler();
302       ioHandler.read(mazeG, getClass().getResource("resource/maze.graphml"));
303       DemoDefaults.applyFillColor(mazeG, DemoDefaults.DEFAULT_CONTRAST_COLOR);
304       DemoDefaults.applyLineColor(mazeG, DemoDefaults.DEFAULT_CONTRAST_COLOR);
305       
306     } catch (IOException e) {
307       System.out.println("Could not initialize maze!");
308       e.printStackTrace();
309       System.exit(-1);
310     }
311     // Create a drawable and add it to the graph as a visual representation
312     // of the maze. This way it is not possible to move the maze's walls.
313     mazeD = new MazeDrawable(mazeG);
314     view.addBackgroundDrawable(mazeD);
315     view.fitRectangle(mazeD.getBounds());
316   }
317 
318   /**
319    * Creates an initial graph and initially routes the contained edge through the maze
320    */
321   private void initializeGraph() {
322     final Graph2D graph = view.getGraph2D();
323     Node start = graph.createNode();
324     graph.setLocation(start, 20,600);
325     Node end = graph.createNode();
326     graph.setLocation(end, 600,20);
327     Edge edge = graph.createEdge(start, end);
328     routeNewEdge(edge);
329   }
330   /**
331    * Launches this demo.
332    */
333   public static void main(String[] args) {
334     EventQueue.invokeLater(new Runnable() {
335       public void run() {
336         Locale.setDefault(Locale.ENGLISH);
337         initLnF();
338         (new MazeRouterDemo()).start();
339       }
340     });
341   }
342 
343   /**
344    * To transform the whole maze graph into a maze drawable.
345    */
346   static class MazeDrawable implements Drawable {
347     private Graph2D mazeG;
348     private DefaultGraph2DRenderer render;
349 
350     public MazeDrawable(Graph2D g) {
351       mazeG = g;
352       render = new DefaultGraph2DRenderer();
353     }
354 
355     public Rectangle getBounds() {
356       return mazeG.getBoundingBox();
357     }
358 
359     public void paint(Graphics2D gfx) {
360       render.paint(gfx, mazeG);
361     }
362   }
363 
364   abstract static class RouterStrategy {
365     abstract YModule getModule();
366 
367     abstract void routeNewEdge(Edge e);
368 
369     abstract void rerouteAdjacentEdges(DataProvider selectedNodes, LayoutGraph graph);
370 
371     abstract void routeEdgesToSelection(Graph2D graph);
372 
373     abstract void route(Layouter router, LayoutGraph graph);
374 
375     protected void routeNewEdge(Layouter router, final Edge e, Graph2D graph) {
376       EdgeMap spc = (EdgeMap) graph.getDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
377       EdgeMap tpc = (EdgeMap) graph.getDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
378 
379       PortConstraintConfigurator pcc = new PortConstraintConfigurator();
380       if (spc != null && tpc != null) {
381         spc.set(e, pcc.createPortConstraintFromSketch(graph, e, true, false));
382         tpc.set(e, pcc.createPortConstraintFromSketch(graph, e, false, false));
383         route(router, graph);
384         spc.set(e, null);
385         tpc.set(e, null);
386       } else {
387         route(router, graph);
388       }
389     }
390 
391     protected void routeEdgesToSelection(final Graph2D graph, Layouter router, Object affectedEdgesKey) {
392       graph.addDataProvider(affectedEdgesKey, new DataProviderAdapter() {
393         public boolean getBool(Object dataHolder) {
394           return graph.isSelected(((Edge) dataHolder).source()) || graph.isSelected(((Edge) dataHolder).target());
395         }
396       });
397       route(router, graph);
398       graph.removeDataProvider(affectedEdgesKey);
399     }
400 
401     protected void routeNewEdge(final Edge e, Graph2D graph, Layouter router, Object selectedEdgesKey) {
402       DataProvider activeEdges = new DataProviderAdapter() {
403         public boolean getBool(Object o) {
404           return e == o;
405         }
406       };
407       graph.addDataProvider(selectedEdgesKey, activeEdges);
408       routeNewEdge(router, e, graph);
409       graph.removeDataProvider(selectedEdgesKey);
410     }
411   }
412 
413   static class OrthogonalEdgeRouterStrategy extends RouterStrategy {
414     private OrthogonalEdgeRouterModule module = new OrthogonalEdgeRouterModule();
415 
416     public YModule getModule() {
417       return module;
418     }
419 
420     public void routeNewEdge(final Edge e) {
421       Graph2D graph = (Graph2D) e.getGraph();
422       OrthogonalEdgeRouter router = new OrthogonalEdgeRouter();
423       module.configure(router);
424       router.setSphereOfAction(OrthogonalEdgeRouter.ROUTE_SELECTED_EDGES);
425       routeNewEdge(e, graph, router, Layouter.SELECTED_EDGES);
426     }
427 
428     public void rerouteAdjacentEdges(DataProvider selectedNodes, LayoutGraph graph) {
429       OrthogonalEdgeRouter router = new OrthogonalEdgeRouter();
430       module.configure(router);
431       router.setSphereOfAction(OrthogonalEdgeRouter.ROUTE_EDGES_AT_SELECTED_NODES);
432       graph.addDataProvider(Layouter.SELECTED_NODES, selectedNodes);
433       this.route(router, graph);
434       graph.removeDataProvider(Layouter.SELECTED_NODES);
435     }
436 
437     public void routeEdgesToSelection(final Graph2D graph) {
438       OrthogonalEdgeRouter router = new OrthogonalEdgeRouter();
439       module.configure(router);
440       router.setSphereOfAction(OrthogonalEdgeRouter.ROUTE_SELECTED_EDGES);
441       routeEdgesToSelection(graph, router, Layouter.SELECTED_EDGES);
442     }
443 
444     void route(Layouter router, LayoutGraph graph) {
445       router.doLayout(graph);
446     }
447   }
448 
449   static class ChannelEdgeRouterStrategy extends RouterStrategy {
450     private ChannelEdgeRouterModule module;
451 
452     public ChannelEdgeRouterStrategy() {
453       module = new ChannelEdgeRouterModule();
454       module.getOptionHandler().set("PATHFINDER", "ORTHOGONAL_SHORTESTPATH_PATH_FINDER");
455     }
456 
457     public YModule getModule() {
458       return module;
459     }
460 
461     public void routeNewEdge(final Edge e) {
462       final Graph2D graph = (Graph2D) e.getGraph();
463       ChannelEdgeRouter router = new ChannelEdgeRouter();
464       module.configure(router);
465       routeNewEdge(e, graph, router, ChannelEdgeRouter.AFFECTED_EDGES);
466     }
467 
468     public void rerouteAdjacentEdges(final DataProvider selectedNodes, LayoutGraph graph) {
469       ChannelEdgeRouter router = new ChannelEdgeRouter();
470       module.configure(router);
471       graph.addDataProvider(ChannelEdgeRouter.AFFECTED_EDGES, new DataProviderAdapter() {
472         public boolean getBool(Object dataHolder) {
473           return selectedNodes.getBool((((Edge) dataHolder).source())) || selectedNodes.getBool(
474               ((Edge) dataHolder).target());
475         }
476       });
477       this.route(router, graph);
478       graph.removeDataProvider(ChannelEdgeRouter.AFFECTED_EDGES);
479     }
480 
481     public void routeEdgesToSelection(final Graph2D graph) {
482       ChannelEdgeRouter router = new ChannelEdgeRouter();
483       module.configure(router);
484       routeEdgesToSelection(graph, router, ChannelEdgeRouter.AFFECTED_EDGES);
485     }
486 
487     void route(Layouter router, LayoutGraph graph) {
488       router.doLayout(graph);
489     }
490   }
491 }
492