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