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.entityrelationship.painters;
29  
30  import y.geom.YRectangle;
31  import y.layout.NodeLabelModel;
32  import y.view.GenericNodeRealizer;
33  import y.view.NodeLabel;
34  import y.view.NodeRealizer;
35  import y.view.ShapeNodePainter;
36  import y.view.YRenderingHints;
37  
38  import java.awt.Color;
39  import java.awt.GradientPaint;
40  import java.awt.Graphics2D;
41  import java.awt.Paint;
42  import java.awt.Shape;
43  import java.awt.Stroke;
44  import java.awt.geom.Line2D;
45  import java.awt.geom.Rectangle2D;
46  
47  /**
48   * This painter draws a node in ERD style that is used in Crow's Foot Notation.
49   *
50   * The node consists of a name label and an attributes label separated by a line
51   * and different background fillings. The painter assumes that an <code>ErdAttributesNodeLabelModel</code>
52   * is used for the attribute label.
53   * @see ErdAttributesNodeLabelModel
54   */
55  public class ErdNodePainter implements GenericNodeRealizer.ContainsTest, GenericNodeRealizer.Painter {
56    /** Shape type constant. Specifies a rectangular shape. */
57    public static final byte RECT = 0;
58  
59    /** Shape type constant. Specifies a rectangular shape whose corners are rounded. */
60    public static final byte ROUND_RECT = 1;
61  
62    /** Implementation for an ErdNodePainter */
63    private final ErdNodePainterImpl painter;
64  
65    /** Creates a new <code>ErdNodePainter</code> with rounded corners */
66    public ErdNodePainter() {
67      this(ROUND_RECT);
68    }
69  
70    /**
71     * Creates a new <code>ErdNodePainter</code>
72     * @param type The type of the rectangle
73     * @throws IllegalArgumentException If type is not <code>RECT</code> or <code>ROUND_RECT</code>
74     * @see #RECT
75     * @see #ROUND_RECT
76     */
77    public ErdNodePainter(final byte type) {
78      switch (type) {
79        case RECT:
80        case ROUND_RECT:
81          break;
82        default:
83          throw new IllegalArgumentException("Illegal type: " + type);
84      }
85  
86      painter = new ErdNodePainterImpl(type);
87    }
88  
89    /**
90     * Paints the ERD entity node.
91     * @param context The context node
92     * @param graphics The graphics object of the component
93     */
94    public void paint( final NodeRealizer context, final Graphics2D graphics ) {
95      painter.paint(context, graphics);
96    }
97  
98    /**
99     * Paints the ERD entity node in a sloppy way.
100    * @param context The context node
101    * @param graphics The graphics object of the component
102    */
103   public void paintSloppy( final NodeRealizer context, final Graphics2D graphics ) {
104     painter.paintSloppy(context, graphics);
105   }
106 
107 
108   /**
109    * Checks if the coordinates <code>(x,y)</code> are within the node borders.
110    * @param context The context node
111    * @param x The x-coordinate
112    * @param y The y-coordinate
113    * @return <code>true</code> if node contains (x,y), <code>false</code> otherwise
114    */
115   public boolean contains( final NodeRealizer context, final double x, final double y ) {
116     return painter.contains(context, x, y);
117   }
118 
119   /**
120    * Calculates the y-coordinate of the line that separates the name and attributes label.
121    * @param label Name label that is above the line
122    * @return y-coordinate of the separator line
123    */
124   static double separator( final NodeLabel label ) {
125     final YRectangle lr = label.getBox();
126     return lr.getY() + lr.getHeight() + Math.max(0, label.getDistance());
127   }
128 
129   /**
130    * Checks if ERD style should be used.
131    * That means the background color of the default label is used to paint the name compartment.
132    * Additionally, a separator line between the name and the attributes compartment is drawn.
133    * @param context The context node
134    * @return <code>true</code> if there is a label on top of the node, <code>false</code> otherwise
135    */
136   static boolean useErdStyle( final NodeRealizer context ) {
137     if (context.labelCount() > 0) {
138       final NodeLabel nl = context.getLabel();
139       if (NodeLabel.INTERNAL == nl.getModel()) {
140         final byte p = nl.getPosition();
141         return NodeLabel.TOP == p ||
142                NodeLabel.TOP_LEFT == p  ||
143                NodeLabel.TOP_RIGHT == p;
144       }
145     }
146 
147     return false;
148   }
149 
150 
151   /**
152    * A customized {@link ShapeNodePainter} implementation for ERD nodes.
153    */
154   private static final class ErdNodePainterImpl extends ShapeNodePainter {
155 
156     /**
157      * Creates a new <code>ErdNodePainterImpl</code>.
158      * @param type The type shape for the node
159      * @see #RECT
160      * @see #ROUND_RECT
161      */
162     ErdNodePainterImpl( final byte type ) {
163       super(type);
164     }
165 
166     /**
167      * Paints the node shape with the two labels.
168      * @param context the node context
169      * @param graphics the graphics object
170      */
171     public void paint( final NodeRealizer context, final Graphics2D graphics ) {
172 
173       // workaround for the fact that the position of the attributes label depends
174       // on the position of the name label.
175       if (context.labelCount() > 0) {
176         for (int i = 0, n = context.labelCount(); i < n; ++i) {
177           final NodeLabel nl = context.getLabel(i);
178           final NodeLabelModel model = nl.getLabelModel();
179           if (model instanceof ErdAttributesNodeLabelModel) {
180             nl.setOffsetDirty();
181           }
182         }
183       }
184 
185       super.paint(context, graphics);
186     }
187 
188     /**
189      * Paints the interior of the node
190      * @param context the node context
191      * @param graphics the graphics object
192      * @param shape the shape to be drawn
193      */
194     protected void paintFilledShape(
195             final NodeRealizer context,
196             final Graphics2D graphics,
197             final Shape shape
198     ) {
199       if (useErdStyle(context)) {
200         // paint in ERD style
201         final NodeLabel label = context.getLabel();
202         final double y = separator(label);
203 
204         final Shape oldClip = graphics.getClip();
205 
206         final Rectangle2D cb;
207         if (oldClip != null) {
208           cb = oldClip.getBounds2D();
209         } else {
210           cb = new Rectangle2D.Double(context.getX() - 10, context.getY() - 10,
211               context.getWidth() + 20, context.getHeight() + 20);
212         }
213 
214         // clip area for attributes on bottom and fill it with paint
215         final Rectangle2D.Double rectangle = new Rectangle2D.Double();
216         rectangle.setFrame(
217                 cb.getX(),
218                 y,
219                 cb.getWidth(),
220                 cb.getY() + cb.getHeight() - y);
221         graphics.clip(rectangle);
222         super.paintFilledShape(context, graphics, shape);
223         graphics.setClip(oldClip);
224 
225         final Color oldColor = graphics.getColor();
226 
227         // clip area for entity name on top and fill it with paint
228         rectangle.setFrame(
229             cb.getX(),
230             cb.getY(),
231             cb.getWidth(),
232             y - cb.getY());
233         graphics.clip(rectangle);
234         graphics.setColor(label.getBackgroundColor());
235         graphics.fill(shape);
236 
237         graphics.setColor(oldColor);
238         graphics.setClip(oldClip);
239       } else {
240         // if no ERD node, paint shape
241         super.paintFilledShape(context, graphics, shape);
242       }
243     }
244 
245     /**
246      * Paints border and separator of the entity node.
247      * @param context  the node realizer that this painter is associated with.
248      * @param graphics the graphics context.
249      * @param shape    the shape that shall be painted.
250      */
251     protected void paintShapeBorder(
252             final NodeRealizer context,
253             final Graphics2D graphics,
254             final Shape shape
255     ) {
256       // Paint the border of the node
257       super.paintShapeBorder(context, graphics, shape);
258 
259       // If ERD node, draw separator line
260       if (useErdStyle(context)) {
261         final double y = separator(context.getLabel());
262 
263         // Only draw line, if separator lies within node
264         final double min = context.getY();
265         if (min < y && y < min + context.getHeight()) {
266           final double x = context.getX();
267 
268           // Draw line with line type of context
269           Color lc = getLineColor(context, useSelectionStyle(context, graphics));
270           if (lc != null) {
271             Stroke oldStroke = graphics.getStroke();
272             graphics.setStroke(context.getLineType());
273             graphics.setColor(lc);
274 
275             final Line2D.Double line = new Line2D.Double();
276             line.setLine(x, y, x + context.getWidth(), y);
277             graphics.draw(line);
278 
279             graphics.setStroke(oldStroke);
280           }
281         }
282       }
283     }
284 
285     /**
286      * Retrieves the paint to fill the interior of the node.
287      * @param context the context node
288      * @param selected whether the node is currently selected
289      * @return the current <code>Paint</code>
290      */
291     protected Paint getFillPaint(
292             final NodeRealizer context,
293             final boolean selected
294     ) {
295       // If there are two fill colors set, create a <code>GradientPaint</code>
296       final Color fc1 = getFillColor(context, selected);
297       if (fc1 != null) {
298         final Color fc2 = getFillColor2(context, selected);
299         if (fc2 != null) {
300           final double x = context.getX();
301           final double y = context.getY();
302 
303           final double _y =
304                   useErdStyle(context)
305                   ? separator(context.getLabel())
306                   : y;
307 
308           return new GradientPaint(
309                   (float) x,
310                   (float) _y,
311                   fc1,
312                   (float) (x + context.getWidth()),
313                   (float) (y + context.getHeight()),
314                   fc2,
315                   true);
316         } else {
317           return fc1;
318         }
319       } else {
320         return null;
321       }
322     }
323 
324     /**
325      * Determines if the selection state should be respected while painting.
326      * @param context The context node
327      * @param gfx graphics object
328      * @return <code>true</code> if selection style is successfully applied, <code>false</code> otherwise
329      */
330     static boolean useSelectionStyle(
331             final NodeRealizer context,
332             final Graphics2D gfx
333     ) {
334       return context.isSelected() && YRenderingHints.isSelectionPaintingEnabled(gfx);
335     }
336   }
337 }
338