]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/FlagNode.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / FlagNode.java
diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/FlagNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/FlagNode.java
new file mode 100644 (file)
index 0000000..2d18495
--- /dev/null
@@ -0,0 +1,366 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.g2d.nodes;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Font;\r
+import java.awt.FontMetrics;\r
+import java.awt.Graphics2D;\r
+import java.awt.RenderingHints;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.font.FontRenderContext;\r
+import java.awt.font.TextLayout;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.Arrays;\r
+\r
+import org.simantics.scenegraph.g2d.G2DNode;\r
+import org.simantics.scenegraph.utils.GeometryUtils;\r
+\r
+public class FlagNode extends G2DNode {\r
+\r
+    private static final long          serialVersionUID = -1716729504104107151L;\r
+\r
+    private static final AffineTransform IDENTITY         = new AffineTransform();\r
+\r
+    private static final byte            LEADING          = 0;\r
+    private static final byte            TRAILING         = 1;\r
+    private static final byte            CENTER           = 2;\r
+\r
+    private static final boolean         DEBUG            = false;\r
+\r
+    private static final double          GLOBAL_SCALE     = 0.1;\r
+\r
+    private static final double          TEXT_MARGIN      = 5;\r
+\r
+    static transient final BasicStroke   STROKE           = new BasicStroke(0.25f, BasicStroke.CAP_BUTT,\r
+                                                                  BasicStroke.JOIN_MITER);\r
+\r
+    final transient Font                 FONT             = Font.decode("Arial 12");\r
+\r
+    protected boolean visible;\r
+\r
+    protected Shape flagShape;\r
+    protected String[] flagText;\r
+    protected Stroke stroke;\r
+    protected Color border;\r
+    protected Color fill;\r
+    protected Color textColor;\r
+    protected float width;\r
+    protected float height;\r
+    protected double direction; // in radians\r
+    protected float beakAngle;\r
+    protected Rectangle2D textArea;\r
+    protected byte hAlign;\r
+    protected byte vAlign;\r
+\r
+    private transient final Point2D      origin           = new Point2D.Double();\r
+    private transient final Point2D      xa               = new Point2D.Double();\r
+    private transient final Point2D      ya               = new Point2D.Double();\r
+\r
+    protected transient TextLayout[]     textLayout       = null;\r
+    protected transient Rectangle2D[]    rects            = null;\r
+    protected transient float            textHeight       = 0;\r
+    protected transient float            lastViewScale    = 0;\r
+\r
+    @SyncField("visible")\r
+    public void setVisible(boolean visible) {\r
+        this.visible = visible;\r
+    }\r
+\r
+    public boolean isVisible() {\r
+        return visible;\r
+    }\r
+\r
+    @SyncField({"visible", "flagShape", "flagText", "stroke", "border", "fill", "textColor", "width", "height", "direction", "beakAngle", "textSize", "hAlign", "vAlign"})\r
+    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
+        this.visible = true;\r
+        this.flagShape = flagShape;\r
+        this.flagText = flagText;\r
+        this.stroke = stroke;\r
+        this.border = border;\r
+        this.fill = fill;\r
+        this.textColor = textColor;\r
+        this.width = width;\r
+        this.height = height;\r
+        this.direction = direction;\r
+        this.beakAngle = beakAngle;\r
+        this.textArea = textArea;\r
+        this.hAlign =  (byte) hAlign;\r
+        this.vAlign = (byte) vAlign;\r
+\r
+        resetCaches();\r
+    }\r
+\r
+    private void resetCaches() {\r
+        textLayout = null;\r
+        rects = null;\r
+    }\r
+\r
+    @Override\r
+    public void render(Graphics2D g) {\r
+        if (!visible)\r
+            return;\r
+\r
+        if (DEBUG) {\r
+            System.out.println("FlagNode.render:");\r
+            System.out.println("\tflagShape:       " + flagShape);\r
+            System.out.println("\tflagText:     " + Arrays.toString(flagText));\r
+            System.out.println("\tstroke:       " + stroke);\r
+            System.out.println("\tborder:       " + border);\r
+            System.out.println("\tfill:         " + fill);\r
+            System.out.println("\ttextColor:    " + textColor);\r
+            System.out.println("\twidth:        " + width);\r
+            System.out.println("\theight:       " + height);\r
+            System.out.println("\tdirection:    " + direction);\r
+            System.out.println("\tbeakAngle:    " + beakAngle);\r
+            System.out.println("\ttextArea:     " + textArea);\r
+            System.out.println("\thAlign:       " + hAlign);\r
+            System.out.println("\tvAlign:       " + vAlign);\r
+            System.out.println("\tdraw:         " + visible);\r
+        }\r
+\r
+        AffineTransform ot = g.getTransform();\r
+        g.transform(transform);\r
+\r
+        try {\r
+            Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);\r
+\r
+            //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
+\r
+            // Paint flag shape\r
+            g.setColor(fill);\r
+            g.fill(flagShape);\r
+            g.setStroke(stroke);\r
+            g.setColor(border);\r
+            g.draw(flagShape);\r
+\r
+            // Speed rendering optimization: don't draw text that is too small to read\r
+            if (renderingHint != RenderingHints.VALUE_RENDER_QUALITY) {\r
+                double viewScale = GeometryUtils.getScale(ot);\r
+                viewScale *= GeometryUtils.getScale(transform);\r
+                if (viewScale < 4.0)\r
+                    return;\r
+            }\r
+\r
+            if (flagText == null || flagText.length == 0)\r
+                return;\r
+\r
+            if (DEBUG) {\r
+                g.setColor(Color.RED);\r
+                g.draw(textArea);\r
+            }\r
+\r
+            // Paint flag text\r
+            Font f = FONT;\r
+            g.setFont(f);\r
+            g.setColor(textColor);\r
+\r
+            AffineTransform orig = g.getTransform();\r
+\r
+            double det = orig.getDeterminant();\r
+            if (DEBUG)\r
+                System.out.println("DETERMINANT: " + det);\r
+\r
+            if (det < 0) {\r
+                // Invert the Y-axis if the symbol is "flipped" either vertically xor horizontally\r
+                origin.setLocation(textArea.getMinX(), textArea.getMaxY());\r
+                xa.setLocation(textArea.getMaxX(), textArea.getMaxY());\r
+                ya.setLocation(textArea.getMinX(), textArea.getMinY());\r
+            } else {\r
+                origin.setLocation(textArea.getMinX(), textArea.getMinY());\r
+                xa.setLocation(textArea.getMaxX(), textArea.getMinY());\r
+                ya.setLocation(textArea.getMinX(), textArea.getMaxY());\r
+            }\r
+\r
+            orig.transform(origin, origin);\r
+            orig.transform(xa, xa);\r
+            orig.transform(ya, ya);\r
+\r
+            double xAxisX = xa.getX() - origin.getX();\r
+            double xAxisY = xa.getY() - origin.getY();\r
+            double yAxisX = ya.getX() - origin.getX();\r
+            double yAxisY = ya.getY() - origin.getY();\r
+\r
+            boolean needToFlip = xAxisX < 0 || yAxisY < 0;\r
+            if (DEBUG)\r
+                System.out.println("TEXT NEEDS FLIPPING: " + needToFlip);\r
+\r
+            byte horizAlign = hAlign;\r
+\r
+            if (needToFlip) {\r
+                // Okay, the text would be upside-down if rendered directly with these axes.\r
+                // Let's flip the origin to the diagonal point and\r
+                // invert both x & y axis of the text area to get\r
+                // the text the right way around. Also, horizontal alignment\r
+                // needs to be switched unless it's centered.\r
+                origin.setLocation(origin.getX() + xAxisX + yAxisX, origin.getY() + xAxisY + yAxisY);\r
+                xAxisX = -xAxisX;\r
+                xAxisY = -xAxisY;\r
+                yAxisX = -yAxisX;\r
+                yAxisY = -yAxisY;\r
+\r
+                // Must flip horizontal alignment to keep text visually at the same\r
+                // end as before.\r
+                if (horizAlign == LEADING)\r
+                    horizAlign = TRAILING;\r
+                else if (horizAlign == TRAILING)\r
+                    horizAlign = LEADING;\r
+            }\r
+\r
+            final double gScale = GLOBAL_SCALE;\r
+            final double gScaleRecip = 1.0 / gScale;\r
+            final double scale = GeometryUtils.getMaxScale(orig) * gScale;\r
+            final double rotation = Math.atan2(xAxisY, xAxisX);\r
+            g.setTransform(IDENTITY);\r
+            g.translate(origin.getX(), origin.getY());\r
+            g.rotate(rotation);\r
+            g.scale(scale, scale);\r
+\r
+            if (DEBUG) {\r
+                System.out.println("ORIGIN: " + origin);\r
+                System.out.println("X-AXIS: (" + xAxisX + "," + xAxisY + ")");\r
+                System.out.println("Y-AXIS: (" + yAxisX + "," + yAxisY + ")");\r
+                System.out.println("rotation: " + Math.toDegrees(rotation));\r
+                System.out.println("scale: " + scale);\r
+                System.out.println("ORIG transform: " + orig);\r
+                System.out.println("transform: " + g.getTransform());\r
+            }\r
+\r
+            FontMetrics fm = g.getFontMetrics(f);\r
+            double fontHeight = fm.getHeight();\r
+\r
+            if (textLayout == null || (float) scale != lastViewScale)\r
+            {\r
+                lastViewScale = (float) scale;\r
+                FontRenderContext frc = g.getFontRenderContext();\r
+                if (textLayout == null)\r
+                    textLayout = new TextLayout[flagText.length];\r
+                if (rects == null)\r
+                    rects = new Rectangle2D[flagText.length];\r
+                textHeight = 0;\r
+                for (int i = 0; i < flagText.length; ++i) {\r
+                    String txt = flagText[i].isEmpty() ? " " : flagText[i]; \r
+                    textLayout[i] = new TextLayout(txt, f, frc);\r
+                    rects[i] = textLayout[i].getBounds();\r
+\r
+                    // If the bb height is not overridden with the font height\r
+                    // text lines will not be drawn in the correct Y location.\r
+                    rects[i].setRect(rects[i].getX(), rects[i].getY(), rects[i].getWidth(), fontHeight);\r
+\r
+                    textHeight += rects[i].getHeight() * gScale;\r
+                    if (DEBUG)\r
+                        System.out.println("  bounding rectangle for line " + i + " '" + flagText[i] + "': " + rects[i]);\r
+                }\r
+            }\r
+\r
+            double leftoverHeight = textArea.getHeight() - textHeight;\r
+            if (leftoverHeight < 0)\r
+                leftoverHeight = 0;\r
+\r
+            if (DEBUG) {\r
+                System.out.println("text area height: " + textArea.getHeight());\r
+                System.out.println("total text height: " + textHeight);\r
+                System.out.println("leftover height: " + leftoverHeight);\r
+            }\r
+\r
+            double lineDist = 0;\r
+            double startY = 0;\r
+\r
+            switch (vAlign) {\r
+                case LEADING:\r
+                    if (DEBUG)\r
+                        System.out.println("VERTICAL LEADING");\r
+                    lineDist = leftoverHeight / flagText.length;\r
+                    startY = fm.getMaxAscent();\r
+                    break;\r
+                case TRAILING:\r
+                    if (DEBUG)\r
+                        System.out.println("VERTICAL TRAILING");\r
+                    lineDist = leftoverHeight / flagText.length;\r
+                    startY = fm.getMaxAscent() + lineDist * gScaleRecip;\r
+                    break;\r
+                case CENTER:\r
+                    if (DEBUG)\r
+                        System.out.println("VERTICAL CENTER");\r
+                    lineDist = leftoverHeight / (flagText.length + 1);\r
+                    startY = fm.getMaxAscent() + lineDist * gScaleRecip;\r
+                    break;\r
+            }\r
+\r
+            if (DEBUG) {\r
+                System.out.println("lineDist: " + lineDist);\r
+                System.out.println("startY: " + startY);\r
+            }\r
+\r
+            lineDist *= gScaleRecip;\r
+            double y = startY;\r
+            double textAreaWidth = textArea.getWidth() * gScaleRecip;\r
+\r
+            for (int i = 0; i < flagText.length; ++i) {\r
+                //String line = flagText[i];\r
+                Rectangle2D rect = rects[i];\r
+\r
+                double x = 0;\r
+\r
+                switch (horizAlign) {\r
+                    case LEADING:\r
+                        if (DEBUG)\r
+                            System.out.println("HORIZ LEADING: " + rect);\r
+                        x = TEXT_MARGIN;\r
+                        break;\r
+                    case TRAILING:\r
+                        if (DEBUG)\r
+                            System.out.println("HORIZ TRAILING: " + rect);\r
+                        x = textAreaWidth - rect.getWidth() - TEXT_MARGIN;;\r
+                        break;\r
+                    case CENTER:\r
+                        if (DEBUG)\r
+                            System.out.println("HORIZ CENTER: " + rect);\r
+                        x = textAreaWidth * 0.5 - rect.getWidth()*0.5;\r
+                        break;\r
+                }\r
+\r
+                if (DEBUG)\r
+                    System.out.println("  X, Y: " + x + ", " + y);\r
+\r
+                if (DEBUG)\r
+                    System.out.println(" DRAW: '" + flagText[i] + "' with " + g.getTransform());\r
+\r
+                //textLayout[i].draw(g, (float) x, (float) y);\r
+                g.drawString(flagText[i], (float) x, (float) y);\r
+\r
+                y += lineDist;\r
+                y += rect.getHeight();\r
+            }\r
+\r
+        } finally {\r
+            g.setTransform(ot);\r
+        }\r
+    }\r
+\r
+    public static double getBeakLength(double height, double beakAngle) {\r
+        beakAngle = Math.min(180, Math.max(10, beakAngle));\r
+        return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));\r
+    }\r
+\r
+    @Override\r
+    public Rectangle2D getBoundsInLocal() {\r
+        if (flagShape == null)\r
+            return null;\r
+        return flagShape.getBounds2D();\r
+    }\r
+\r
+}\r