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 y.geom.YDimension;
31  import y.view.GenericNodeRealizer;
32  import y.view.NodeLabel;
33  import y.view.NodeRealizer;
34  import y.view.SmartNodeLabelModel;
35  
36  import java.awt.Color;
37  import java.awt.Font;
38  import java.awt.Insets;
39  import java.awt.geom.Rectangle2D;
40  import java.util.List;
41  
42  /**
43   * Helper class to synchronize the labels of the uml class realizer with the {@link UmlClassModel}. It supports
44   * <ul>
45   *   <li>creating labels from the model</li>
46   *   <li>remove attribute or operation from model by removing its appropriate label</li>
47   *   <li>add attribute or operation to model by adding an appropriate label</li>
48   * </ul>
49   * The mapping between labels and model item:
50   * <ul>
51   *   <li>label 0: name</li>
52   *   <li>label 1: attribute heading</li>
53   *   <li>label 2: operation heading</li>
54   *   <li>label 3 to n: attributes</li>
55   *   <li>label n+1 to m: operations</li>
56   * </ul>
57   */
58  class UmlClassLabelSupport {
59  
60    /** Gap between the last item of one section and the cation of the next section. */
61    public static final double SECTION_GAP = 5;
62  
63    private UmlClassLabelSupport() {
64    }
65  
66    /**
67     * Removes and creates all labels of the given node realizer.
68     */
69    public static void updateAllLabels(final NodeRealizer context) {
70      removeAllLabels(context);
71      createAllLabels(context);
72    }
73  
74    /**
75     * Removes all labels of the given node realizer.
76     */
77    private static void removeAllLabels(final NodeRealizer context) {
78      for (int i = context.labelCount() - 1; i >= 0; i--) {
79        context.removeLabel(i);
80      }
81    }
82  
83    /**
84     * Creates all labels of the given node realizer.
85     */
86    private static void createAllLabels(final NodeRealizer context) {
87      final UmlClassModel model = getModel(context);
88      double yOffset = 0d;
89  
90      // Create and add label 0: name label.
91      final NodeLabel nameLabel = createNameLabel(context);
92      configureLabelModel(nameLabel, false, 20d, yOffset);
93      context.addLabel(nameLabel);
94      yOffset += nameLabel.getHeight() + 5;
95  
96      if (model.areSectionsVisible()) {
97        // Create and add label 1: attribute heading label.
98        final NodeLabel attributeHeadingLabel = createCaptionLabel("Attributes");
99        configureLabelModel(attributeHeadingLabel, false, 20d, yOffset);
100       context.addLabel(attributeHeadingLabel);
101       yOffset += attributeHeadingLabel.getHeight();
102 
103       // Create and add label 2: operation heading label. It is below all attribute labels!
104       final NodeLabel operationHeadingLabel = createCaptionLabel("Operations");
105       context.addLabel(operationHeadingLabel);
106 
107       // Create and add label 3 to n: attribute labels.
108       if (model.areAttributesVisible()) {
109         final List attributes = model.getAttributes();
110         for (java.util.Iterator it = attributes.iterator(); it.hasNext(); ) {
111           final String text = (String) it.next();
112           final NodeLabel attributeLabel = createLabelByText(text);
113           configureLabelModel(attributeLabel, false, 30d, yOffset);
114           context.addLabel(attributeLabel);
115           yOffset += attributeLabel.getHeight();
116         }
117       }
118 
119       // Configure label 2; now we know its position.
120       yOffset += SECTION_GAP;
121       configureLabelModel(operationHeadingLabel, false, 20d, yOffset);
122       yOffset += operationHeadingLabel.getHeight();
123 
124       // Create and add label n+1 to m: operation labels.
125       if (model.areOperationsVisible()) {
126         final List operations = model.getOperations();
127         for (java.util.Iterator it = operations.iterator(); it.hasNext(); ) {
128           final String text = (String) it.next();
129           final NodeLabel operationLabel = createLabelByText(text);
130           configureLabelModel(operationLabel, false, 30d, yOffset);
131           context.addLabel(operationLabel);
132           yOffset += operationLabel.getHeight();
133         }
134       }
135     }
136   }
137 
138   /**
139    * Returns the {@link UmlClassModel model} stored and visualized by the given node realizer.
140    */
141   static UmlClassModel getModel(final NodeRealizer context) {
142     final GenericNodeRealizer gnr = (GenericNodeRealizer) context;
143     return (UmlClassModel) gnr.getUserData();
144   }
145 
146   /**
147    * Creates a name label for the given node realizer.
148    */
149   private static NodeLabel createNameLabel(final NodeRealizer context) {
150     final NodeLabel label = new NodeLabel();
151     final UmlClassModel model = getModel(context);
152     label.setText(model.getClassName());
153     label.setConfiguration(UmlRealizerFactory.LABEL_CONFIG_NAME);
154     label.setFontSize(16);
155     label.setFontStyle(Font.BOLD);
156     label.setTextColor(Color.WHITE);
157     label.setInsets(new Insets(10, 0, 15, 0));
158     return label;
159   }
160 
161   /**
162    * Creates a caption label for the given node realizer.
163    */
164   private static NodeLabel createCaptionLabel(final String text) {
165     final NodeLabel label = new NodeLabel();
166     label.setConfiguration(UmlRealizerFactory.LABEL_CONFIG_NAME);
167     label.setText(text);
168     label.setFontSize(14);
169     label.setTextColor(Color.WHITE);
170     return label;
171   }
172 
173   private static NodeLabel createLabelByText(final String text) {
174     final NodeLabel label = new NodeLabel();
175     label.setConfiguration(UmlRealizerFactory.LABEL_CONFIG_NAME);
176     label.setText(text);
177     label.setFontSize(14);
178     label.setTextColor(Color.DARK_GRAY);
179     return label;
180   }
181 
182   /**
183    * Configures the model for the given label.
184    */
185   private static void configureLabelModel(
186       final NodeLabel label,
187       final boolean isCenter,
188       final double xOffset,
189       final double yOffset
190   ) {
191     final SmartNodeLabelModel model = new SmartNodeLabelModel();
192     if (isCenter) {
193       label.setLabelModel(model, model.createSpecificModelParameter(0, -0.5, 0, -0.5, xOffset, yOffset, 0, -1));
194     } else {
195       label.setLabelModel(model, model.createSpecificModelParameter(-0.5, -0.5, -0.5, -0.5, xOffset, yOffset, 0, -1));
196     }
197   }
198 
199   /**
200    * Removes the label and its corresponding list item from the model.
201    */
202   public static void removeSelectedLabel(final NodeRealizer context) {
203     final NodeLabel label = getSelectedLabel(context);
204     if (label != null) {
205       int labelIndex = indexOfLabel(context, label);
206       updateSelection(context, labelIndex);
207       removeLabelFromModel(context, labelIndex);
208       updateAllLabels(context);
209       updateRealizerSize(context);
210     }
211   }
212 
213   /**
214    * Updates the selection when removing a label with the given index.
215    */
216   private static void updateSelection(final NodeRealizer context, final int labelIndexToRemove) {
217     // Removing the last item -> select the second last item
218     // Removing the sole item -> select none
219     // Removing any other -> keep selected index
220     final int modelListIndex = modelListIndexOf(context, labelIndexToRemove);
221     final List modelList = getModelList(context, labelIndexToRemove);
222     if (modelListIndex == modelList.size() - 1) {
223       getModel(context).setSelectedListIndex(modelList.size() - 2);
224     } else if (modelList.size() == 1) {
225       getModel(context).setSelectedListIndex(UmlClassModel.LIST_INDEX_NONE);
226     }
227   }
228 
229   /**
230    * Removes an attribute or operation corresponding to the given label index from the model.
231    */
232   private static void removeLabelFromModel(final NodeRealizer context, final int labelIndex) {
233     final List modelList = getModelList(context, labelIndex);
234     final int modelListIndex = modelListIndexOf(context, labelIndex);
235     modelList.remove(modelListIndex);
236   }
237 
238   /**
239    * Returns the list of attributes or operations depending to which one the given label index belongs to.
240    */
241   private static List getModelList(final NodeRealizer context, final int labelIndex) {
242     final UmlClassModel model = getModel(context);
243     if (getModel(context).areAttributesVisible()) {
244       final int attributeCount = model.getAttributes().size();
245       if (labelIndex < attributeCount + 3) {
246         // starts with index 3 -> labels for name, attribute and operation caption
247         return model.getAttributes();
248       }
249     }
250     return model.getOperations();
251   }
252 
253   /**
254    * Returns the list index of an attribute or operation depending to which one the given label index belongs to.
255    */
256   private static int modelListIndexOf(final NodeRealizer context, final int labelIndex) {
257     final UmlClassModel model = getModel(context);
258     if (getModel(context).areAttributesVisible()) {
259       final int attributeCount = model.getAttributes().size();
260       // starts with index 3 -> labels for name, attribute and operation caption
261       if (labelIndex >= attributeCount + 3) {
262         return labelIndex - 3 - attributeCount;
263       }
264     }
265     return labelIndex - 3;
266   }
267 
268   /**
269    * Returns the index of the given label that belongs to the given node realizer.
270    */
271   private static int indexOfLabel(final NodeRealizer context, final NodeLabel label) {
272     int labelIndex = -1;
273     for (int i = 0; i < context.labelCount(); i++) {
274       if (label == context.getLabel(i)) {
275         labelIndex = i;
276         break;
277       }
278     }
279     return labelIndex;
280   }
281 
282   /**
283    * Adds a label and a list item to the model for a new attribute.
284    */
285   public static NodeLabel addAttribute(final NodeRealizer context) {
286     addAttributeToModel(context, "attribute");
287     updateAllLabels(context);
288     updateRealizerSize(context);
289     final int index = indexOfLastAttributeLabel(context);
290     selectLabel(context, index);
291     return context.getLabel(index);
292   }
293 
294   /**
295    * Adds a list item to the model for a new attribute.
296    */
297   private static void addAttributeToModel(final NodeRealizer context, final String text) {
298     final UmlClassModel model = getModel(context);
299     model.getAttributes().add(text);
300   }
301 
302   /**
303    * Returns the index of the labels of the last attribute.
304    */
305   private static int indexOfLastAttributeLabel(final NodeRealizer context) {
306     final UmlClassModel model = getModel(context);
307     return model.getAttributes().size() + 2;
308   }
309 
310   /**
311    * Adds a label and a list item to the model for a new operation.
312    */
313   public static NodeLabel addOperation(final NodeRealizer context) {
314     addOperationToModel(context, "operation");
315     updateAllLabels(context);
316     updateRealizerSize(context);
317     final int index = indexOfLastOperationLabel(context);
318     selectLabel(context, index);
319     return context.getLabel(index);
320   }
321 
322   /**
323    * Adds a list item to the model for a new operation.
324    */
325   private static void addOperationToModel(final NodeRealizer context, final String text) {
326     final UmlClassModel model = getModel(context);
327     model.getOperations().add(text);
328   }
329 
330   /**
331    * Returns the index of the labels of the last operation.
332    */
333   private static int indexOfLastOperationLabel(final NodeRealizer context) {
334     final UmlClassModel model = getModel(context);
335     if (getModel(context).areAttributesVisible()) {
336       return model.getAttributes().size() + model.getOperations().size() + 2;
337     } else
338       return model.getOperations().size() + 2;
339   }
340 
341   /**
342    * Updates the attribute or operation corresponding to the given label with its text.
343    */
344   public static void updateLabelText(final NodeRealizer context, final NodeLabel label) {
345     final String text = label.getText();
346     final int labelIndex = indexOfLabel(context, label);
347     updateLabelTextInModel(context, labelIndex, text);
348   }
349 
350   /**
351    * Updates the attribute or operation corresponding to the given label index with the given text.
352    */
353   private static void updateLabelTextInModel(final NodeRealizer context, final int labelIndex, final String text) {
354     if (labelIndex == 0) {
355       final UmlClassModel model = getModel(context);
356       model.setClassName(text);
357     } else {
358       final List modelList = getModelList(context, labelIndex);
359       final int modelListIndex = modelListIndexOf(context, labelIndex);
360       modelList.set(modelListIndex, text);
361     }
362   }
363 
364   /**
365    * Recalculates the size of the given node realizer.
366    */
367   public static void updateRealizerSize(final NodeRealizer context) {
368     final double x = context.getX();
369     final double y = context.getY();
370     final YDimension minimumSize = context.getSizeConstraintProvider().getMinimumSize();
371     final double width = Math.max(context.getWidth(), minimumSize.getWidth());
372     context.setSize(width, minimumSize.getHeight());
373     context.setLocation(x, y);
374   }
375 
376   /**
377    * Selects the list item of the label that contains the given point.
378    */
379   public static boolean selectListItemAt(final NodeRealizer context, final double x, final double y) {
380     if (!UmlClassLabelSupport.getModel(context).areSectionsVisible()) {
381       return false;
382     }
383 
384     final int labelIndex = indexOfLabelAt(context, x, y);
385     if (labelIndex > 2) {
386       selectLabel(context, labelIndex);
387       return true;
388     }
389     return false;
390   }
391 
392   /**
393    * Returns the index of the label that contains the given point.
394    */
395   private static int indexOfLabelAt(final NodeRealizer context, final double x, final double y) {
396     for (int i = 3; i < context.labelCount(); i++) {
397       final NodeLabel label = context.getLabel(i);
398       if (labelContains(context, label, x, y)) {
399         return i;
400       }
401     }
402     return -1;
403   }
404 
405   /**
406    * Selects the list item corresponding to the given label index.
407    */
408   private static void selectLabel(final NodeRealizer context, final int labelIndex) {
409     final UmlClassModel model = getModel(context);
410     if (model.getAttributes() == getModelList(context, labelIndex)) {
411       getModel(context).setSelectedList(UmlClassModel.LIST_ATTRIBUTES);
412     } else {
413       getModel(context).setSelectedList(UmlClassModel.LIST_OPERATIONS);
414     }
415     getModel(context).setSelectedListIndex(modelListIndexOf(context, labelIndex));
416   }
417 
418   /**
419    * Selects the list item corresponding to the given label.
420    */
421   public static void selectLabel(final NodeRealizer context, final NodeLabel label) {
422     final int labelIndex = indexOfLabel(context, label);
423     selectLabel(context, labelIndex);
424   }
425 
426   /**
427    * Checks whether or not the given label contains the given point.
428    */
429   private static boolean labelContains(final NodeRealizer context, final NodeLabel label, final double x, final double y) {
430    final double lx = context.getX();
431    final double ly = label.getLocation().getY();
432    final double lw = context.getWidth();
433    final double lh = label.getHeight();
434     return x >= lx && x <= lx + lw &&
435            y >= ly && y <= ly + lh;
436   }
437 
438   /**
439    * Returns the area of the given label.
440    */
441   public static void getLabelArea(final NodeRealizer context, final NodeLabel label, final Rectangle2D rect) {
442     rect.setFrame(
443         context.getX(),
444         label.getLocation().getY(),
445         context.getWidth(),
446         label.getHeight());
447   }
448 
449   /**
450    * Returns the label that is selected.
451    */
452   public static NodeLabel getSelectedLabel(final NodeRealizer context) {
453     final int listIndex = getModel(context).getSelectedListIndex();
454     if (listIndex < 0 || !isSelectedSectionVisible(context)) {
455       return null;
456     }
457 
458     final int list = getModel(context).getSelectedList();
459     final int labelIndex = indexOfLabel(context, list, listIndex);
460     return context.getLabel(labelIndex);
461   }
462 
463   /**
464    * Returns the index of the label corresponding to the item with the given index in the given list.
465    */
466   private static int indexOfLabel(final NodeRealizer context, final int list, final int index) {
467     if ((list == UmlClassModel.LIST_ATTRIBUTES) || !getModel(context).areAttributesVisible()) {
468       return index + 3;
469     } else {
470       final UmlClassModel model = getModel(context);
471       return model.getAttributes().size() + index + 3;
472     }
473   }
474 
475   /**
476    * Checks whether or not the section with the selection is visible.
477    */
478   private static boolean isSelectedSectionVisible(final NodeRealizer context) {
479     final int list = getModel(context).getSelectedList();
480     return UmlClassLabelSupport.getModel(context).areSectionsVisible() &&
481            (getModel(context).areAttributesVisible() && (list == UmlClassModel.LIST_ATTRIBUTES)) ||
482            (getModel(context).areOperationsVisible() && (list == UmlClassModel.LIST_OPERATIONS));
483   }
484 
485   /**
486    * Checks whether or not an attribute item is selected.
487    */
488   public static boolean isAttributeSelected(final NodeRealizer context) {
489     return (getModel(context).getSelectedList() == UmlClassModel.LIST_ATTRIBUTES) &&
490            getModel(context).getSelectedListIndex() >= 0;
491 
492   }
493 
494   /**
495    * Checks whether or not an operation item is selected.
496    */
497   public static boolean isOperationSelected(final NodeRealizer context) {
498     return (getModel(context).getSelectedList() == UmlClassModel.LIST_OPERATIONS) &&
499            getModel(context).getSelectedListIndex() >= 0;
500 
501   }
502 
503   /**
504    * Returns the label of the class name.
505    */
506   static NodeLabel getNameLabel(final NodeRealizer context) {
507     return context.getLabel(0);
508   }
509 
510   /**
511    * Returns the label of the caption of the attribute section.
512    */
513   static NodeLabel getAttributeCaptionLabel(final NodeRealizer context) {
514     return context.getLabel(1);
515   }
516 
517   /**
518    * Returns the label of the caption of the operation section.
519    */
520   static NodeLabel getOperationCaptionLabel(final NodeRealizer context) {
521     return context.getLabel(2);
522   }
523 
524   /**
525    * Checks whether or not the given label is a caption label e.g. shows "Attributes" or "Operations".
526    */
527   static boolean isCaptionLabel(final NodeLabel label) {
528     final int labelIndex = indexOfLabel(label.getRealizer(), label);
529     return (labelIndex == 1) || (labelIndex == 2);
530   }
531 }
532