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.Shape;
41  import java.awt.geom.GeneralPath;
42  import java.awt.geom.Rectangle2D;
43  
44  /**
45   * An {@link UmlClassButton} that opens or closes the class on mouse click.
46   */
47  class UmlClassOpenCloseClassButton extends UmlClassButton {
48    public static final double ICON_SIZE = 16;
49    public static final double ICON_GAP = 5;
50  
51    /**
52     * Checks whether or not the button is currently visible. The button is always visible.
53     */
54    protected boolean isVisible(final NodeRealizer context) {
55      return true;
56    }
57  
58    /**
59     * Paints the button with its icon.
60     */
61    public void paint(final NodeRealizer context, final Graphics2D graphics) {
62      if (isVisible(context)) {
63        final Rectangle2D area = getButtonArea(context);
64        paintButton(context, graphics, area);
65        paintIcon(context, graphics, area);
66      }
67    }
68  
69    /**
70     * Returns the area where to paint the button with its icon.
71     */
72    protected Rectangle2D getButtonArea(final NodeRealizer context) {
73      return new Rectangle2D.Double(
74          context.getX() + context.getWidth() - ICON_SIZE - ICON_GAP,
75          context.getY() + ICON_GAP,
76          ICON_SIZE,
77          ICON_SIZE);
78    }
79  
80    /**
81     * Paints the button into the given area.
82     */
83    private void paintButton(final NodeRealizer context, final Graphics2D graphics, final Rectangle2D area) {
84      final Color color = getButtonColor(context);
85      graphics.setColor(color);
86      graphics.fill(area);
87    }
88  
89    /**
90     * Returns the color of the button.
91     */
92    private Color getButtonColor(final NodeRealizer context) {
93      return isMouseOverButton(context) ?
94          UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_ACTIVE :
95          UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_BLANK;
96    }
97  
98    /**
99     * Checks whether or not the mouse is currently over this button. The button where the mouse is currently over is
100    * stored as an appropriate style property of the given context.
101    */
102   private boolean isMouseOverButton(final NodeRealizer context) {
103     return UmlRealizerFactory.getMouseOverButton(context) == UmlRealizerFactory.BUTTON_OPEN_CLOSE_CLASS_SECTIONS;
104   }
105 
106   /**
107    * Paints the icon into the given area.
108    */
109   private void paintIcon(final NodeRealizer context, final Graphics2D graphics, final Rectangle2D area) {
110     final Shape shape = getIconShape(context, area);
111     graphics.setColor(UmlRealizerFactory.COLOR_BUTTON_FOREGROUND_ENABLED);
112     graphics.setStroke(LineType.LINE_1);
113     graphics.draw(shape);
114   }
115 
116   /**
117    * Returns the shape of the icon.
118    */
119   private Shape getIconShape(final NodeRealizer context, final Rectangle2D area) {
120     if (UmlClassLabelSupport.getModel(context).areSectionsVisible()) {
121       return getCloseShape(area);
122     } else {
123       return getOpenShape(area);
124     }
125   }
126 
127   /**
128    * Returns the shape of the icon of the closed state.
129    */
130   private Shape getCloseShape(final Rectangle2D area) {
131     final float minX = (float) (area.getX() + area.getWidth() * 0.25);
132     final float midX = (float) (area.getX() + area.getWidth() * 0.5);
133     final float maxX = (float) (area.getX() + area.getWidth() * 0.75);
134     final float minY = (float) (area.getY() + area.getHeight() * 0.25);
135     final float midY = (float) (area.getY() + area.getHeight() * 0.5);
136     final float maxY = (float) (area.getY() + area.getHeight() * 0.75);
137 
138     final GeneralPath icon = new GeneralPath();
139     icon.moveTo(minX, midY);
140     icon.lineTo(midX, minY);
141     icon.lineTo(maxX, midY);
142     icon.moveTo(minX, maxY);
143     icon.lineTo(midX, midY);
144     icon.lineTo(maxX, maxY);
145 
146     return icon;
147   }
148 
149   /**
150    * Returns the shape of the icon of the opened state.
151    */
152   private Shape getOpenShape(final Rectangle2D area) {
153     final float minX = (float) (area.getX() + area.getWidth() * 0.25);
154     final float midX = (float) (area.getX() + area.getWidth() * 0.5);
155     final float maxX = (float) (area.getX() + area.getWidth() * 0.75);
156     final float minY = (float) (area.getY() + area.getHeight() * 0.25);
157     final float midY = (float) (area.getY() + area.getHeight() * 0.5);
158     final float maxY = (float) (area.getY() + area.getHeight() * 0.75);
159 
160     final GeneralPath icon = new GeneralPath();
161     icon.moveTo(minX, minY);
162     icon.lineTo(midX, midY);
163     icon.lineTo(maxX, minY);
164     icon.moveTo(minX, midY);
165     icon.lineTo(midX, maxY);
166     icon.lineTo(maxX, midY);
167     return icon;
168   }
169 
170   /**
171    * Toggles the visibility of the class sections on button click.
172    */
173   protected void buttonClicked(final NodeRealizer context, final Graph2DView view) {
174     final Graph2D graph = view.getGraph2D();
175     graph.firePreEvent();
176     try {
177       graph.backupRealizers(new NodeList(context.getNode()).nodes());
178       graph.backupRealizers(context.getNode().edges());
179       final boolean isOpened = UmlClassLabelSupport.getModel(context).areSectionsVisible();
180       final UmlClassAnimation animation = new OpenCloseClassAnimation(view, context, isOpened);
181       animation.play();
182     } finally {
183       graph.firePostEvent();
184     }
185   }
186 
187   /**
188    * Sets an appropriate style property of the given context to notify that the mouse is currently over this button.
189    */
190   protected void buttonEntered(final NodeRealizer context, final Graph2DView view) {
191     UmlRealizerFactory.setMouseOverButton(context, UmlRealizerFactory.BUTTON_OPEN_CLOSE_CLASS_SECTIONS);
192     view.updateView();
193   }
194 
195   /**
196    * Animates the opening and closing of the details of the class.
197    */
198   private static class OpenCloseClassAnimation extends UmlClassAnimation  {
199     private OpenCloseClassAnimation(final Graph2DView view, final NodeRealizer context, final boolean isClosing) {
200       super(view, context, isClosing);
201     }
202 
203     /**
204      * Closes the class details.
205      */
206     protected void close() {
207       UmlClassLabelSupport.getModel(context).setSectionsVisible(false);
208       UmlClassLabelSupport.updateAllLabels(context);
209       UmlClassLabelSupport.updateRealizerSize(context);
210     }
211 
212     /**
213      * Opens the class details.
214      */
215     protected void open() {
216       UmlClassLabelSupport.getModel(context).setSectionsVisible(true);
217       UmlClassLabelSupport.updateAllLabels(context);
218       UmlClassLabelSupport.updateRealizerSize(context);
219     }
220 
221     /**
222      * Returns the size of the realizer when the class details are closed.
223      */
224     protected YDimension getClosedSize() {
225       final boolean isVisible = UmlClassLabelSupport.getModel(context).areSectionsVisible();
226       UmlClassLabelSupport.getModel(context).setSectionsVisible(false);
227       UmlClassLabelSupport.updateAllLabels(context);
228       UmlClassLabelSupport.updateRealizerSize(context);
229 
230       final YDimension dimension = new YDimension(context.getWidth(), context.getHeight());
231       UmlClassLabelSupport.getModel(context).setSectionsVisible(isVisible);
232       UmlClassLabelSupport.updateAllLabels(context);
233       UmlClassLabelSupport.updateRealizerSize(context);
234       return dimension;
235     }
236 
237     /**
238      * The lower y-coordinate of the fixed part of the realizer is the bottom of its name label.
239      */
240     protected double getFixedY() {
241       final NodeLabel nameLabel = UmlClassLabelSupport.getNameLabel(context);
242       return nameLabel.getLocation().getY() + nameLabel.getHeight();
243     }
244 
245     /**
246      * The upper y-coordinate of the moving part of the realizer is the bottom of its last label.
247      */
248     protected double getMovingY() {
249       double fixedY = getFixedY();
250       for (int i = 0; i < context.labelCount(); i++) {
251         final NodeLabel label = context.getLabel(i);
252         fixedY = Math.max(fixedY, label.getLocation().getY() + label.getHeight());
253       }
254       return fixedY;
255     }
256   }
257 }
258