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.AbstractCustomNodePainter;
32  import y.view.GenericNodeRealizer;
33  import y.view.Graph2DView;
34  import y.view.HitInfo;
35  import y.view.MouseInputEditor;
36  import y.view.NodeLabel;
37  import y.view.NodeRealizer;
38  import y.view.YRenderingHints;
39  
40  import java.awt.AlphaComposite;
41  import java.awt.Color;
42  import java.awt.Composite;
43  import java.awt.Graphics2D;
44  import java.awt.Shape;
45  import java.awt.Stroke;
46  import java.awt.geom.AffineTransform;
47  import java.awt.geom.Rectangle2D;
48  
49  /**
50   * This configuration paints the UML class realizer.
51   */
52  class UmlClassConfiguration extends AbstractCustomNodePainter
53      implements GenericNodeRealizer.GenericMouseInputEditorProvider,
54        GenericNodeRealizer.GenericSizeConstraintProvider {
55     private UmlClassButton[] buttons;
56  
57    public UmlClassConfiguration() {
58      buttons = new UmlClassButton[] {
59          new UmlClassOpenCloseSectionButton(true),
60          new UmlClassOpenCloseSectionButton(false),
61          new UmlClassOpenCloseClassButton(),
62          new UmlClassAddItemButton(true),
63          new UmlClassAddItemButton(false),
64          new UmlClassRemoveItemButton(true),
65          new UmlClassRemoveItemButton(false)
66      };
67    }
68  
69    /**
70     * Paints the realizer with its name area, attribute and operation sections.
71     */
72    protected void paintNode(final NodeRealizer context, final Graphics2D graphics, final boolean sloppy) {
73      final Color fillColor = context.getFillColor();
74      final Color fillColor2 = context.getFillColor2();
75      final Color lineColor = context.getLineColor();
76      final boolean selected = useSelectionStyle(context, graphics);
77  
78      // Set the opacity of the realizer.
79      final Composite orgComposite = graphics.getComposite();
80      final AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, UmlRealizerFactory.getNodeOpacity(context));
81      graphics.setComposite(composite);
82  
83      // Paint background.
84      final Rectangle2D.Double rect = context.getBoundingBox();
85      final Color backgroundColor = selected && sloppy ? createSelectionColor(fillColor) : fillColor;
86      graphics.setColor(backgroundColor);
87      graphics.fill(rect);
88  
89      // Paint area of the name.
90      final NodeLabel nameLabel = UmlClassLabelSupport.getNameLabel(context);
91      UmlClassLabelSupport.getLabelArea(context, nameLabel, rect);
92      final Color nameColor = selected && sloppy ? createSelectionColor(fillColor2) : fillColor2;
93      graphics.setColor(nameColor);
94      graphics.fill(rect);
95  
96      if (!sloppy) {
97        if (UmlClassLabelSupport.getModel(context).areSectionsVisible()) {
98  
99          // Paint area of the attribute caption.
100         final NodeLabel attributeCaptionLabel = UmlClassLabelSupport.getAttributeCaptionLabel(context);
101         UmlClassLabelSupport.getLabelArea(context, attributeCaptionLabel, rect);
102         graphics.setColor(fillColor2);
103         graphics.fill(rect);
104 
105         // Paint area of the operation caption.
106         final NodeLabel operationCaptionLabel = UmlClassLabelSupport.getOperationCaptionLabel(context);
107         UmlClassLabelSupport.getLabelArea(context, operationCaptionLabel, rect);
108         graphics.setColor(fillColor2);
109         graphics.fill(rect);
110 
111         // Paint area of the selected label.
112         final NodeLabel selectedLabel = UmlClassLabelSupport.getSelectedLabel(context);
113         if (selectedLabel != null) {
114           UmlClassLabelSupport.getLabelArea(context, selectedLabel, rect);
115           final Color selectedLabelColor = new Color(
116               lineColor.getRed(),
117               lineColor.getGreen(),
118               lineColor.getBlue(),
119               (int) (UmlRealizerFactory.getSelectionOpacity(context) * 255));
120           graphics.setColor(selectedLabelColor);
121           graphics.fill(rect);
122         }
123       }
124 
125       // Paint buttons.
126       for (int i = 0; i < buttons.length; i++) {
127         final UmlClassButton button = buttons[i];
128         button.paint(context, graphics);
129       }
130     }
131 
132     // Paint outline.
133     rect.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
134     final Color outlineColor = selected && sloppy ? createSelectionColor(fillColor2) : fillColor2;
135     graphics.setColor(outlineColor);
136     graphics.setStroke(context.getLineType());
137     graphics.draw(rect);
138 
139     graphics.setComposite(orgComposite);
140   }
141 
142   /**
143    * Paints the node in an animated state. The state of the animation is defined by the <code>state</code> that is an
144    * value between <code>0</code> and <code>1</code>. The value <code>0</code> means the section is completely closed,
145    * <code>1</code> means the section is completely open.
146    *
147    * @param context      the realizer to animate
148    * @param graphics     the graphics context
149    * @param fixedOffset  the lower y-coordinate of the part of the realizer that is fixed during the animation
150    * @param movingOffset the upper y-coordinate of the part of the realizer that moves during the animation
151    * @param state        the state of the animation. The value <code>0</code> means the section is completely closed,
152    *                     <code>1</code> means the section is completely open.
153    */
154   public void paintAnimatedNode(
155       final NodeRealizer context,
156       final Graphics2D graphics,
157       final double fixedOffset,
158       final double movingOffset,
159       final double state
160   ) {
161     final Shape clip = graphics.getClip();
162     final Color color = graphics.getColor();
163     final Stroke stroke = graphics.getStroke();
164     final AffineTransform transform = graphics.getTransform();
165     try {
166       paintAnimatedNodeImpl(context, graphics, fixedOffset, movingOffset, state);
167     } finally {
168       graphics.setClip(clip);
169       graphics.setColor(color);
170       graphics.setStroke(stroke);
171       graphics.setTransform(transform);
172     }
173   }
174 
175   private void paintAnimatedNodeImpl(
176           final NodeRealizer context,
177           final Graphics2D graphics,
178           final double fixedOffset,
179           final double movingOffset,
180           final double state
181   ) {
182     final double offset = fixedOffset + (movingOffset - fixedOffset) * state;
183     final double lineWidth = UmlRealizerFactory.LINE_EDGE_CREATION_BUTTON_OUTLINE.getLineWidth();
184 
185     // Paint the fix (upper) part of the node.
186     final Rectangle2D clipRect = new Rectangle2D.Double(
187         context.getX() - lineWidth * 0.5,
188         context.getY() - lineWidth * 0.5,
189         context.getWidth() + lineWidth,
190         offset - (context.getY() - lineWidth * 0.5));
191     graphics.setClip(clipRect);
192     paintNode(context, graphics, false);
193     paintText(context, graphics);
194 
195     // Paint the moving (lower) part of the node.
196     clipRect.setFrame(
197         context.getX() - lineWidth * 0.5,
198         offset,
199         context.getWidth() + lineWidth,
200         context.getY() + context.getHeight() + lineWidth * 0.5 - movingOffset);
201     graphics.setClip(clipRect);
202 
203     // Shift the lower part by offset.
204     graphics.translate(0, -(movingOffset - offset));
205     paintNode(context, graphics, false);
206     paintText(context, graphics);
207   }
208 
209   /**
210    * Checks whether or not selection style is used.
211    */
212   private static boolean useSelectionStyle(
213       final NodeRealizer context,
214       final Graphics2D gfx
215   ) {
216     return context.isSelected() && YRenderingHints.isSelectionPaintingEnabled(gfx);
217   }
218 
219   /**
220    * Returns an {@link MouseInputEditor} to handle mouse events for buttons of UML class nodes.
221    * */
222   public MouseInputEditor findMouseInputEditor(
223       final NodeRealizer context,
224       final Graph2DView view,
225       final double x,
226       final double y,
227       final HitInfo hitInfo
228   ) {
229     if (context instanceof GenericNodeRealizer && isDetailed(view)) {
230       for (int i = 0; i < buttons.length; i++) {
231         final UmlClassButton button = buttons[i];
232         if (button.contains(context, x, y)) {
233           return button.getMouseInputEditor(context, view);
234         }
235       }
236     }
237     return null;
238   }
239 
240   /**
241    * Checks whether or not sloppy painting is enabled.
242    */
243   private static boolean isDetailed(final Graph2DView view) {
244     return view.getZoom() > view.getPaintDetailThreshold();
245   }
246 
247   /**
248    * Returns the minimum size of the realizer. The minimum size contains all visible labels and buttons.
249    */
250   public YDimension getMinimumSize(final NodeRealizer context) {
251     double minY = context.getY();
252     double minX = context.getX();
253 
254     double maxY = getMaxYOfLabel(UmlClassLabelSupport.getNameLabel(context));
255     double maxX = getNameLabelMaxX(context);
256     if (UmlClassLabelSupport.getModel(context).areSectionsVisible()) {
257       maxX = Math.max(maxX, getCaptionLabelMaxX(UmlClassLabelSupport.getAttributeCaptionLabel(context)));
258       maxX = Math.max(maxX, getCaptionLabelMaxX(UmlClassLabelSupport.getOperationCaptionLabel(context)));
259       for (int i = 1; i < context.labelCount(); i++) {
260         final NodeLabel label = context.getLabel(i);
261         maxX = Math.max(maxX, getMaxXOfLabel(label));
262         maxY = Math.max(maxY, getMaxYOfLabel(label));
263       }
264     }
265     final double INSET_RIGHT = 10;
266     final double INSET_BOTTOM = UmlClassLabelSupport.getModel(context).areSectionsVisible() ? 5 : 0;
267     return new YDimension(maxX - minX + INSET_RIGHT, maxY - minY + INSET_BOTTOM);
268   }
269 
270   /**
271    * Returns the maximum x-coordinate of the given label.
272    */
273   private double getMaxXOfLabel(final NodeLabel label) {
274     return label.getLocation().getX() + label.getWidth();
275   }
276 
277   /**
278    * Returns the maximum y-coordinate of the given label.
279    */
280   private double getMaxYOfLabel(final NodeLabel label) {
281     return label.getLocation().getY() + label.getHeight();
282   }
283 
284   /**
285    * Returns the maximum x-coordinate of the name label including the open/close button.
286    */
287   private double getNameLabelMaxX(final NodeRealizer context) {
288     final NodeLabel label = UmlClassLabelSupport.getNameLabel(context);
289     return label.getLocation().getX() +
290            label.getWidth() +
291            UmlClassOpenCloseClassButton.ICON_SIZE +
292            UmlClassOpenCloseClassButton.ICON_GAP;
293   }
294 
295   /**
296    * Returns the maximum x-coordinate of the given caption label including the add/remove button.
297    */
298   private double getCaptionLabelMaxX(final NodeLabel label) {
299     return label.getLocation().getX() +
300            label.getWidth() +
301            UmlClassAddItemButton.ICON_SIZE +
302            UmlClassAddItemButton.ICON_GAP +
303            UmlClassRemoveItemButton.ICON_SIZE +
304            UmlClassRemoveItemButton.ICON_GAP;
305   }
306 
307   /**
308    * Returns the maximum size of the realizer.
309    */
310   public YDimension getMaximumSize(final NodeRealizer context) {
311     return new YDimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
312   }
313 
314 }
315