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.geom.YPoint;
33  import y.view.Graph2D;
34  import y.view.Graph2DView;
35  import y.view.LineType;
36  import y.view.NodeLabel;
37  import y.view.NodeRealizer;
38  
39  import java.awt.AlphaComposite;
40  import java.awt.Color;
41  import java.awt.Composite;
42  import java.awt.Graphics2D;
43  import java.awt.RenderingHints;
44  import java.awt.Shape;
45  import java.awt.geom.Ellipse2D;
46  import java.awt.geom.GeneralPath;
47  import java.awt.geom.Rectangle2D;
48  import java.beans.PropertyChangeEvent;
49  import java.beans.PropertyChangeListener;
50  
51  /**
52   * An {@link UmlClassButton} that adds a new attribute or operation on mouse click.
53   */
54  class UmlClassAddItemButton extends UmlClassButton{
55    public static final double ICON_SIZE = 12;
56    public static final double ICON_GAP = 5;
57  
58    private final boolean isAttributeSection;
59  
60    /**
61     * Creates an add button for the given section.
62     */
63    public UmlClassAddItemButton(final boolean attributeSection) {
64      isAttributeSection = attributeSection;
65    }
66  
67    /**
68     * Paints the button with its icon.
69     */
70    public void paint(final NodeRealizer context, final Graphics2D graphics) {
71      if (isVisible(context)) {
72        // Set the opacity of the button.
73        final Composite orgComposite = graphics.getComposite();
74        final AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getButtonOpacity(context));
75        graphics.setComposite(composite);
76  
77        final Rectangle2D area = getButtonArea(context);
78        paintBackground(context, graphics, area);
79        paintIcon(graphics, area);
80  
81        graphics.setComposite(orgComposite);
82      }
83    }
84  
85    /**
86     * Checks whether or not the button is currently visible. The button is not visible if the corresponding section is
87     * not opened.
88     */
89    protected boolean isVisible(final NodeRealizer context) {
90      final boolean areClassDetailsVisible = UmlClassLabelSupport.getModel(context).areSectionsVisible();
91      final boolean isSectionVisible =
92          isAttributeSection && UmlClassLabelSupport.getModel(context).areAttributesVisible() ||
93          !isAttributeSection && UmlClassLabelSupport.getModel(context).areOperationsVisible();
94      return isSectionVisible && areClassDetailsVisible;
95    }
96  
97    /**
98     * Returns the area where to paint the button with its icon.
99     */
100   protected Rectangle2D getButtonArea(final NodeRealizer context) {
101     final NodeLabel label = getCaptionLabel(context);
102     return new Rectangle2D.Double(
103         context.getX() + context.getWidth() - 2* ICON_SIZE - 2* ICON_GAP,
104         label.getLocation().getY() + (label.getHeight() - ICON_SIZE) * 0.5,
105         ICON_SIZE,
106         ICON_SIZE);
107   }
108 
109   /** Returns the caption label of the section that corresponds to this button. */
110   private NodeLabel getCaptionLabel(final NodeRealizer context) {
111     if (isAttributeSection) {
112       return UmlClassLabelSupport.getAttributeCaptionLabel(context);
113     } else {
114       return UmlClassLabelSupport.getOperationCaptionLabel(context);
115     }
116   }
117 
118   /**
119    * Paints the button into the given area.
120    */
121   private void paintBackground(final NodeRealizer context, final Graphics2D graphics, final Rectangle2D area) {
122     final Color color = isMouseOverButton(context) ?
123         UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_ACTIVE :
124         UmlRealizerFactory.COLOR_BUTTON_BACKGROUND_BLANK;
125     final Shape shape = new Ellipse2D.Double(area.getX(), area.getY(), area.getWidth(), area.getHeight());
126     graphics.setColor(color);
127     graphics.fill(shape);
128   }
129 
130   /** Returns the opacity of this button. */
131   private float getButtonOpacity(final NodeRealizer context) {
132     if (isAttributeSection) {
133       return UmlRealizerFactory.getAttributeButtonOpacity(context);
134     } else {
135       return UmlRealizerFactory.getOperationButtonOpacity(context);
136     }
137   }
138 
139   /**
140    * Checks whether or not the mouse is currently over this button. The button where the mouse is currently over is
141    * stored as an appropriate style property of the given context.
142    */
143   private boolean isMouseOverButton(final NodeRealizer context) {
144     final int button = UmlRealizerFactory.getMouseOverButton(context);
145     return ((button == UmlRealizerFactory.BUTTON_ADD_ATTRIBUTE) && isAttributeSection) ||
146            ((button == UmlRealizerFactory.BUTTON_ADD_OPERATION) && !isAttributeSection);
147   }
148 
149   /**
150    * Paints the icon into the given area. Anti-aliasing is temporary disabled for nicer visualization of the icon.
151    */
152   private void paintIcon(final Graphics2D graphics, final Rectangle2D area) {
153     final Object orgRenderingHint = graphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
154     graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
155     try {
156       final Shape shape = getIconShape(area);
157       final Color color = UmlRealizerFactory.COLOR_BUTTON_FOREGROUND_ENABLED;
158       graphics.setColor(color);
159       graphics.setStroke(LineType.LINE_1);
160       graphics.draw(shape);
161     } finally {
162       graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, orgRenderingHint);
163     }
164   }
165 
166   /**
167    * Returns the shape of the icon.
168    */
169   private Shape getIconShape(final Rectangle2D area) {
170     final float minX = (float) (area.getX() + area.getWidth() * 0.25);
171     final float midX = (float) (area.getX() + area.getWidth() * 0.5);
172     final float maxX = (float) (area.getX() + area.getWidth() * 0.75);
173     final float minY = (float) (area.getY() + area.getHeight() * 0.25);
174     final float midY = (float) (area.getY() + area.getHeight() * 0.5);
175     final float maxY = (float) (area.getY() + area.getHeight() * 0.75);
176 
177     final GeneralPath icon = new GeneralPath();
178     icon.moveTo(minX, midY);
179     icon.lineTo(maxX, midY);
180     icon.moveTo(midX, minY);
181     icon.lineTo(midX, maxY);
182     return icon;
183   }
184 
185   /**
186    * Adds a new list item at the end of the attribute or operation list on button click.
187    */
188   protected void buttonClicked(final NodeRealizer context, final Graph2DView view) {
189     if (getButtonOpacity(context) == UmlRealizerFactory.OPAQUE) {
190       final Graph2D graph = view.getGraph2D();
191       graph.firePreEvent();
192       try {
193         // Add a new label.
194         graph.backupRealizers(new NodeList(context.getNode()).nodes());
195         graph.backupRealizers(context.getNode().edges());
196         final UmlClassAnimation animation = new AddItemAnimation(view, context, false);
197         animation.play();
198 
199         // Open the new label in an editor.
200         final NodeLabel label = UmlClassLabelSupport.getSelectedLabel(context);
201         final YPoint location = label.getTextLocation();
202         view.openLabelEditor(label, location.getX(), location.getY(), new TextChangeHandler(view), true, true);
203 
204       } finally {
205         graph.firePostEvent();
206       }
207     }
208   }
209 
210   /** Sets an appropriate style property of the given context to notify that the mouse is currently over this button. */
211   protected void buttonEntered(final NodeRealizer context, final Graph2DView view) {
212     if (isAttributeSection) {
213       UmlRealizerFactory.setMouseOverButton(context, UmlRealizerFactory.BUTTON_ADD_ATTRIBUTE);
214     } else {
215       UmlRealizerFactory.setMouseOverButton(context, UmlRealizerFactory.BUTTON_ADD_OPERATION);
216     }
217     view.updateView(getButtonArea(context));
218   }
219 
220   /**
221    * This handler listens for text changes and adjusts the node size to
222    * the label size.
223    */
224   private class TextChangeHandler implements PropertyChangeListener {
225     private final Graph2DView view;
226 
227     public TextChangeHandler(final Graph2DView view) {
228       this.view = view;
229     }
230 
231     /**
232      * After the label editor has been closed the changed text of the label must be updated in the {@link UmlClassModel
233      * model}. Empty labels will be removed.
234      */
235     public void propertyChange(PropertyChangeEvent e) {
236       final Object source = e.getSource();
237       if (source instanceof NodeLabel) {
238         final NodeLabel label = (NodeLabel) source;
239         final NodeRealizer realizer = view.getGraph2D().getRealizer(label.getNode());
240 
241         // Update model.
242         UmlClassLabelSupport.updateLabelText(realizer, label);
243         UmlClassLabelSupport.selectLabel(realizer, label);
244 
245         // Remove empty label.
246         if ("".equals(label.getText())) {
247           label.setText(" ");
248           final UmlClassAnimation animation = new UmlClassRemoveItemButton.RemoveItemAnimation(view, realizer, true);
249           animation.play();
250         } else {
251           UmlClassLabelSupport.updateRealizerSize(realizer);
252         }
253       }
254     }
255   }
256 
257   /**
258    * Animates the adding of an attribute or operation.
259    */
260   private class AddItemAnimation extends UmlClassAnimation  {
261     final double closedHeight;
262 
263     private AddItemAnimation(final Graph2DView view, final NodeRealizer context, final boolean isClosing) {
264       super(view, context, isClosing);
265       closedHeight = context.getHeight();
266     }
267 
268     /** Opening means adding an additional attribute or operation label. */
269     protected void open() {
270       if (isAttributeSection) {
271         UmlClassLabelSupport.addAttribute(context);
272       } else {
273         UmlClassLabelSupport.addOperation(context);
274       }
275     }
276 
277     /**
278      * Closing is not possible.
279      */
280     protected void close() {
281     }
282 
283     /**
284      * Returns the size of the realizer without the additional label.
285      */
286     protected YDimension getClosedSize() {
287       return new YDimension(context.getWidth(), closedHeight);
288     }
289 
290     /**
291      * Returns the upper y-coordinate of the selected label.
292      */
293     protected double getFixedY() {
294       final NodeLabel label = UmlClassLabelSupport.getSelectedLabel(context);
295       return label.getLocation().getY();
296     }
297 
298     /**
299      * Returns the lower y-coordinate of the selected label.
300      */
301     protected double getMovingY() {
302       final NodeLabel label = UmlClassLabelSupport.getSelectedLabel(context);
303       return label.getLocation().getY() + label.getHeight();
304     }
305   }
306 
307 }
308