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.viewmode;
15  
16  import demo.view.DemoBase;
17  import y.base.Edge;
18  import y.base.Node;
19  import y.base.YCursor;
20  import y.base.YList;
21  import y.geom.OrientedRectangle;
22  import y.geom.YPoint;
23  import y.geom.YRectangle;
24  import y.layout.LabelCandidate;
25  import y.view.Graph2D;
26  import y.view.HitInfo;
27  import y.view.HitInfoFactories;
28  import y.view.SmartEdgeLabelModel;
29  import y.view.SmartNodeLabelModel;
30  import y.option.OptionHandler;
31  import y.view.DefaultLabelConfiguration;
32  import y.view.EdgeLabel;
33  import y.view.EdgeRealizer;
34  import y.view.EditMode;
35  import y.view.LabelSnapContext;
36  import y.view.MoveLabelMode;
37  import y.view.NodeLabel;
38  import y.view.NodeRealizer;
39  import y.view.PopupMode;
40  import y.view.YLabel;
41  
42  import javax.swing.AbstractAction;
43  import javax.swing.JPopupMenu;
44  import java.awt.Color;
45  import java.awt.Dimension;
46  import java.awt.EventQueue;
47  import java.awt.RenderingHints;
48  import java.awt.event.ActionEvent;
49  import java.util.Locale;
50  import java.util.Map;
51  
52  
53  /**
54   * Demonstrates how to use {@link y.view.SmartNodeLabelModel} and
55   * {@link y.view.SmartEdgeLabelModel}.
56   * <p>
57   * Both models allow for labels to be placed freely at every position.
58   * By default, node labels using <code>SmartNodeLabelModel</code> snap to the
59   * borders and the center of the node as well as the node's other labels.
60   * Edge labels using <code>SmartEdgeLabelModel</code> snap to the edge itself as
61   * well as an imaginary snap segment to the left and an imaginary snap segment
62   * to the right of the edge.
63   * </p>
64   * <p>
65   * Snapping can be disabled by pressing the control key while dragging a label.
66   * </p>
67   * <p>
68   * Method {@link #createEditMode()} shows how to configure {@link MoveLabelMode}
69   * for snapping.
70   * </p>
71   * @see #createEditMode()
72   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/mvc_controller.html#cls_MoveLabelMode">Section User Interaction</a> in the yFiles for Java Developer's Guide
73   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/realizer_related.html#node_label_model">Section Realizer-Related Features: Node Label Models</a> in the yFiles for Java Developer's Guide
74   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/realizer_related.html#edge_label_model">Section Realizer-Related Features: Edge Label Models</a> in the yFiles for Java Developer's Guide
75   */
76  public class SmartLabelModelDemo extends DemoBase {
77    private static final Color LABEL_LINE_COLOR = new Color(153, 204, 255, 255);
78    private static final Color LABEL_BACKGROUND_COLOR = Color.WHITE;
79    private static final String AUTO_FLIPPING_CONFIG = "AutoFlipConfig";
80    private boolean fractionMetricsForSizeCalculationEnabled;
81  
82    public SmartLabelModelDemo() {
83      this(null);
84    }
85  
86    public SmartLabelModelDemo( final String helpFilePath ) {
87      loadInitialGraph();
88      addHelpPane(helpFilePath);
89    }
90  
91    /**
92     * Loads a sample graph.
93     */
94    protected void loadInitialGraph() {
95      loadGraph("resource/SmartLabelModelDemo.graphml");
96    }
97  
98    protected void initialize() {
99      super.initialize();
100     view.setPreferredSize(new Dimension(700, 650));
101     view.setHitInfoFactory(new HitInfoFactories.DefaultHitInfoFactory(view) {
102       public HitInfo createHitInfo(
103               final double x, final double y,
104               final int types,
105               final boolean firstHitOnly
106       ) {
107         return createHitInfo(getView(), getGraph(), x, y, types, firstHitOnly);
108       }
109     });
110 
111     // Ensures that text always fits into label bounds independent of zoom level.
112     view.getRenderingHints().put(
113         RenderingHints.KEY_FRACTIONALMETRICS,
114         RenderingHints.VALUE_FRACTIONALMETRICS_ON);
115     // Store value to be able to reset it when running the demo in the DemoBrowser,
116     // so this setting cannot effect other demos.
117     fractionMetricsForSizeCalculationEnabled = YLabel.isFractionMetricsForSizeCalculationEnabled();
118     YLabel.setFractionMetricsForSizeCalculationEnabled(true);
119   }
120 
121   /**
122    * Cleans up.
123    * This method is called by the demo browser when the demo is stopped or another demo starts.
124    */
125   public void dispose() {
126     YLabel.setFractionMetricsForSizeCalculationEnabled(fractionMetricsForSizeCalculationEnabled);
127   }
128 
129   /**
130    * Configures the default realizers to use labels with
131    * {@link y.view.SmartNodeLabelModel} and
132    * {@link y.view.SmartEdgeLabelModel} as appropriate.
133    */
134   protected void configureDefaultRealizers() {
135     super.configureDefaultRealizers();
136 
137     NodeRealizer nodeRealizer = view.getGraph2D().getDefaultNodeRealizer();
138     nodeRealizer.setSize(200, 100);
139     final NodeLabel nodeLabel = nodeRealizer.getLabel();
140     nodeLabel.setText("Smart Node Label");
141     nodeLabel.setLineColor(LABEL_LINE_COLOR);
142     nodeLabel.setBackgroundColor(LABEL_BACKGROUND_COLOR);
143     final SmartNodeLabelModel nodeLabelModel = new SmartNodeLabelModel();
144     nodeLabel.setLabelModel(nodeLabelModel);
145     nodeLabel.setModelParameter(nodeLabelModel.getDefaultParameter());
146 
147     final YLabel.Factory factory = EdgeLabel.getFactory();
148     final Map defaultConfigImplementationsMap = factory.createDefaultConfigurationMap();
149     DefaultLabelConfiguration customLabelConfig = new DefaultLabelConfiguration();
150     customLabelConfig.setAutoFlippingEnabled(true);
151     defaultConfigImplementationsMap.put(YLabel.Painter.class, customLabelConfig);
152     defaultConfigImplementationsMap.put(YLabel.Layout.class, customLabelConfig);
153     defaultConfigImplementationsMap.put(YLabel.BoundsProvider.class, customLabelConfig);
154     factory.addConfiguration(AUTO_FLIPPING_CONFIG, defaultConfigImplementationsMap);
155 
156     EdgeRealizer edgeRealizer = view.getGraph2D().getDefaultEdgeRealizer();
157     final EdgeLabel edgeLabel = edgeRealizer.getLabel();
158     edgeLabel.setText("Smart Edge Label");
159     edgeLabel.setLineColor(LABEL_LINE_COLOR);
160     edgeLabel.setBackgroundColor(LABEL_BACKGROUND_COLOR);
161     final SmartEdgeLabelModel edgeLabelModel = new SmartEdgeLabelModel();
162     edgeLabel.setLabelModel(edgeLabelModel);
163     edgeLabel.setModelParameter(edgeLabelModel.createDiscreteModelParameter(SmartEdgeLabelModel.POSITION_CENTER));
164     edgeLabel.setConfiguration(AUTO_FLIPPING_CONFIG);
165   }
166 
167   /**
168    * Creates a edit mode that is configured to use snapping for label movements
169    * and to provide custom context menus for edge, nodes, and their labels.
170    * @return an appropriately configured {@link EditMode} instance.
171    */
172   protected EditMode createEditMode() {
173     final EditMode mode = super.createEditMode();
174     mode.setCyclicSelectionEnabled(true);   // To be able to select labels in front of a node
175     mode.setPopupMode(new DemoPopupMode());
176     final SnappingConfiguration snappingConfiguration = DemoBase.createDefaultSnappingConfiguration();
177     snappingConfiguration.configureView(view);
178     snappingConfiguration.configureEditMode(mode);
179 
180     // configure snap context for label snapping
181     final LabelSnapContext snapContext = new LabelSnapContext(view);
182     ((MoveLabelMode) mode.getMoveLabelMode()).setSnapContext(snapContext);
183     snapContext.setNodeLabelSnapDistance(10);
184     snapContext.setEdgeLabelSnapDistance(30);
185     snapContext.setSnapLineColor(Color.BLUE);
186 
187     return mode;
188   }
189 
190   /**
191    * Launches <code>SmartLabelModelDemo</code>.
192    * @param args not used
193    */
194   public static void main( final String[] args ) {
195     EventQueue.invokeLater(new Runnable() {
196       public void run() {
197         Locale.setDefault(Locale.ENGLISH);
198         initLnF();
199         (new SmartLabelModelDemo("resource/smartlabelmodeldemo.html")).start();
200       }
201     });
202   }
203 
204 
205   /**
206    * A PopupMode for adding or editing node labels and edge labels.
207    */
208   private static final class DemoPopupMode extends PopupMode {
209     /**
210      * Creates a context menu for nodes that allows for adding labels.
211      * @param node the node whose context actions have to be displayed.
212      * @return a context menu for nodes that allows for adding labels.
213      */
214     public JPopupMenu getNodePopup( final Node node ) {
215       JPopupMenu pm = new JPopupMenu();
216       pm.add(new AbstractAction("Add Label") {
217         public void actionPerformed(ActionEvent e) {
218           if (node == null) {
219             return;
220           }
221 
222           final SmartNodeLabelModel nodeLabelModel = new SmartNodeLabelModel();
223           final NodeLabel label = new NodeLabel("Smart Node Label");
224           label.setLineColor(LABEL_LINE_COLOR);
225           label.setBackgroundColor(LABEL_BACKGROUND_COLOR);
226           label.setLabelModel(nodeLabelModel);
227           label.setModelParameter(nodeLabelModel.getDefaultParameter());
228           getGraph2D().getRealizer(node).addLabel(label);
229           getGraph2D().updateViews();
230         }
231       });
232       return pm;
233     }
234 
235     /**
236      * Creates a context menu for node labels that allows for editing label
237      * properties such as text, font size, and rotation.
238      * @param label the label whose context actions have to be displayed.
239      * @return a context menu for node labels that allows for editing label
240      * properties.
241      */
242     public JPopupMenu getNodeLabelPopup( final NodeLabel label ) {
243       JPopupMenu pm = new JPopupMenu();
244       pm.add(new AbstractAction("Edit Label") {
245         public void actionPerformed(ActionEvent e) {
246           if (label == null) {
247             return;
248           }
249 
250           LabelOptionHandler oh = new LabelOptionHandler(label, getGraph2D());
251           oh.showEditor(null, OptionHandler.OK_CANCEL_BUTTONS);
252         }
253       });
254       return pm;
255     }
256 
257     /**
258      * Creates a context menu for edges that allows for adding labels.
259      * @param edge the edge whose context actions have to be displayed.
260      * @return a context menu for edges that allows for adding labels.
261      */
262     public JPopupMenu getEdgePopup( final Edge edge ) {
263       JPopupMenu pm = new JPopupMenu();
264       pm.add(new AbstractAction("Add Label") {
265         public void actionPerformed(ActionEvent e) {
266           if (edge == null) {
267             return;
268           }
269 
270           final SmartEdgeLabelModel edgeLabelModel = new SmartEdgeLabelModel();
271           final EdgeLabel label = new EdgeLabel("Smart Edge Label");
272           label.setConfiguration(AUTO_FLIPPING_CONFIG);
273           label.setLineColor(LABEL_LINE_COLOR);
274           label.setBackgroundColor(LABEL_BACKGROUND_COLOR);
275           label.setLabelModel(edgeLabelModel);
276           label.setModelParameter(edgeLabelModel.getDefaultParameter());
277           getGraph2D().getRealizer(edge).addLabel(label);
278 
279           final Object parameter = calculateBestParameter(edge, label);
280           if (parameter != null) {
281             label.setModelParameter(parameter);
282           }
283           getGraph2D().updateViews();
284         }
285       });
286       return pm;
287     }
288 
289     /**
290      * Creates a context menu for edge labels that allows for editing label
291      * properties such as text, font size, and rotation.
292      * @param label the label whose context actions have to be displayed.
293      * @return a context menu for edge labels that allows for editing label
294      * properties.
295      */
296     public JPopupMenu getEdgeLabelPopup( final EdgeLabel label ) {
297       JPopupMenu pm = new JPopupMenu();
298       pm.add(new AbstractAction("Edit Label") {
299         public void actionPerformed(ActionEvent e) {
300           if (label == null) {
301             return;
302           }
303 
304           LabelOptionHandler oh = new LabelOptionHandler(label, getGraph2D());
305           oh.showEditor(null, OptionHandler.OK_CANCEL_BUTTONS);
306         }
307       });
308       return pm;
309     }
310 
311     /**
312      * Determines a model parameter that represents a label position that is not
313      * already occupied by another label and lies near the current mouse
314      * position.
315      * @param e The edge where the label is set
316      * @param label The current label layout
317      * @return A model parameter with the new label position
318      */
319     private Object calculateBestParameter( final Edge e, final EdgeLabel label ) {
320       if (lastPressEvent == null) {
321         return null;
322       }
323 
324       //identify occupied boxes
325       YList occupiedRectList = new YList();
326       Graph2D graph = view.getGraph2D();
327       EdgeRealizer er = graph.getRealizer(e);
328       for (int i = 0, n = er.labelCount(); i < n; ++i) {
329         EdgeLabel el = er.getLabel(i);
330         if (el != label) {
331           occupiedRectList.add(el.getOrientedBox());
332         }
333       }
334 
335       //find label candidates with non-occupied boxes and low distance to point "mousePressedEventLocation"
336       final YPoint mousePressedEventLocation = new YPoint(
337               view.toWorldCoordX(lastPressEvent.getX()),
338               view.toWorldCoordY(lastPressEvent.getY()));
339       double minDist = Double.MAX_VALUE;
340       LabelCandidate chosen = null;
341       YList candidates = label.getLabelModel().getLabelCandidates(
342               label, er, er.getSourceRealizer(), er.getTargetRealizer());
343       for (YCursor cu = candidates.cursor(); cu.ok(); cu.next()) {
344         LabelCandidate lc = (LabelCandidate) cu.current();
345 
346         boolean isOccupied = false;
347         YRectangle bBox = lc.getBox().getBoundingBox();
348         for (YCursor cur = occupiedRectList.cursor(); cur.ok() && !isOccupied; cur.next()) {
349           if (OrientedRectangle.intersects((OrientedRectangle) cur.current(), bBox, 0)) {
350             isOccupied = true;
351             break;
352           }
353         }
354 
355         if (!isOccupied) {
356           double dist = YPoint.distance(mousePressedEventLocation, lc.getBox().getCenter());
357           if (dist < minDist) {
358             minDist = dist;
359             chosen = lc;
360           }
361         }
362       }
363 
364       return chosen == null ? null : chosen.getModelParameter();
365     }
366   }
367 
368   /**
369    * An option handler for editing node or edge labels.
370    * It is possible to edit the label text, the font size and
371    * the rotation angle.
372    */
373   private static final class LabelOptionHandler extends OptionHandler {
374     static final String LABEL_TEXT = "Label Text";
375     static final String FONT_SIZE = "Font Size";
376     static final String ROTATION_ANGLE = "Rotation Angle";
377     static final String AUTO_ROTATION = "Auto Rotation";
378 
379     private final YLabel label;
380     private final Graph2D graph;
381     private final boolean isEdgeLabel;
382 
383     /**
384      * Initializes a new <code>LabelOptionHandler</code> for the specified
385      * label and view.
386      * @param label the label to edit.
387      * @param graph the label's associated graph.
388      */
389     LabelOptionHandler( final YLabel label, final Graph2D graph ) {
390       super("Label Options");
391       this.label = label;
392       this.graph = graph;
393       this.isEdgeLabel = label instanceof EdgeLabel;
394       addString(LABEL_TEXT, label.getText(), 5);
395       addInt(FONT_SIZE, label.getFontSize());
396       int value = 0;
397       if (isEdgeLabel) {
398         final EdgeLabel el = (EdgeLabel) label;
399         if (el.getLabelModel() instanceof SmartEdgeLabelModel) {
400           // OrientedRectangle interprets angles in counter-clockwise direction
401           // but label models and their parameters interpret angles in
402           // clockwise direction therefore the parameter angle has to be
403           // translated from clockwise to counter-clockwise here
404           value = toDegrees(2*Math.PI - SmartEdgeLabelModel.getAngle(el.getModelParameter()));
405         }
406       } else {
407         value = toDegrees(label.getOrientedBox().getAngle());
408       }
409       while (value < 0) {
410         value += 360;
411       }
412       addInt(ROTATION_ANGLE, value, 0, 360);
413       if (isEdgeLabel) {
414         final EdgeLabel el = (EdgeLabel) label;
415         if (el.getLabelModel() instanceof SmartEdgeLabelModel) {
416           SmartEdgeLabelModel model = (SmartEdgeLabelModel) el.getLabelModel();
417           addBool(AUTO_ROTATION, model.isAutoRotationEnabled());
418         }
419       }
420     }
421 
422     public void commitValues() {
423       super.commitValues();
424       label.setText(getString(LABEL_TEXT));
425       label.setFontSize(getInt(FONT_SIZE));
426 
427       final OrientedRectangle box = label.getOrientedBox();
428       final YPoint center = box.getCenter();
429       box.setAngle(Math.toRadians(getInt(ROTATION_ANGLE)));
430       box.setCenter(center);
431 
432       if (isEdgeLabel && ((EdgeLabel) label).getLabelModel() instanceof SmartEdgeLabelModel) {
433         final EdgeLabel el = (EdgeLabel) label;
434         final EdgeRealizer er = el.getRealizer();
435         final SmartEdgeLabelModel model = (SmartEdgeLabelModel) el.getLabelModel();
436         model.setAutoRotationEnabled(getBool(AUTO_ROTATION));
437         el.setModelParameter(model.createRelativeModelParameter(
438             box,
439             er,
440             er.getSourceRealizer(),
441             er.getTargetRealizer()));
442       } else {
443         label.setModelParameter(label.getBestModelParameterForBounds(box));
444       }
445 
446       graph.updateViews();
447     }
448 
449     private static int toDegrees( final double angle ) {
450       return ((int) Math.rint(Math.toDegrees(angle))) % 360;
451     }
452   }
453 }