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.uml;
29  
30  import demo.view.DemoBase;
31  import y.base.DataProvider;
32  import y.base.Edge;
33  import y.io.GraphMLIOHandler;
34  import y.io.graphml.graph2d.Graph2DGraphMLHandler;
35  import y.layout.LayoutOrientation;
36  import y.layout.Layouter;
37  import y.layout.PortConstraint;
38  import y.layout.PortConstraintKeys;
39  import y.layout.orthogonal.DirectedOrthogonalLayouter;
40  import y.layout.router.polyline.EdgeRouter;
41  import y.util.DataProviderAdapter;
42  import y.view.EdgeRealizer;
43  import y.view.EditMode;
44  import y.view.Graph2D;
45  import y.view.Graph2DLayoutExecutor;
46  import y.view.TooltipMode;
47  import y.view.YLabel;
48  
49  import javax.swing.AbstractAction;
50  import javax.swing.Action;
51  import javax.swing.JToolBar;
52  import java.awt.EventQueue;
53  import java.awt.RenderingHints;
54  import java.awt.event.ActionEvent;
55  import java.beans.PropertyChangeEvent;
56  import java.beans.PropertyChangeListener;
57  import java.util.Locale;
58  
59  /**
60   * This demo allows you to visualize and edit UML class diagrams. It shows how to
61   * <ul>
62   *  <li>create a configuration for {@link y.view.hierarchy.GroupNodeRealizer} that provides interactive buttons to show
63   *  or hide parts of the node and to add or remove labels.</li>
64   *  <li>load and save the customized node realizer.</li>
65   *  <li>use {@link y.view.Drawable}s to display interactive buttons that invoke edge creation.</li>
66   *  <li>animate the appearance and disappearance of the buttons.</li>
67   *  <li>write a customized {@link EditMode} that handles mouse interactions with buttons.</li>
68   *  <li>add a layout action that calculates a layout well-suited for UML diagrams.</li>
69   *  <li>route edges incrementally after graph changes.</li>
70   * </ul>
71   */
72  public class UmlDemo extends DemoBase {
73    private static final double PAINT_DETAIL_THRESHOLD = 0.4;
74    private static final double MAX_ZOOM = 4.0;
75    private static final double MIN_ZOOM = 0.05;
76  
77    private Layouter layouter;
78    private boolean fractionMetricsEnabled;
79  
80    public UmlDemo() {
81      this(null);
82    }
83  
84    public UmlDemo(String helpFilePath) {
85      addHelpPane(helpFilePath);
86  
87      configureLabelRendering();
88      configureZoomThreshold();
89      layouter = createLayouter();
90  
91      loadGraph("resource/shopping.graphml");
92      addPortConstraints(view.getGraph2D());
93    }
94  
95    /**
96     * Adds {@link PortConstraint}s to the edges which are inheritance relations. Inheritance relations always start on
97     * top of the source node and end at the bottom of the target node. This behaviour is enforced by adding these
98     * port constraints.
99     */
100   private void addPortConstraints(final Graph2D graph) {
101     // add source port constraints to the inheritance edges so they have to connect to the north of a node
102     final DataProvider sourcePorts = new DataProviderAdapter() {
103       public Object get(Object dataHolder) {
104         final Edge edge = (Edge) dataHolder;
105         final EdgeRealizer er = graph.getRealizer(edge);
106         return UmlRealizerFactory.isInheritance(er) ? PortConstraint.create(PortConstraint.NORTH) : null;
107       }
108     };
109     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, sourcePorts);
110 
111     // add target port constraints to the inheritance edges so they have to connect to the south of a node
112     final DataProvider targetPorts = new DataProviderAdapter() {
113       public Object get(Object dataHolder) {
114         final Edge edge = (Edge) dataHolder;
115         final EdgeRealizer er = graph.getRealizer(edge);
116         return UmlRealizerFactory.isInheritance(er) ? PortConstraint.create(PortConstraint.SOUTH) : null;
117       }
118     };
119     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, targetPorts);
120   }
121 
122   /**
123    * Ensures that text always fits into label bounds independent of zoom level. Stores the value to be able to reset it
124    * when running the demo in the DemoBrowser, so this setting cannot effect other demos.
125    */
126   private void configureLabelRendering() {
127     fractionMetricsEnabled = YLabel.isFractionMetricsForSizeCalculationEnabled();
128     YLabel.setFractionMetricsForSizeCalculationEnabled(true);
129     view.getRenderingHints().put(
130         RenderingHints.KEY_FRACTIONALMETRICS,
131         RenderingHints.VALUE_FRACTIONALMETRICS_ON);
132   }
133 
134   /**
135    * Cleans up.
136    * This method is called by the demo browser when the demo is stopped or another demo starts.
137    */
138   public void dispose() {
139     YLabel.setFractionMetricsForSizeCalculationEnabled(fractionMetricsEnabled);
140   }
141 
142   private void configureZoomThreshold() {
143     // set threshold for sloppy painting
144     view.setPaintDetailThreshold(PAINT_DETAIL_THRESHOLD);
145     // limit zooming in and out
146     view.getCanvasComponent().addPropertyChangeListener(
147         new PropertyChangeListener() {
148           public void propertyChange(PropertyChangeEvent evt) {
149             if ("Zoom".equals(evt.getPropertyName())) {
150               final double zoom = ((Double) evt.getNewValue()).doubleValue();
151               if (zoom > MAX_ZOOM) {
152                 view.setZoom(MAX_ZOOM);
153               } else if (zoom < MIN_ZOOM) {
154                 view.setZoom(MIN_ZOOM);
155               }
156             }
157           }
158         });
159   }
160 
161   /**
162    * Creates the layouter for UML layout.
163    */
164   private Layouter createLayouter() {
165     // create a directed orthogonal layouter with reversed layout orientation to place the target nodes of the directed
166     // edges above their source nodes
167     final DirectedOrthogonalLayouter dol = new DirectedOrthogonalLayouter();
168     dol.setLayoutOrientation(LayoutOrientation.BOTTOM_TO_TOP);
169 
170     // mark all inheritance edges (generalization, realization) as directed so their target nodes
171     // will be placed above their source nodes
172     // all other edges are treated as undirected
173     final Graph2D graph = view.getGraph2D();
174     final DataProvider directedEdges = new DataProviderAdapter() {
175       public boolean getBool(Object dataHolder) {
176         final Edge edge = (Edge) dataHolder;
177         return UmlRealizerFactory.isInheritance(graph.getRealizer(edge));
178       }
179     };
180     graph.addDataProvider(DirectedOrthogonalLayouter.DIRECTED_EDGE_DPKEY, directedEdges);
181 
182     // combine all edges with a white delta as target arrow (generalization, realization) in edge groups according to
183     // their line type
184     // do not group the other edges
185     final DataProvider targetGroups = new DataProviderAdapter() {
186       public Object get(Object dataHolder) {
187         final Edge edge = (Edge) dataHolder;
188         final EdgeRealizer er = graph.getRealizer(edge);
189         return UmlRealizerFactory.isRealization(er) ? er.getLineType() : null;
190       }
191     };
192     graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, targetGroups);
193 
194     return dol;
195   }
196 
197   /**
198    * Overwritten to set the UML graph elements as default.
199    */
200   protected void configureDefaultRealizers() {
201     final Graph2D graph = view.getGraph2D();
202     graph.setDefaultNodeRealizer(UmlRealizerFactory.createClassRealizer());
203     graph.setDefaultEdgeRealizer(UmlRealizerFactory.createAssociationRealizer());
204   }
205 
206   /**
207    * Overwritten to create and configure an {@link EditMode} that is customized for this demo.
208    */
209   protected EditMode createEditMode() {
210     final UmlEditMode editMode = new UmlEditMode(view, new EdgeRouter());
211     editMode.getMouseInputMode().setNodeSearchingEnabled(true);
212     editMode.setSnappingEnabled(true);
213     return editMode;
214   }
215 
216   /**
217    * Registers customized {@link y.view.ViewMode}s that handles clicks on labels.
218    */
219   protected void registerViewModes() {
220     super.registerViewModes();
221     view.addViewMode(new UmlClassLabelEditMode());
222   }
223 
224   /**
225    * Overwritten to add a layout button to the default toolbar.
226    */
227   protected JToolBar createToolBar() {
228     final JToolBar toolBar = super.createToolBar();
229     toolBar.addSeparator();
230 
231     final AbstractAction layoutAction = new AbstractAction("Layout") {
232       public void actionPerformed(ActionEvent e) {
233         runLayout();
234       }
235     };
236     layoutAction.putValue(Action.SHORT_DESCRIPTION, "Layout");
237     layoutAction.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);
238     toolBar.add(createActionControl(layoutAction, true));
239 
240     return toolBar;
241   }
242 
243   /**
244    * Overwritten to add (de-)serialization handling of UML class nodes.
245    */
246   protected GraphMLIOHandler createGraphMLIOHandler() {
247     final GraphMLIOHandler graphMLIOHandler = super.createGraphMLIOHandler();
248     final Graph2DGraphMLHandler graphMLHandler = graphMLIOHandler.getGraphMLHandler();
249     final UmlClassModelIOHandler modelHandler = new UmlClassModelIOHandler();
250     graphMLHandler.addSerializationHandler(modelHandler);
251     graphMLHandler.addDeserializationHandler(modelHandler);
252     return graphMLIOHandler;
253   }
254 
255   /**
256    * Calculates a layout for the current graph.
257    */
258   private void runLayout() {
259     new Graph2DLayoutExecutor().doLayout(view, layouter);
260   }
261 
262   /**
263    * Overwritten to turn off tooltips for graph elements.
264    */
265   protected TooltipMode createTooltipMode() {
266     return null;
267   }
268 
269   /**
270    * Starts the {@link UmlDemo}.
271    */
272   public static void main(String[] args) {
273     EventQueue.invokeLater(
274         new Runnable() {
275           public void run() {
276             Locale.setDefault(Locale.ENGLISH);
277             initLnF();
278             final UmlDemo demo = new UmlDemo("resource/umlhelp.html");
279             demo.start("UML Diagram Demo");
280           }
281         });
282   }
283 }
284