1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.g2d.nodes;
\r
14 import java.awt.BasicStroke;
\r
15 import java.awt.Color;
\r
16 import java.awt.Font;
\r
17 import java.awt.FontMetrics;
\r
18 import java.awt.Graphics2D;
\r
19 import java.awt.RenderingHints;
\r
20 import java.awt.Shape;
\r
21 import java.awt.Stroke;
\r
22 import java.awt.font.FontRenderContext;
\r
23 import java.awt.font.TextLayout;
\r
24 import java.awt.geom.AffineTransform;
\r
25 import java.awt.geom.Point2D;
\r
26 import java.awt.geom.Rectangle2D;
\r
27 import java.util.Arrays;
\r
29 import org.simantics.scenegraph.g2d.G2DNode;
\r
30 import org.simantics.scenegraph.utils.GeometryUtils;
\r
32 public class FlagNode extends G2DNode {
\r
34 private static final long serialVersionUID = -1716729504104107151L;
\r
36 private static final AffineTransform IDENTITY = new AffineTransform();
\r
38 private static final byte LEADING = 0;
\r
39 private static final byte TRAILING = 1;
\r
40 private static final byte CENTER = 2;
\r
42 private static final boolean DEBUG = false;
\r
44 private static final double GLOBAL_SCALE = 0.1;
\r
46 private static final double TEXT_MARGIN = 5;
\r
48 static transient final BasicStroke STROKE = new BasicStroke(0.25f, BasicStroke.CAP_BUTT,
\r
49 BasicStroke.JOIN_MITER);
\r
51 final transient Font FONT = Font.decode("Arial 12");
\r
53 protected boolean visible;
\r
55 protected Shape flagShape;
\r
56 protected String[] flagText;
\r
57 protected Stroke stroke;
\r
58 protected Color border;
\r
59 protected Color fill;
\r
60 protected Color textColor;
\r
61 protected float width;
\r
62 protected float height;
\r
63 protected double direction; // in radians
\r
64 protected float beakAngle;
\r
65 protected Rectangle2D textArea;
\r
66 protected byte hAlign;
\r
67 protected byte vAlign;
\r
69 private transient final Point2D origin = new Point2D.Double();
\r
70 private transient final Point2D xa = new Point2D.Double();
\r
71 private transient final Point2D ya = new Point2D.Double();
\r
73 protected transient TextLayout[] textLayout = null;
\r
74 protected transient Rectangle2D[] rects = null;
\r
75 protected transient float textHeight = 0;
\r
76 protected transient float lastViewScale = 0;
\r
78 @SyncField("visible")
\r
79 public void setVisible(boolean visible) {
\r
80 this.visible = visible;
\r
83 public boolean isVisible() {
\r
87 @SyncField({"visible", "flagShape", "flagText", "stroke", "border", "fill", "textColor", "width", "height", "direction", "beakAngle", "textSize", "hAlign", "vAlign"})
\r
88 public void init(Shape flagShape, String[] flagText, Stroke stroke, Color border, Color fill, Color textColor, float width, float height, double direction, float beakAngle, Rectangle2D textArea, int hAlign, int vAlign) {
\r
89 this.visible = true;
\r
90 this.flagShape = flagShape;
\r
91 this.flagText = flagText;
\r
92 this.stroke = stroke;
\r
93 this.border = border;
\r
95 this.textColor = textColor;
\r
97 this.height = height;
\r
98 this.direction = direction;
\r
99 this.beakAngle = beakAngle;
\r
100 this.textArea = textArea;
\r
101 this.hAlign = (byte) hAlign;
\r
102 this.vAlign = (byte) vAlign;
\r
107 private void resetCaches() {
\r
113 public void render(Graphics2D g) {
\r
118 System.out.println("FlagNode.render:");
\r
119 System.out.println("\tflagShape: " + flagShape);
\r
120 System.out.println("\tflagText: " + Arrays.toString(flagText));
\r
121 System.out.println("\tstroke: " + stroke);
\r
122 System.out.println("\tborder: " + border);
\r
123 System.out.println("\tfill: " + fill);
\r
124 System.out.println("\ttextColor: " + textColor);
\r
125 System.out.println("\twidth: " + width);
\r
126 System.out.println("\theight: " + height);
\r
127 System.out.println("\tdirection: " + direction);
\r
128 System.out.println("\tbeakAngle: " + beakAngle);
\r
129 System.out.println("\ttextArea: " + textArea);
\r
130 System.out.println("\thAlign: " + hAlign);
\r
131 System.out.println("\tvAlign: " + vAlign);
\r
132 System.out.println("\tdraw: " + visible);
\r
135 AffineTransform ot = g.getTransform();
\r
136 g.transform(transform);
\r
139 Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);
\r
141 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
\r
143 // Paint flag shape
\r
146 g.setStroke(stroke);
\r
147 g.setColor(border);
\r
150 // Speed rendering optimization: don't draw text that is too small to read
\r
151 if (renderingHint != RenderingHints.VALUE_RENDER_QUALITY) {
\r
152 double viewScale = GeometryUtils.getScale(ot);
\r
153 viewScale *= GeometryUtils.getScale(transform);
\r
154 if (viewScale < 4.0)
\r
158 if (flagText == null || flagText.length == 0)
\r
162 g.setColor(Color.RED);
\r
169 g.setColor(textColor);
\r
171 AffineTransform orig = g.getTransform();
\r
173 double det = orig.getDeterminant();
\r
175 System.out.println("DETERMINANT: " + det);
\r
178 // Invert the Y-axis if the symbol is "flipped" either vertically xor horizontally
\r
179 origin.setLocation(textArea.getMinX(), textArea.getMaxY());
\r
180 xa.setLocation(textArea.getMaxX(), textArea.getMaxY());
\r
181 ya.setLocation(textArea.getMinX(), textArea.getMinY());
\r
183 origin.setLocation(textArea.getMinX(), textArea.getMinY());
\r
184 xa.setLocation(textArea.getMaxX(), textArea.getMinY());
\r
185 ya.setLocation(textArea.getMinX(), textArea.getMaxY());
\r
188 orig.transform(origin, origin);
\r
189 orig.transform(xa, xa);
\r
190 orig.transform(ya, ya);
\r
192 double xAxisX = xa.getX() - origin.getX();
\r
193 double xAxisY = xa.getY() - origin.getY();
\r
194 double yAxisX = ya.getX() - origin.getX();
\r
195 double yAxisY = ya.getY() - origin.getY();
\r
197 boolean needToFlip = xAxisX < 0 || yAxisY < 0;
\r
199 System.out.println("TEXT NEEDS FLIPPING: " + needToFlip);
\r
201 byte horizAlign = hAlign;
\r
204 // Okay, the text would be upside-down if rendered directly with these axes.
\r
205 // Let's flip the origin to the diagonal point and
\r
206 // invert both x & y axis of the text area to get
\r
207 // the text the right way around. Also, horizontal alignment
\r
208 // needs to be switched unless it's centered.
\r
209 origin.setLocation(origin.getX() + xAxisX + yAxisX, origin.getY() + xAxisY + yAxisY);
\r
215 // Must flip horizontal alignment to keep text visually at the same
\r
217 if (horizAlign == LEADING)
\r
218 horizAlign = TRAILING;
\r
219 else if (horizAlign == TRAILING)
\r
220 horizAlign = LEADING;
\r
223 final double gScale = GLOBAL_SCALE;
\r
224 final double gScaleRecip = 1.0 / gScale;
\r
225 final double scale = GeometryUtils.getMaxScale(orig) * gScale;
\r
226 final double rotation = Math.atan2(xAxisY, xAxisX);
\r
227 g.setTransform(IDENTITY);
\r
228 g.translate(origin.getX(), origin.getY());
\r
229 g.rotate(rotation);
\r
230 g.scale(scale, scale);
\r
233 System.out.println("ORIGIN: " + origin);
\r
234 System.out.println("X-AXIS: (" + xAxisX + "," + xAxisY + ")");
\r
235 System.out.println("Y-AXIS: (" + yAxisX + "," + yAxisY + ")");
\r
236 System.out.println("rotation: " + Math.toDegrees(rotation));
\r
237 System.out.println("scale: " + scale);
\r
238 System.out.println("ORIG transform: " + orig);
\r
239 System.out.println("transform: " + g.getTransform());
\r
242 FontMetrics fm = g.getFontMetrics(f);
\r
243 double fontHeight = fm.getHeight();
\r
245 if (textLayout == null || (float) scale != lastViewScale)
\r
247 lastViewScale = (float) scale;
\r
248 FontRenderContext frc = g.getFontRenderContext();
\r
249 if (textLayout == null)
\r
250 textLayout = new TextLayout[flagText.length];
\r
252 rects = new Rectangle2D[flagText.length];
\r
254 for (int i = 0; i < flagText.length; ++i) {
\r
255 String txt = flagText[i].isEmpty() ? " " : flagText[i];
\r
256 textLayout[i] = new TextLayout(txt, f, frc);
\r
257 rects[i] = textLayout[i].getBounds();
\r
259 // If the bb height is not overridden with the font height
\r
260 // text lines will not be drawn in the correct Y location.
\r
261 rects[i].setRect(rects[i].getX(), rects[i].getY(), rects[i].getWidth(), fontHeight);
\r
263 textHeight += rects[i].getHeight() * gScale;
\r
265 System.out.println(" bounding rectangle for line " + i + " '" + flagText[i] + "': " + rects[i]);
\r
269 double leftoverHeight = textArea.getHeight() - textHeight;
\r
270 if (leftoverHeight < 0)
\r
271 leftoverHeight = 0;
\r
274 System.out.println("text area height: " + textArea.getHeight());
\r
275 System.out.println("total text height: " + textHeight);
\r
276 System.out.println("leftover height: " + leftoverHeight);
\r
279 double lineDist = 0;
\r
285 System.out.println("VERTICAL LEADING");
\r
286 lineDist = leftoverHeight / flagText.length;
\r
287 startY = fm.getMaxAscent();
\r
291 System.out.println("VERTICAL TRAILING");
\r
292 lineDist = leftoverHeight / flagText.length;
\r
293 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
\r
297 System.out.println("VERTICAL CENTER");
\r
298 lineDist = leftoverHeight / (flagText.length + 1);
\r
299 startY = fm.getMaxAscent() + lineDist * gScaleRecip;
\r
304 System.out.println("lineDist: " + lineDist);
\r
305 System.out.println("startY: " + startY);
\r
308 lineDist *= gScaleRecip;
\r
310 double textAreaWidth = textArea.getWidth() * gScaleRecip;
\r
312 for (int i = 0; i < flagText.length; ++i) {
\r
313 //String line = flagText[i];
\r
314 Rectangle2D rect = rects[i];
\r
318 switch (horizAlign) {
\r
321 System.out.println("HORIZ LEADING: " + rect);
\r
326 System.out.println("HORIZ TRAILING: " + rect);
\r
327 x = textAreaWidth - rect.getWidth() - TEXT_MARGIN;;
\r
331 System.out.println("HORIZ CENTER: " + rect);
\r
332 x = textAreaWidth * 0.5 - rect.getWidth()*0.5;
\r
337 System.out.println(" X, Y: " + x + ", " + y);
\r
340 System.out.println(" DRAW: '" + flagText[i] + "' with " + g.getTransform());
\r
342 //textLayout[i].draw(g, (float) x, (float) y);
\r
343 g.drawString(flagText[i], (float) x, (float) y);
\r
346 y += rect.getHeight();
\r
350 g.setTransform(ot);
\r
354 public static double getBeakLength(double height, double beakAngle) {
\r
355 beakAngle = Math.min(180, Math.max(10, beakAngle));
\r
356 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
\r
360 public Rectangle2D getBoundsInLocal() {
\r
361 if (flagShape == null)
\r
363 return flagShape.getBounds2D();
\r