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.base.NodeList;
31  import y.geom.YDimension;
32  import y.view.Graph2D;
33  import y.view.Graph2DView;
34  import y.view.LineType;
35  import y.view.NodeLabel;
36  import y.view.NodeRealizer;
37  
38  import java.awt.Color;
39  import java.awt.Graphics2D;
40  import java.awt.RenderingHints;
41  import java.awt.Shape;
42  import java.awt.geom.GeneralPath;
43  import java.awt.geom.Rectangle2D;
44  
45  /**
46   * An {@link UmlClassButton} that opens or closes the section of attributes or operations on mouse click.
47   */
48  class UmlClassOpenCloseSectionButton extends UmlClassButton {
49    private static final double ICON_SIZE = 12;
50    private static final double GAP = 5;
51  
52    private final boolean isAttributeSection;
53  
54    /**
55     * Creates an open/close button for the given section.
56     */
57    public UmlClassOpenCloseSectionButton(final boolean attributeSection) {
58      isAttributeSection = attributeSection;
59    }
60  
61    /**
62     * Paints the button with its icon. Anti-aliasing is temporary disabled for nicer visualization of the icon.
63     */
64    public void paint(final NodeRealizer context, final Graphics2D graphics) {
65      if (isVisible(context)) {
66        final Object orgRenderingHint = graphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
67        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
68        try {
69          final Rectangle2D area = getButtonArea(context);
70          paintButton(context, graphics, area);
71          paintIcon(context, graphics, area);
72        } finally {
73          graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, orgRenderingHint);
74        }
75      }
76    }
77  
78    /**
79     * Checks whether or not the button is currently visible. The button is not visible if the class details is not
80     * opened.
81     */
82    protected boolean isVisible(final NodeRealizer context) {
83      return UmlClassLabelSupport.getModel(context).areSectionsVisible();
84    }
85  
86    /**
87     * Returns the area where to paint the button with its icon.
88     */
89    protected Rectangle2D getButtonArea(final NodeRealizer context) {
90      final NodeLabel label = getCaptionLabel(context);
91      return new Rectangle2D.Double(
92          context.getX() + GAP,
93          label.getLocation().getY() + (label.getHeight() - ICON_SIZE) * 0.5,
94          ICON_SIZE,
95          ICON_SIZE);
96    }
97  
98    /** Returns the caption label of the section that corresponds to this button. */
99    private NodeLabel getCaptionLabel(final NodeRealizer context) {
100     if (isAttributeSection) {
101       return UmlClassLabelSupport.getAttributeCaptionLabel(context);
102     } else {
103       return UmlClassLabelSupport.getOperationCaptionLabel(context);
104     }
105   }
106 
107   /**
108    * Paints the button into the given area.
109    */
110   private void paintButton(final NodeRealizer context, final Graphics2D graphics, final Rectangle2D area) {
111     final Color color = getButtonColor(context);
112     graphics.setColor(color);
113     graphics.fill(area);
114   }
115 
116   /**
117    * Returns the color of the button.
118    */
119   private Color getButtonColor(final NodeRealizer context) {
120     return isMouseOverButton(context) ?
121         UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_ACTIVE :
122         UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_BLANK;
123   }
124 
125   /**
126    * Checks whether or not the mouse is currently over this button. The button where the mouse is currently over is
127    * stored as an appropriate style property of the given context.
128    */
129   private boolean isMouseOverButton(final NodeRealizer context) {
130     final int button = UmlRealizerFactory.getMouseOverButton(context);
131     return ((button == UmlRealizerFactory.BUTTON_OPEN_CLOSE_ATTRIBUTE_SECTION) && isAttributeSection) ||
132            ((button == UmlRealizerFactory.BUTTON_OPEN_CLOSE_OPERATION_SECTION) && !isAttributeSection);
133   }
134 
135   /**
136    * Paints the icon into the given area.
137    */
138   private void paintIcon(final NodeRealizer context, final Graphics2D graphics, final Rectangle2D area) {
139     final Shape shape = getIconShape(context, area);
140     graphics.setColor(UmlRealizerFactory.COLOR_BUTTON_FOREGROUND_ENABLED);
141     graphics.setStroke(LineType.LINE_1);
142     graphics.draw(shape);
143   }
144 
145   /** Returns the shape of the icon. */
146   private Shape getIconShape(final NodeRealizer context, final Rectangle2D area) {
147     if (isAttributeSection) {
148       return UmlClassLabelSupport.getModel(context).areAttributesVisible() ? getMinusShape(area) : getPlusShape(area);
149     } else {
150       return UmlClassLabelSupport.getModel(context).areOperationsVisible() ? getMinusShape(area) : getPlusShape(area);
151     }
152   }
153 
154   /**
155    * Returns the shape of the icon of the closed state.
156    */
157   private Shape getMinusShape(final Rectangle2D area) {
158     final float minX = (float) (area.getX() + area.getWidth() * 0.25);
159     final float maxX = (float) (area.getX() + area.getWidth() * 0.75);
160     final float midY = (float) (area.getY() + area.getHeight() * 0.5);
161 
162     final GeneralPath icon = new GeneralPath();
163     icon.moveTo(minX, midY);
164     icon.lineTo(maxX, midY);
165     return icon;
166   }
167 
168   /**
169    * Returns the shape of the icon of the opened state.
170    */
171   private Shape getPlusShape(final Rectangle2D area) {
172     final float minX = (float) (area.getX() + area.getWidth() * 0.25);
173     final float midX = (float) (area.getX() + area.getWidth() * 0.5);
174     final float maxX = (float) (area.getX() + area.getWidth() * 0.75);
175     final float minY = (float) (area.getY() + area.getHeight() * 0.25);
176     final float midY = (float) (area.getY() + area.getHeight() * 0.5);
177     final float maxY = (float) (area.getY() + area.getHeight() * 0.75);
178 
179     final GeneralPath icon = new GeneralPath();
180     icon.moveTo(minX, midY);
181     icon.lineTo(maxX, midY);
182     icon.moveTo(midX, minY);
183     icon.lineTo(midX, maxY);
184     return icon;
185   }
186 
187   /**
188    * Toggles the visibility of the attribute or operation section on button click.
189    */
190   protected void buttonClicked(final NodeRealizer context, final Graph2DView view) {
191     final Graph2D graph = view.getGraph2D();
192     graph.firePreEvent();
193     try {
194       graph.backupRealizers(new NodeList(context.getNode()).nodes());
195       graph.backupRealizers(context.getNode().edges());
196       if (isAttributeSection) {
197         final boolean areAttributesOpened = UmlClassLabelSupport.getModel(context).areAttributesVisible();
198         final UmlClassAnimation attributeAnimation = new OpenCloseAttributeSectionAnimation(
199             view,
200             context,
201             areAttributesOpened);
202         attributeAnimation.play();
203       } else {
204         final boolean areOperationsOpened = UmlClassLabelSupport.getModel(context).areOperationsVisible();
205         final UmlClassAnimation operationAnimation = new OpenCloseOperationSectionAnimation(
206             view,
207             context,
208             areOperationsOpened);
209         operationAnimation.play();
210       }
211     } finally {
212       graph.firePostEvent();
213     }
214   }
215 
216   /** Sets an appropriate style property of the given context to notify that the mouse is currently over this button. */
217   protected void buttonEntered(final NodeRealizer context, final Graph2DView view) {
218     if (isAttributeSection) {
219       UmlRealizerFactory.setMouseOverButton(context, UmlRealizerFactory.BUTTON_OPEN_CLOSE_ATTRIBUTE_SECTION);
220     } else {
221       UmlRealizerFactory.setMouseOverButton(context, UmlRealizerFactory.BUTTON_OPEN_CLOSE_OPERATION_SECTION);
222     }
223     view.updateView();
224   }
225 
226   /**
227    * Animates the opening and closing of the attributes section.
228    */
229   private static class OpenCloseAttributeSectionAnimation extends UmlClassAnimation  {
230     private OpenCloseAttributeSectionAnimation(
231         final Graph2DView view,
232         final NodeRealizer context,
233         final boolean isClosing
234     ) {
235       super(view, context, isClosing);
236     }
237 
238     /**
239      * Closes the attribute section.
240      */
241     protected void close() {
242       UmlClassLabelSupport.getModel(context).setAttributesVisible(false);
243       UmlClassLabelSupport.updateAllLabels(context);
244       UmlClassLabelSupport.updateRealizerSize(context);
245     }
246 
247     /**
248      * Opens the attribute section.
249      */
250     protected void open() {
251       UmlClassLabelSupport.getModel(context).setAttributesVisible(true);
252       UmlClassLabelSupport.updateAllLabels(context);
253       UmlClassLabelSupport.updateRealizerSize(context);
254     }
255 
256     /**
257      * Returns the size of the realizer when the attribute section is closed.
258      */
259     protected YDimension getClosedSize() {
260       final boolean isVisible = UmlClassLabelSupport.getModel(context).areAttributesVisible();
261       UmlClassLabelSupport.getModel(context).setAttributesVisible(false);
262       UmlClassLabelSupport.updateAllLabels(context);
263       UmlClassLabelSupport.updateRealizerSize(context);
264       final YDimension dimension = new YDimension(context.getWidth(), context.getHeight());
265       UmlClassLabelSupport.getModel(context).setAttributesVisible(isVisible);
266       UmlClassLabelSupport.updateAllLabels(context);
267       UmlClassLabelSupport.updateRealizerSize(context);
268       return dimension;
269     }
270 
271     /**
272      * The lower y-coordinate of the fixed part of the realizer is the bottom of its attribute caption label.
273      */
274     protected double getFixedY() {
275       final NodeLabel label = UmlClassLabelSupport.getAttributeCaptionLabel(context);
276       return label.getLocation().getY() + label.getHeight();
277     }
278 
279     /**
280      * The upper y-coordinate of the moving part of the realizer is the top of its operation caption label.
281      */
282     protected double getMovingY() {
283       final NodeLabel label = UmlClassLabelSupport.getOperationCaptionLabel(context);
284       return label.getLocation().getY() - UmlClassLabelSupport.SECTION_GAP;
285     }
286 
287     /**
288      * Sets the opacity of the attribute buttons depending on the given state.
289      *
290      * @param state the state of the animation between <code>0</code> and <code>1</code>
291      */
292     protected void stateUpdated(final double state) {
293       UmlRealizerFactory.setAttributeButtonOpacity(context, (float) state);
294     }
295   }
296 
297   /**
298    * Animates the opening and closing of the operations section.
299    */
300   private static class OpenCloseOperationSectionAnimation extends UmlClassAnimation  {
301     private OpenCloseOperationSectionAnimation(
302         final Graph2DView view,
303         final NodeRealizer context,
304         final boolean isClosing
305     ) {
306       super(view, context, isClosing);
307     }
308 
309     /**
310      * Closes the operation section.
311      */
312     protected void close() {
313       UmlClassLabelSupport.getModel(context).setOperationsVisible(false);
314       UmlClassLabelSupport.updateAllLabels(context);
315       UmlClassLabelSupport.updateRealizerSize(context);
316     }
317 
318     /**
319      * Opens the operation section.
320      */
321     protected void open() {
322       UmlClassLabelSupport.getModel(context).setOperationsVisible(true);
323       UmlClassLabelSupport.updateAllLabels(context);
324       UmlClassLabelSupport.updateRealizerSize(context);
325     }
326 
327     /**
328      * Returns the size of the realizer when the attribute section is closed.
329      */
330     protected YDimension getClosedSize() {
331       final boolean isVisible = UmlClassLabelSupport.getModel(context).areOperationsVisible();
332       UmlClassLabelSupport.getModel(context).setOperationsVisible(false);
333       UmlClassLabelSupport.updateAllLabels(context);
334       UmlClassLabelSupport.updateRealizerSize(context);
335       final YDimension dimension = new YDimension(context.getWidth(), context.getHeight());
336       UmlClassLabelSupport.getModel(context).setOperationsVisible(isVisible);
337       UmlClassLabelSupport.updateAllLabels(context);
338       UmlClassLabelSupport.updateRealizerSize(context);
339       return dimension;
340     }
341 
342     /**
343      * The lower y-coordinate of the fixed part of the realizer is the bottom of its operation caption label.
344      */
345     protected double getFixedY() {
346       final NodeLabel label = UmlClassLabelSupport.getOperationCaptionLabel(context);
347       return label.getLocation().getY() + label.getHeight();
348     }
349 
350     /**
351      * The upper y-coordinate of the moving part of the realizer is the bottom of its last label.
352      */
353     protected double getMovingY() {
354       double fixedY = getFixedY();
355       for (int i = 0; i < context.labelCount(); i++) {
356         final NodeLabel label = context.getLabel(i);
357         fixedY = Math.max(fixedY, label.getLocation().getY() + label.getHeight());
358       }
359       return fixedY;
360     }
361 
362     /**
363      * Sets the opacity of the operation buttons depending on the given state.
364      *
365      * @param state the state of the animation between <code>0</code> and <code>1</code>
366      */
367     protected void stateUpdated(final double state) {
368       UmlRealizerFactory.setOperationButtonOpacity(context, (float) state);
369     }
370   }
371 }