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