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.layout.labeling;
29  
30  import y.base.Edge;
31  import y.base.EdgeCursor;
32  import y.base.YCursor;
33  import y.layout.LabelLayoutConstants;
34  import y.layout.PreferredPlacementDescriptor;
35  import y.option.MappedListCellRenderer;
36  import y.option.OptionHandler;
37  import y.view.EdgeLabel;
38  import y.view.EdgeRealizer;
39  import y.view.Graph2D;
40  import y.view.YLabel;
41  
42  import java.util.ArrayList;
43  import java.util.Iterator;
44  import java.util.LinkedHashMap;
45  import java.util.List;
46  import java.util.Map;
47  
48  /**
49   * An OptionHandler that is used manipulate the values of the PreferredPlacementDescriptor.
50   */
51  public class PreferredPlacementOptionHandler extends OptionHandler {
52    private static final String EDGE_LABEL_PROPERTIES = "Edge Label Properties";
53    private static final String ITEM_TEXT = "Text";
54    private static final String ITEM_PLACEMENT = "Placement Along the Edge";
55    private static final String ITEM_SIDE = "Side of the Edge";
56    private static final String ITEM_SIDE_REFERENCE = "Side Reference";
57    private static final String ITEM_ANGLE = "Angle (in Degrees)";
58    private static final String ITEM_ANGLE_REFERENCE = "Angle Reference";
59    private static final String ITEM_ANGLE_ROTATION_ON_RIGHT_SIDE = "Angle Rotation on Right Side";
60    // u00b0 is the unicode escape sequence for the degrees symbol
61    private static final String ITEM_ANGLE_OFFSET_ON_RIGHT_SIDE = "Add 180\u00b0 on Right Side";
62    private static final String ITEM_DISTANCE_TO_EDGE = "Distance to Edge";
63  
64    private static final Byte DEFAULT_PLACE = new Byte(
65      (byte) LabelLayoutConstants.PLACE_ANYWHERE);
66    private static final Byte DEFAULT_SIDE = new Byte(
67      (byte) LabelLayoutConstants.PLACE_ANYWHERE);
68    private static final Byte DEFAULT_SIDE_REFERENCE = new Byte(
69      (byte) (PreferredPlacementDescriptor.SIDE_IS_RELATIVE_TO_EDGE_FLOW |
70              PreferredPlacementDescriptor.SIDE_IS_ABSOLUTE_WITH_LEFT_IN_NORTH |
71              PreferredPlacementDescriptor.SIDE_IS_ABSOLUTE_WITH_RIGHT_IN_NORTH));
72    private static final Byte DEFAULT_ANGLE_REFERENCE = new Byte(
73      (byte) (PreferredPlacementDescriptor.ANGLE_IS_ABSOLUTE |
74              PreferredPlacementDescriptor.ANGLE_IS_RELATIVE_TO_EDGE_FLOW));
75    private static final Byte DEFAULT_ANGLE_ROTATION_ON_RIGHT_SIDE = new Byte(
76      (byte) (PreferredPlacementDescriptor.ANGLE_ON_RIGHT_SIDE_CO_ROTATING |
77              PreferredPlacementDescriptor.ANGLE_ON_RIGHT_SIDE_COUNTER_ROTATING));
78    private static final Double DEFAULT_ANGLE = new Double(0);
79    private static final Double DEFAULT_DISTANCE_TO_EDGE = new Double(0);
80    private static final Boolean DEFAULT_ANGLE_OFFSET_ON_RIGHT_SIDE = Boolean.FALSE;
81  
82    /**
83     * Creates a new option handler for {@link PreferredPlacementDescriptor} settings.
84     */
85    public PreferredPlacementOptionHandler() {
86      super(EDGE_LABEL_PROPERTIES);
87  
88      addString(ITEM_TEXT, "", 2);
89  
90      final Map placementMap = createPlacementsToStringMap();
91      addEnum(
92        ITEM_PLACEMENT,
93        placementMap.keySet().toArray(),
94        DEFAULT_PLACE,
95        new MappedListCellRenderer(placementMap));
96  
97      final Map sideMap = createSidesToStringMap();
98      addEnum(
99        ITEM_SIDE,
100       sideMap.keySet().toArray(),
101       DEFAULT_SIDE,
102       new MappedListCellRenderer(sideMap));
103 
104     final Map sideInterpretationMap = createSideReferencesToStringMap();
105     addEnum(
106       ITEM_SIDE_REFERENCE,
107       sideInterpretationMap.keySet().toArray(),
108       DEFAULT_SIDE_REFERENCE,
109       new MappedListCellRenderer(sideInterpretationMap));
110 
111     addDouble(ITEM_ANGLE, DEFAULT_ANGLE.doubleValue());
112 
113     final Map angleReferenceMap = createAngleReferencesToStringMap();
114     addEnum(
115       ITEM_ANGLE_REFERENCE,
116       angleReferenceMap.keySet().toArray(),
117       DEFAULT_ANGLE_REFERENCE,
118       new MappedListCellRenderer(angleReferenceMap));
119 
120     final Map angleRotationOnRightSideMap = createAngleRotationsOnRightSideToStringMap();
121     addEnum(
122       ITEM_ANGLE_ROTATION_ON_RIGHT_SIDE,
123       angleRotationOnRightSideMap.keySet().toArray(),
124       DEFAULT_ANGLE_ROTATION_ON_RIGHT_SIDE,
125       new MappedListCellRenderer(angleRotationOnRightSideMap));
126 
127     addBool(ITEM_ANGLE_OFFSET_ON_RIGHT_SIDE, DEFAULT_ANGLE_OFFSET_ON_RIGHT_SIDE.booleanValue());
128 
129     addDouble(ITEM_DISTANCE_TO_EDGE, DEFAULT_DISTANCE_TO_EDGE.doubleValue());
130   }
131 
132   /**
133    * Retrieves the values from the edge labels of the given graph and stores them in the respective option items.
134    *
135    * @param graph graph whose edge labels to store the values from
136    */
137   public void updateValues(final Graph2D graph) {
138     final Iterator labelIterator = getEdgeLabels(graph).iterator();
139     if (!labelIterator.hasNext()) {
140       return;
141     }
142 
143     EdgeLabel edgeLabel = (EdgeLabel) labelIterator.next();
144     PreferredPlacementDescriptor descriptor = edgeLabel.getPreferredPlacementDescriptor();
145 
146     // Get the initial values from the first selected node.
147     final String text = edgeLabel.getText();
148     boolean sameTexts = true;
149     final byte placement = descriptor.getPlaceAlongEdge();
150     boolean samePlacements = true;
151     final byte side = descriptor.getSideOfEdge();
152     boolean sameSides = true;
153     final byte sideReference = descriptor.getSideReference();
154     boolean sameSideReferences = true;
155     final double angle = descriptor.getAngle();
156     boolean sameAngles = true;
157     final byte angleReference = descriptor.getAngleReference();
158     boolean sameAngleReferences = true;
159     final byte angleRotationOnRightSide = descriptor.getAngleRotationOnRightSide();
160     boolean sameAngleRotations = true;
161     final boolean hasAngleOffset = descriptor.isAngleOffsetOnRightSide180();
162     boolean sameAngleOffsets = true;
163     final double distance = descriptor.getDistanceToEdge();
164     boolean sameDistances = true;
165 
166     // Get all further values from the remaining set of selected edge labels.
167     while (labelIterator.hasNext()) {
168       edgeLabel = (EdgeLabel) labelIterator.next();
169       descriptor = edgeLabel.getPreferredPlacementDescriptor();
170 
171       if (sameTexts && !text.equals(edgeLabel.getText())) {
172         sameTexts = false;
173       }
174       if (samePlacements && placement != descriptor.getPlaceAlongEdge()) {
175         samePlacements = false;
176       }
177       if (sameSides && side != descriptor.getSideOfEdge()) {
178         sameSides = false;
179       }
180       if (sameSideReferences && sideReference != descriptor.getSideReference()) {
181         sameSideReferences = false;
182       }
183       if (sameAngles && Double.compare(angle, descriptor.getAngle()) != 0) {
184         sameAngles = false;
185       }
186       if (sameAngleReferences &&
187         angleReference != descriptor.getAngleReference()) {
188         sameAngleReferences = false;
189       }
190       if (sameAngleRotations &&
191         angleRotationOnRightSide != descriptor.getAngleRotationOnRightSide()) {
192         sameAngleRotations = false;
193       }
194       if (sameAngleOffsets && hasAngleOffset != descriptor.isAngleOffsetOnRightSide180()) {
195         sameAngleOffsets = false;
196       }
197       if (sameDistances && Double.compare(distance, descriptor.getDistanceToEdge()) != 0) {
198         sameDistances = false;
199       }
200 
201       if (!(sameTexts | samePlacements | sameSides | sameSideReferences | sameAngles | sameAngleReferences
202         | sameAngleRotations | sameDistances)) {
203         break;
204       }
205     }
206 
207     // If, for a single property, there are multiple values present in the set of selected edge labels, the
208     // respective option item is set to indicate an "undefined value" state.
209     // Note that property "valueUndefined" for an option item is set *after* its value has actually been modified!
210     set(ITEM_TEXT, sameTexts ? text : null);
211     getItem(ITEM_TEXT).setValueUndefined(!sameTexts);
212 
213     set(ITEM_PLACEMENT, samePlacements ? new Byte(placement) : DEFAULT_PLACE);
214     getItem(ITEM_PLACEMENT).setValueUndefined(!samePlacements);
215 
216     set(ITEM_SIDE, sameSides ? new Byte(side) : DEFAULT_SIDE);
217     getItem(ITEM_SIDE).setValueUndefined(!sameSides);
218 
219     set(ITEM_SIDE_REFERENCE, sameSideReferences ? new Byte(sideReference) : DEFAULT_SIDE_REFERENCE);
220     getItem(ITEM_SIDE_REFERENCE).setValueUndefined(!sameSideReferences);
221 
222     set(ITEM_ANGLE, sameAngles ? new Double(Math.toDegrees(angle)) : new Double(-1));
223     getItem(ITEM_ANGLE).setValueUndefined(!sameAngles);
224 
225     set(ITEM_ANGLE_REFERENCE, sameAngleReferences ? new Byte(angleReference) : DEFAULT_ANGLE_REFERENCE);
226     getItem(ITEM_ANGLE_REFERENCE).setValueUndefined(!sameAngleReferences);
227 
228     set(ITEM_ANGLE_ROTATION_ON_RIGHT_SIDE, sameAngleRotations ? new Byte(angleRotationOnRightSide) :
229       DEFAULT_ANGLE_ROTATION_ON_RIGHT_SIDE);
230     getItem(ITEM_ANGLE_ROTATION_ON_RIGHT_SIDE).setValueUndefined(!sameAngleRotations);
231 
232     set(ITEM_ANGLE_OFFSET_ON_RIGHT_SIDE, sameAngleOffsets ? hasAngleOffset ? Boolean.TRUE : Boolean.FALSE :
233       DEFAULT_ANGLE_OFFSET_ON_RIGHT_SIDE);
234     getItem(ITEM_ANGLE_ROTATION_ON_RIGHT_SIDE).setValueUndefined(!sameAngleOffsets);
235 
236     set(ITEM_DISTANCE_TO_EDGE, sameDistances ? new Double(distance) : new Double(-1));
237     getItem(ITEM_DISTANCE_TO_EDGE).setValueUndefined(!sameDistances);
238   }
239 
240   /**
241    * Commits the stored edge label properties to the given edge labels.
242    *
243    * @param graph graph whose selected edge labels to commit the stored properties
244    */
245   public void commitValues(final Graph2D graph) {
246     for (Iterator labelIterator = getEdgeLabels(graph).iterator(); labelIterator.hasNext(); ) {
247       final EdgeLabel edgeLabel = (EdgeLabel) labelIterator.next();
248       final PreferredPlacementDescriptor prototype =
249               edgeLabel.getPreferredPlacementDescriptor();
250 
251       final PreferredPlacementDescriptor descriptor =
252               new PreferredPlacementDescriptor(prototype);
253       if (!getItem(ITEM_TEXT).isValueUndefined()) {
254         edgeLabel.setText(getString(ITEM_TEXT));
255       }
256       if (!getItem(ITEM_PLACEMENT).isValueUndefined()) {
257         descriptor.setPlaceAlongEdge(((Byte) get(ITEM_PLACEMENT)).byteValue());
258       }
259       if (!getItem(ITEM_SIDE).isValueUndefined()) {
260         descriptor.setSideOfEdge(((Byte) get(ITEM_SIDE)).byteValue());
261       }
262       if (!getItem(ITEM_SIDE_REFERENCE).isValueUndefined()) {
263         descriptor.setSideReference(((Byte) get(ITEM_SIDE_REFERENCE)).byteValue());
264       }
265       if (!getItem(ITEM_ANGLE).isValueUndefined()) {
266         descriptor.setAngle(Math.toRadians(getDouble(ITEM_ANGLE)));
267       }
268       if (!getItem(ITEM_ANGLE_REFERENCE).isValueUndefined()) {
269         descriptor.setAngleReference(((Byte) get(ITEM_ANGLE_REFERENCE)).byteValue());
270       }
271       if (!getItem(ITEM_ANGLE_ROTATION_ON_RIGHT_SIDE).isValueUndefined()) {
272         descriptor.setAngleRotationOnRightSide(((Byte) get(ITEM_ANGLE_ROTATION_ON_RIGHT_SIDE)).byteValue());
273       }
274       if (!getItem(ITEM_ANGLE_OFFSET_ON_RIGHT_SIDE).isValueUndefined()) {
275         final byte angleOffset = getBool(ITEM_ANGLE_OFFSET_ON_RIGHT_SIDE) ?
276           PreferredPlacementDescriptor.ANGLE_OFFSET_ON_RIGHT_SIDE_180 :
277           PreferredPlacementDescriptor.ANGLE_OFFSET_ON_RIGHT_SIDE_0;
278         descriptor.setAngleOffsetOnRightSide(angleOffset);
279       }
280       if (!getItem(ITEM_DISTANCE_TO_EDGE).isValueUndefined()) {
281         descriptor.setDistanceToEdge(getDouble(ITEM_DISTANCE_TO_EDGE));
282       }
283 
284       if (!descriptor.equals(prototype)) {
285         edgeLabel.setPreferredPlacementDescriptor(descriptor);
286       }
287     }
288   }
289 
290   /**
291    * Returns a list for all selected edge labels of the given graph. If there is no edge label selected, all edge labels
292    * are returned.
293    *
294    * @param graph the graph to return its edge labels
295    * @return a list of edge labels
296    */
297   private List getEdgeLabels(final Graph2D graph) {
298     List labels = getSelectedEdgeLabels(graph);
299     if (labels.isEmpty()) {
300       labels = getAllEdgeLabels(graph);
301     }
302     return labels;
303   }
304 
305   /**
306    * Returns a list for all selected edge labels of the given graph.
307    *
308    * @param graph graph to return its selected edge labels.
309    * @return a list of all selected edge labels.
310    */
311   private List getSelectedEdgeLabels(final Graph2D graph) {
312     final List selectedEdgeLabels = new ArrayList();
313     for (YCursor selectedLabels = graph.selectedLabels(); selectedLabels.ok(); selectedLabels.next()) {
314       final YLabel selectedLabel = (YLabel) selectedLabels.current();
315       if (selectedLabel instanceof EdgeLabel) {
316         selectedEdgeLabels.add(selectedLabel);
317       }
318     }
319     return selectedEdgeLabels;
320   }
321 
322   /**
323    * Returns a list for all edge labels of the given graph.
324    *
325    * @param graph graph to return its edge labels.
326    * @return a list for all edge labels.
327    */
328   private List getAllEdgeLabels(final Graph2D graph) {
329     final List result = new ArrayList();
330     for (EdgeCursor edgeCursor = graph.edges(); edgeCursor.ok(); edgeCursor.next()) {
331       final Edge edge = edgeCursor.edge();
332       final EdgeRealizer edgeRealizer = graph.getRealizer(edge);
333       for (int i = 0, n = edgeRealizer.labelCount(); i < n; i++) {
334         final EdgeLabel edgeLabel = edgeRealizer.getLabel(i);
335         result.add(edgeLabel);
336       }
337     }
338     return result;
339   }
340 
341   /**
342    * Creates a map that maps the preferred placement constants to strings.
343    */
344   private static Map createPlacementsToStringMap() {
345     final Map result = new LinkedHashMap(6);
346     result.put(new Byte(LabelLayoutConstants.PLACE_AT_SOURCE), "At Source");
347     result.put(new Byte(LabelLayoutConstants.PLACE_AT_CENTER), "At Center");
348     result.put(new Byte(LabelLayoutConstants.PLACE_AT_TARGET), "At Target");
349     result.put(new Byte(LabelLayoutConstants.PLACE_AT_SOURCE_PORT), "At Source Port");
350     result.put(new Byte(LabelLayoutConstants.PLACE_AT_TARGET_PORT), "At Target Port");
351     result.put(DEFAULT_PLACE, "Anywhere");
352     return result;
353   }
354 
355   /**
356    * Creates a map that maps the preferred side constants to strings.
357    */
358   private static Map createSidesToStringMap() {
359     final Map result = new LinkedHashMap(4);
360     result.put(new Byte(LabelLayoutConstants.PLACE_LEFT_OF_EDGE), "Left Of Edge");
361     result.put(new Byte(LabelLayoutConstants.PLACE_ON_EDGE), "On Edge");
362     result.put(new Byte(LabelLayoutConstants.PLACE_RIGHT_OF_EDGE), "Right Of Edge");
363     result.put(DEFAULT_SIDE, "Any side");
364     return result;
365   }
366 
367   /**
368    * Creates a map that maps the side references constants to strings.
369    */
370   private static Map createSideReferencesToStringMap() {
371     final Map result = new LinkedHashMap(3);
372     result.put(new Byte(PreferredPlacementDescriptor.SIDE_IS_RELATIVE_TO_EDGE_FLOW), "Relative to edge flow");
373     result.put(new Byte(PreferredPlacementDescriptor.SIDE_IS_ABSOLUTE_WITH_LEFT_IN_NORTH), "Absolute with left in north");
374     result.put(new Byte(PreferredPlacementDescriptor.SIDE_IS_ABSOLUTE_WITH_RIGHT_IN_NORTH), "Absolute with right in north");
375     return result;
376   }
377 
378   /**
379    * Creates a map that maps the angle reference constants to strings.
380    */
381   private static Map createAngleReferencesToStringMap() {
382     final Map result = new LinkedHashMap(2);
383     result.put(new Byte(PreferredPlacementDescriptor.ANGLE_IS_ABSOLUTE), "Absolute");
384     result.put(new Byte(PreferredPlacementDescriptor.ANGLE_IS_RELATIVE_TO_EDGE_FLOW), "Relative to edge");
385     return result;
386   }
387 
388   /**
389    * Creates a map that maps the angle rotation on right side of the ede constants to strings.
390    */
391   private static Map createAngleRotationsOnRightSideToStringMap() {
392     final Map result = new LinkedHashMap(2);
393     result.put(new Byte(PreferredPlacementDescriptor.ANGLE_ON_RIGHT_SIDE_CO_ROTATING), "Co-rotating");
394     result.put(new Byte(PreferredPlacementDescriptor.ANGLE_ON_RIGHT_SIDE_COUNTER_ROTATING), "Counter-rotating");
395     return result;
396   }
397 }
398