| ErdNodePainter.java |
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