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.view.entityrelationship;
15  
16  import demo.view.DemoBase;
17  import demo.view.entityrelationship.painters.ErdAttributesNodeLabelModel;
18  import demo.view.entityrelationship.painters.ErdRealizerFactory;
19  import demo.view.flowchart.FlowchartView;
20  import y.base.Edge;                                                             
21  import y.io.GraphMLIOHandler;
22  import y.io.graphml.graph2d.Graph2DGraphMLHandler;
23  import y.layout.orthogonal.EdgeLayoutDescriptor;                                
24  import y.layout.orthogonal.OrthogonalLayouter;                                  
25  import y.module.ModuleEvent;                                                    
26  import y.module.ModuleListener;                                                 
27  import y.module.OrthogonalLayoutModule;                                         
28  import y.option.OptionHandler;                                                  
29  import y.util.DataProviderAdapter;                                              
30  import y.view.Arrow;                                                            
31  import y.view.EdgeRealizer;                                                     
32  import y.view.EditMode;
33  import y.view.Graph2D;                                                          
34  import y.view.Graph2DClipboard;
35  import y.view.Graph2DUndoManager;
36  import y.view.Graph2DView;
37  import y.view.Graph2DViewActions;
38  import y.view.HitInfo;
39  import y.view.NodeLabel;
40  import y.view.NodeRealizer;
41  import y.view.ViewMode;
42  
43  import javax.swing.AbstractAction;
44  import javax.swing.Action;
45  import javax.swing.BorderFactory;
46  import javax.swing.JComponent;
47  import javax.swing.JLabel;
48  import javax.swing.JMenu;
49  import javax.swing.JMenuBar;
50  import javax.swing.JMenuItem;
51  import javax.swing.JPanel;
52  import javax.swing.JSplitPane;
53  import javax.swing.JToolBar;
54  import javax.swing.KeyStroke;
55  import java.awt.BorderLayout;
56  import java.awt.Color;
57  import java.awt.EventQueue;
58  import java.awt.Font;
59  import java.awt.event.ActionEvent;
60  import java.awt.event.InputEvent;
61  import java.awt.event.KeyEvent;
62  import java.beans.PropertyChangeEvent;
63  import java.beans.PropertyChangeListener;
64  import java.util.Iterator;
65  import java.util.Locale;
66  
67  /**
68   * A viewer and editor for entity relationship diagrams (ERD). It shows how to
69   * <ul>
70   * <li>add a palette of ERD symbols, the {@link EntityRelationshipPalette}, to ease the creation of diagrams</li>
71   * <li>implement a {@link y.view.GenericNodeRealizer.Painter} tailored for the drawing of ERD symbols
72   *      with two labels</li>
73   * <li>convert the notation of the diagram with a custom class, the {@link ErdNotationConverter}</li>
74   * <li>apply an orthogonal layout with suitable default values</li>
75   * <li>add undo/redo/cut/copy/paste</li>
76   * </ul>
77   *
78   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/orthogonal_layouter.html">Section Orthogonal Layoutt</a> in the yFiles for Java Developer's Guide
79   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/directed_orthogonal_layouter.html">Section Directed Orthogonal Layout</a> in the yFiles for Java Developer's Guide
80   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/orthogonal_group_layouter.html">Section Orthogonal Layout of Grouped Graphst</a> in the yFiles for Java Developer's Guide
81   */
82  public class EntityRelationshipDemo extends DemoBase {
83  
84    /** Names of the provided example graphs */
85    private static final String[] EXAMPLES_FILE_NAMES = {
86        "chen.graphml",
87        "alaska_geologic_database.graphml",
88        "crows_foot.graphml",
89        "space_database.graphml"
90    };
91  
92    /** Component that provides the symbols of ERD diagrams */
93    EntityRelationshipPalette palette;
94    /** Manager of undo- and redo-events */
95    private Graph2DUndoManager undoManager;
96    /** Clipboard to provide cut/copy/past */
97    private Graph2DClipboard clipboard;
98    /** Module for execution of orthogonal layout */                              
99    private OrthogonalLayoutModule module;                                        
100 
101   /** Instantiates this demo and builds the GUI. */
102   public EntityRelationshipDemo(){
103     super();
104 
105     JPanel panelPalette = createTitledPanel(palette, "ERD Palette");
106     contentPane.add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panelPalette, view), BorderLayout.CENTER);
107 
108     loadGraph("resource/graphs/" + EXAMPLES_FILE_NAMES[0]);
109   }
110 
111   /** Initializes the Flowchart palette, the undo manager, the clipboard and the layout module */
112   protected void initialize() {
113     palette = new EntityRelationshipPalette(view);
114     palette.setSnapMode(true);
115 
116     undoManager = new Graph2DUndoManager(view.getGraph2D());
117     undoManager.setViewContainer(view);
118 
119     clipboard = new Graph2DClipboard(view);
120     clipboard.setCopyFactory(view.getGraph2D().getGraphCopyFactory());
121 
122     // orthogonal layout with default settings                                  
123     module = new OrthogonalLayoutModule();                                      
124     OptionHandler defaultSettings = module.getOptionHandler();                  
125     defaultSettings.set("LAYOUT", "STYLE", "NORMAL_TREE");                      
126     defaultSettings.set("LAYOUT", "GRID", new Integer(5));                      
127     defaultSettings.set("LAYOUT", "USE_FACE_MAXIMIZATION", Boolean.TRUE);       
128     defaultSettings.set("LABELING", "EDGE_LABELING", "GENERIC");                
129     defaultSettings.set("LABELING", "EDGE_LABEL_MODEL", "AS_IS");               
130     defaultSettings.getItem("LAYOUT", "MINIMUM_FIRST_SEGMENT_LENGTH").setEnabled(false);              
131     defaultSettings.getItem("LAYOUT", "MINIMUM_LAST_SEGMENT_LENGTH").setEnabled(false);               
132     defaultSettings.getItem("LAYOUT", "MINIMUM_SEGMENT_LENGTH").setEnabled(false);                    
133 
134     // register module listener in order to assign minimal first/last segment lengths for every       
135     // individually before running layout using a DataProvider and remove the DataProvider afterwards 
136     module.addModuleListener(new ModuleListener() {                                                   
137       public void moduleEventHappened(ModuleEvent moduleEvent) {                                      
138         short type = moduleEvent.getEventType();                                                      
139         final Graph2D graph = view.getGraph2D();                                                      
140         if (type == ModuleEvent.TYPE_MODULE_INITIALIZING) {                                           
141           // add DataProvider to ensure a minimal first/last segment length if there are arrows       
142           graph.addDataProvider(OrthogonalLayouter.EDGE_LAYOUT_DESCRIPTOR_DPKEY,                      
143               new DataProviderAdapter() {                                                             
144                 public Object get(Object dataHolder) {                                                
145                   EdgeRealizer realizer = graph.getRealizer(                                          
146                       ((Edge) dataHolder));                                                           
147                   Arrow sourceArrow = realizer.getSourceArrow();                                      
148                   Arrow targetArrow = realizer.getTargetArrow();                                      
149                   EdgeLayoutDescriptor descriptor = new EdgeLayoutDescriptor();                       
150                   descriptor.setMinimumFirstSegmentLength(getArrowLength(sourceArrow) + 10);          
151                   descriptor.setMinimumLastSegmentLength(getArrowLength(targetArrow) + 10);           
152                   return descriptor;                                                                  
153                 }                                                                                     
154               });                                                                                     
155         } else if (type == ModuleEvent.TYPE_MODULE_DISPOSED) {                                        
156           // remove DataProvider with information about the minimal first/last segment length         
157           graph.removeDataProvider(OrthogonalLayouter.EDGE_LAYOUT_DESCRIPTOR_DPKEY);                  
158         }                                                                                             
159       }                                                                                               
160 
161       private double getArrowLength(Arrow arrow) {                                                    
162         switch (arrow.getType()) {                                                                    
163           case Arrow.CROWS_FOOT_ONE_TYPE:                                                             
164             return 10;                                                                                
165           case Arrow.CROWS_FOOT_ONE_MANDATORY_TYPE:                                                   
166             return 15;                                                                                
167           case Arrow.CROWS_FOOT_ONE_OPTIONAL_TYPE:                                                    
168             return 20;                                                                                
169           case Arrow.CROWS_FOOT_MANY_TYPE:                                                            
170             return 10;                                                                                
171           case Arrow.CROWS_FOOT_MANY_MANDATORY_TYPE:                                                  
172             return 15;                                                                                
173           case Arrow.CROWS_FOOT_MANY_OPTIONAL_TYPE:                                                   
174             return 20;                                                                                
175           default:                                                                                    
176             return arrow.getArrowLength();                                                            
177         }                                                                                             
178       }                                                                                               
179     });                                                                                               
180   }
181 
182   /** Registers the default view actions and an additional handler that reacts to label changes */
183   protected void registerViewActions() {
184     super.registerViewActions();
185     final Action action = view.getCanvasComponent().getActionMap().get(Graph2DViewActions.EDIT_LABEL);
186     action.putValue("PROPERTY_CHANGE_LISTENER", new LabelChangeHandler());
187   }
188 
189   /** Prevents view from registering view modes automatically because the <code>FlowchartView</code> will register its own view modes */
190   protected void registerViewModes() {
191   }
192 
193   /**
194    * Creates a <code>GraphMLOIHandler</code> with additionally (de-)serialization
195    * support for the custom label model that is used in big entities.
196    * @return an extended <code>GraphMLOIHandler</code> with support for bit entities
197    * @see ErdAttributesNodeLabelModel
198    */
199   protected GraphMLIOHandler createGraphMLIOHandler() {
200     GraphMLIOHandler graphMLIOHandler = super.createGraphMLIOHandler();
201     Graph2DGraphMLHandler graphMLHandler = graphMLIOHandler.getGraphMLHandler();
202     ErdAttributesNodeLabelModel.Handler handler = new ErdAttributesNodeLabelModel.Handler();
203     graphMLHandler.addSerializationHandler(handler);
204     graphMLHandler.addDeserializationHandler(handler);
205 
206     return graphMLIOHandler;
207   }
208 
209   /**
210    * Adds menu items for example graphs to the default menu bar.
211    * @return the menu bar for this demo.
212    */
213   protected JMenuBar createMenuBar() {
214     JMenu examplesMenu = new JMenu("Examples");
215     for (int i = 0; i < EXAMPLES_FILE_NAMES.length; i++) {
216       final String fileName = EXAMPLES_FILE_NAMES[i];
217       examplesMenu.add(new JMenuItem(new AbstractAction(fileName) {
218         public void actionPerformed(ActionEvent e) {
219           loadGraph("resource/graphs/" + fileName);
220         }
221       }));
222     }
223 
224     JMenuBar menuBar = super.createMenuBar();
225     menuBar.add(examplesMenu);
226     return menuBar;
227   }
228 
229   /**
230    * Adds undo/redo actions, cut/copy/paste actions, an orthogonal layout editor action
231    * and notation converter actions to the default toolbar.
232    * @return the toolbar for this demo.
233    */
234   protected JToolBar createToolBar() {
235     JToolBar toolBar = super.createToolBar();
236     toolBar.addSeparator();
237     toolBar.add(createUndoAction());
238     toolBar.add(createRedoAction());
239     toolBar.addSeparator();
240     toolBar.add(createCutAction());
241     toolBar.add(createCopyAction());
242     toolBar.add(createPasteAction());
243     toolBar.addSeparator();                                                                           
244     toolBar.add(createActionControl(createOrthogonalLayoutAction()));                                 
245     toolBar.addSeparator();                                                                           
246     toolBar.add(createCrowsFootNotationAction());                                                     
247     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);                                                    
248     toolBar.add(createChenNotationAction());                                                          
249     return toolBar;
250   }
251 
252   /**                                                                           
253    * Creates an action to trigger a conversion to Crow's Foot notation.         
254    * @return the converter action                                               
255    */                                                                           
256   private Action createCrowsFootNotationAction(){                               
257     Action action = new AbstractAction("Convert to Crow's Foot") {              
258                                                                                 
259       public void actionPerformed(ActionEvent e){                               
260         Graph2D graph = view.getGraph2D();                                      
261         try{                                                                    
262           graph.firePreEvent();                                                 
263           ErdNotationConverter.convertToCrowFoot(graph);                        
264           module.start(view.getGraph2D());                                      
265         }finally {                                                              
266           graph.firePostEvent();                                                
267         }                                                                       
268                                                                                 
269         view.fitContent();                                                      
270         view.updateView();                                                      
271       }                                                                         
272     };                                                                          
273                                                                                 
274     return action;                                                              
275   }                                                                             
276 
277   /**                                                                           
278    * Creates an action to trigger a conversion to Chen notation.                
279    * @return the converter action                                               
280    */                                                                           
281   private Action createChenNotationAction(){                                    
282     Action action = new AbstractAction("Convert to Chen") {                     
283                                                                                 
284       public void actionPerformed(ActionEvent e){                               
285         Graph2D graph = view.getGraph2D();                                      
286         try{                                                                    
287           graph.firePreEvent();                                                 
288           ErdNotationConverter.convertToChen(graph);                            
289           module.start(view.getGraph2D());                                      
290         }finally {                                                              
291           graph.firePostEvent();                                                
292         }                                                                       
293                                                                                 
294         view.fitContent();                                                      
295         view.updateView();                                                      
296       }                                                                         
297     };                                                                          
298                                                                                 
299     return action;                                                              
300   }                                                                             
301 
302   /**                                                                                                 
303    * Creates an action that shows an editor to adjust and execute orthogonal                          
304    * layout.                                                                                          
305    * @return the orthogonal layout action                                                             
306    */                                                                                                 
307   private Action createOrthogonalLayoutAction() {                                                     
308     Action action = new AbstractAction("Layout") {                                                    
309       public void actionPerformed(ActionEvent e) {                                                    
310         OptionSupport.showDialog(module, view.getGraph2D(), true, view.getFrame());                   
311       }                                                                                               
312     };                                                                                                
313     action.putValue(Action.SHORT_DESCRIPTION, "Configure and run the layout algorithm");              
314     action.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);                                           
315 
316     return action;                                                                                    
317   }                                                                                                   
318 
319   /**
320    * Creates and configures the undo action.
321    * @return the undo action.
322    */
323   protected Action createUndoAction() {
324     Action undoAction = undoManager.getUndoAction();
325     undoAction.putValue(Action.SMALL_ICON, getIconResource("resource/undo.png"));
326     undoAction.putValue(Action.SHORT_DESCRIPTION, "Undo");
327 
328     return undoAction;
329   }
330 
331   /**
332    * Creates and configures the redo action.
333    * @return the redo action.
334    */
335   protected Action createRedoAction() {
336     Action redoAction = undoManager.getRedoAction();
337     redoAction.putValue(Action.SMALL_ICON, getIconResource("resource/redo.png"));
338     redoAction.putValue(Action.SHORT_DESCRIPTION, "Redo");
339     return redoAction;
340   }
341 
342   /**
343    * Creates and configures the cut action.
344    * @return the cut action.
345    */
346   protected Action createCutAction() {
347     Action cutAction = clipboard.getCutAction();
348     cutAction.putValue(Action.SMALL_ICON, getIconResource("resource/cut.png"));
349     cutAction.putValue(Action.SHORT_DESCRIPTION, "Cut");
350 
351     view.getCanvasComponent().getActionMap().put("CUT", cutAction);
352     view.getCanvasComponent().getInputMap().put(
353         KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK), "CUT");
354 
355     return cutAction;
356   }
357 
358   /**
359    * Creates and configures the copy action.
360    * @return the copy action.
361    */
362   Action createCopyAction() {
363     Action copyAction = clipboard.getCopyAction();
364     copyAction.putValue(Action.SMALL_ICON, getIconResource("resource/copy.png"));
365     copyAction.putValue(Action.SHORT_DESCRIPTION, "Copy");
366 
367     view.getCanvasComponent().getActionMap().put("COPY", copyAction);
368     view.getCanvasComponent().getInputMap().put(
369         KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "COPY");
370 
371     return copyAction;
372   }
373 
374   /**
375    * Creates and configures the paste action.
376    * @return the paste action.
377    */
378   Action createPasteAction() {
379     Action pasteAction = clipboard.getPasteAction();
380     pasteAction.putValue(Action.SMALL_ICON, getIconResource("resource/paste.png"));
381     pasteAction.putValue(Action.SHORT_DESCRIPTION, "Paste");
382 
383     view.getCanvasComponent().getActionMap().put("PASTE", pasteAction);
384     view.getCanvasComponent().getInputMap().put(
385         KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "PASTE");
386 
387     return pasteAction;
388   }
389 
390   /**
391    * Creates a panel which contains the specified component and a title on top.
392    * @param content the Component that will be shown in the panel
393    * @param title the text that will be displayed on top of the panel
394    * @return the panel
395    */
396   protected JPanel createTitledPanel(JComponent content, String title) {
397     JLabel label = new JLabel(title);
398     label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
399     label.setBackground(new Color(231, 219, 182));
400     label.setOpaque(true);
401     label.setForeground(Color.DARK_GRAY);
402     label.setFont(label.getFont().deriveFont(Font.BOLD));
403     label.setFont(label.getFont().deriveFont(13.0f));
404 
405     JPanel panel = new JPanel();
406     panel.setLayout(new BorderLayout());
407     panel.add(label, BorderLayout.NORTH);
408     panel.add(content, BorderLayout.CENTER);
409     return panel;
410   }
411 
412   /**
413    * Creates a view of the graph that supports label editing on double-click.
414    */
415   protected Graph2DView createGraphView() {
416     Graph2DView view = new FlowchartView();
417     for (Iterator iterator = view.getViewModes(); iterator.hasNext(); ) {
418       final Object next = iterator.next();
419       if (next instanceof EditMode) {
420         final EditMode editMode = (EditMode) next;
421         editMode.setCyclicSelectionEnabled(true);
422         editMode.setPopupMode(new EntityRelationshipPopupMode());
423       }
424     }
425     view.addViewMode(new ViewMode(){
426 
427       // Reacts to double-click on nodes/labels by presenting a label editor
428       public void mouseClicked(double x, double y) {
429         if(lastClickEvent != null && lastClickEvent.getClickCount() == 2){
430           final HitInfo hitInfo = getHitInfo(x, y);
431           if (hitInfo.hasHitNodeLabels()) {
432             view.openLabelEditor(hitInfo.getHitNodeLabel(),x,y,new LabelChangeHandler(), true);
433           }else {
434             if (hitInfo.hasHitNodes()){
435               final NodeRealizer realizer = view.getGraph2D().getRealizer(hitInfo.getHitNode());
436               for(int i=realizer.labelCount(); i --> 0;){
437                 final NodeLabel label = realizer.getLabel(i);
438                 if (label.contains(x,y)) {
439                   view.openLabelEditor(label,x,y,new LabelChangeHandler(), true);
440                   return;
441                 }
442               }
443             }
444           }
445         }
446       }
447     });
448     view.setFitContentOnResize(true);
449     return view;
450   }
451 
452   /**
453    * Starts the <code>EntityRelationshipDemo</code>
454    * @param args --
455    */
456   public static void main(String[] args) {
457     EventQueue.invokeLater(new Runnable() {
458 
459       public void run() {
460         Locale.setDefault(Locale.ENGLISH);
461         initLnF();
462         final EntityRelationshipDemo demo = new EntityRelationshipDemo();
463         demo.start();
464       }
465     });
466   }
467 
468   /**
469    * This handler listens for label changes and adjusts the node size to
470    * the label size.
471    */
472   private class LabelChangeHandler implements PropertyChangeListener {
473     public void propertyChange(PropertyChangeEvent e) {
474       final Object source = e.getSource();
475       if (source instanceof NodeLabel){
476         NodeLabel srcLabel = (NodeLabel) source;
477         NodeRealizer realizer = view.getGraph2D().getRealizer(srcLabel.getNode());
478         if(ErdRealizerFactory.isBigEntityRealizer(realizer)
479             || ErdRealizerFactory.isSmallEntityRealizer(realizer)){
480           double newHeight = 0;
481           double newWidth = 0;
482           for (int i=0; i < realizer.labelCount(); i++){
483             newHeight += realizer.getLabel(i).getBox().getHeight();
484             newWidth = Math.max(newWidth, realizer.getLabel(i).getBox().getWidth());
485           }
486           if(newHeight > realizer.getHeight()) {
487             realizer.setHeight(newHeight + 15);
488           }
489           if(newWidth > realizer.getWidth()) {
490             realizer.setWidth(newWidth + 15);
491           }
492         }
493       }
494     }
495   }
496 }
497