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.AlphaComposite;
39  import java.awt.Color;
40  import java.awt.Composite;
41  import java.awt.Graphics2D;
42  import java.awt.RenderingHints;
43  import java.awt.Shape;
44  import java.awt.geom.Ellipse2D;
45  import java.awt.geom.GeneralPath;
46  import java.awt.geom.Rectangle2D;
47  
48  /**
49   * An {@link UmlClassButton} that removes a selected attribute or operation on mouse click.
50   */
51  class UmlClassRemoveItemButton extends UmlClassButton {
52    public static final double ICON_SIZE = 12;
53    public static final double ICON_GAP = 5;
54  
55    private final boolean isAttributeSection;
56  
57    /**
58     * Creates a remove button for the given section.
59     */
60    public UmlClassRemoveItemButton(final boolean attributeSection) {
61      isAttributeSection = attributeSection;
62    }
63  
64    /**
65     * Paints the button with its icon.
66     */
67    public void paint(final NodeRealizer context, final Graphics2D graphics) {
68      if (isVisible(context)) {
69        // Set the opacity of the button.
70        final Composite orgComposite = graphics.getComposite();
71        final AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getButtonOpacity(context));
72        graphics.setComposite(composite);
73  
74        final Rectangle2D area = getButtonArea(context);
75        paintBackground(context, graphics, area);
76        paintIcon(context, graphics, area);
77  
78        graphics.setComposite(orgComposite);
79      }
80    }
81  
82    /**
83     * Checks whether or not the button is currently visible. The button is not visible if the corresponding section is
84     * not opened.
85     */
86    protected boolean isVisible(final NodeRealizer context) {
87      final boolean areClassDetailsVisible = UmlClassLabelSupport.getModel(context).areSectionsVisible();
88      final boolean isSectionVisible =
89          isAttributeSection && UmlClassLabelSupport.getModel(context).areAttributesVisible() ||
90          !isAttributeSection && UmlClassLabelSupport.getModel(context).areOperationsVisible();
91      return isSectionVisible && areClassDetailsVisible;
92    }
93  
94    /**
95     * Returns the area where to paint the button with its icon.
96     */
97    protected Rectangle2D getButtonArea(final NodeRealizer context) {
98      final NodeLabel label = getCaptionLabel(context);
99      return new Rectangle2D.Double(
100         context.getX() + context.getWidth() - ICON_SIZE - ICON_GAP,
101         label.getLocation().getY() + (label.getHeight() - ICON_SIZE) * 0.5,
102         ICON_SIZE,
103         ICON_SIZE);
104   }
105 
106   /** Returns the caption label of the section that corresponds to this button. */
107   private NodeLabel getCaptionLabel(final NodeRealizer context) {
108     if (isAttributeSection) {
109       return UmlClassLabelSupport.getAttributeCaptionLabel(context);
110     } else {
111       return UmlClassLabelSupport.getOperationCaptionLabel(context);
112     }
113   }
114 
115   /**
116    * Paints the button into the given area.
117    */
118   private void paintBackground(final NodeRealizer context, final Graphics2D graphics, final Rectangle2D area) {
119     final Color color = isMouseOverButton(context) && isEnabled(context) ?
120         UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_ACTIVE :
121         UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_BLANK;
122     final Shape shape = new Ellipse2D.Double(area.getX(), area.getY(), area.getWidth(), area.getHeight());
123     graphics.setColor(color);
124     graphics.fill(shape);
125   }
126 
127   /** Returns the opacity of this button. */
128   private float getButtonOpacity(final NodeRealizer context) {
129     if (isAttributeSection) {
130       return UmlRealizerFactory.getAttributeButtonOpacity(context);
131     } else {
132       return UmlRealizerFactory.getOperationButtonOpacity(context);
133     }
134   }
135 
136   /**
137    * Checks whether or not the mouse is currently over this button. The button where the mouse is currently over is
138    * stored as an appropriate style property of the given context.
139    */
140   private boolean isMouseOverButton(final NodeRealizer context) {
141     final int button = UmlRealizerFactory.getMouseOverButton(context);
142     return ((button == UmlRealizerFactory.BUTTON_SUB_ATTRIBUTE) && isAttributeSection) ||
143            ((button == UmlRealizerFactory.BUTTON_SUB_OPERATION) && !isAttributeSection);
144   }
145 
146   /**
147    * Paints the icon into the given area. Anti-aliasing is temporary disabled for nicer visualization of the icon.
148    */
149   private void paintIcon(final NodeRealizer context, final Graphics2D graphics, final Rectangle2D area) {
150     final Object orgRenderingHint = graphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
151     graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
152     try {
153       final Shape shape = getIconShape(area);
154       final Color color = isEnabled(context) ?
155           UmlRealizerFactory.COLOR_BUTTON_FOREGROUND_ENABLED :
156           UmlRealizerFactory.COLOR_BUTTON_FOREGROUND_DISABLED;
157       graphics.setColor(color);
158       graphics.setStroke(LineType.LINE_1);
159       graphics.draw(shape);
160     } finally {
161       graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, orgRenderingHint);
162     }
163   }
164 
165   /**
166    * Returns whether or not this button is currently enabled. The button is enabled when one item of its corresponding
167    * list is currently selected.
168    */
169   private boolean isEnabled(final NodeRealizer context) {
170     return (isAttributeSection && UmlClassLabelSupport.isAttributeSelected(context)) ||
171            (!isAttributeSection && UmlClassLabelSupport.isOperationSelected(context));
172   }
173 
174   /**
175    * Returns the shape of the icon.
176    */
177   private Shape getIconShape(final Rectangle2D area) {
178     final float minX = (float) (area.getX() + area.getWidth() * 0.25);
179     final float maxX = (float) (area.getX() + area.getWidth() * 0.75);
180     final float midY = (float) (area.getY() + area.getHeight() * 0.5);
181 
182     final GeneralPath icon = new GeneralPath();
183     icon.moveTo(minX, midY);
184     icon.lineTo(maxX, midY);
185     return icon;
186   }
187 
188   /**
189    * Removes the currently selected label on button click.
190    */
191   protected void buttonClicked(final NodeRealizer context, final Graph2DView view) {
192     if (isEnabled(context) && getButtonOpacity(context) == UmlRealizerFactory.OPAQUE) {
193       final Graph2D graph = view.getGraph2D();
194       graph.firePreEvent();
195       try {
196         graph.backupRealizers(new NodeList(context.getNode()).nodes());
197         graph.backupRealizers(context.getNode().edges());
198         final UmlClassAnimation animation = new RemoveItemAnimation(view, context, true);
199         animation.play();
200       } finally {
201         graph.firePostEvent();
202       }
203       view.updateView();
204     }
205   }
206 
207   /** Sets an appropriate style property of the given context to notify that the mouse is currently over this button. */
208   protected void buttonEntered(final NodeRealizer context, final Graph2DView view) {
209     if (isAttributeSection) {
210       UmlRealizerFactory.setMouseOverButton(context, UmlRealizerFactory.BUTTON_SUB_ATTRIBUTE);
211     } else {
212       UmlRealizerFactory.setMouseOverButton(context, UmlRealizerFactory.BUTTON_SUB_OPERATION);
213     }
214     view.updateView(getButtonArea(context));
215   }
216 
217   /**
218    * Animates the removing of an attribute or an operation.
219    */
220   static class RemoveItemAnimation extends UmlClassAnimation  {
221 
222     RemoveItemAnimation(final Graph2DView view, final NodeRealizer context, final boolean isClosing) {
223       super(view, context, isClosing);
224     }
225 
226     /**
227      * Opening is not possible.
228      */
229     protected void open() {
230     }
231 
232     /**
233      * Closing means removing the selected label.
234      */
235     protected void close() {
236       UmlClassLabelSupport.removeSelectedLabel(context);
237     }
238 
239     /**
240      * Returns the size of the realizer without the selected label.
241      */
242     protected YDimension getClosedSize() {
243       final NodeLabel selectedLabel = UmlClassLabelSupport.getSelectedLabel(context);
244       return new YDimension(context.getWidth(), context.getHeight() - selectedLabel.getHeight());
245     }
246 
247     /**
248      * Returns the upper y-coordinate of the selected label.
249      */
250     protected double getFixedY() {
251       final NodeLabel label = UmlClassLabelSupport.getSelectedLabel(context);
252       return label.getLocation().getY();
253     }
254 
255     /**
256      * Returns the lower y-coordinate of the selected label.
257      */
258     protected double getMovingY() {
259       final NodeLabel label = UmlClassLabelSupport.getSelectedLabel(context);
260       return label.getLocation().getY() + label.getHeight();
261     }
262   }
263 }
264