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