| EntityRelationshipDemo.java |
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