]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / RulerNode.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes;
13
14 import java.awt.AlphaComposite;
15 import java.awt.BasicStroke;
16 import java.awt.Color;
17 import java.awt.Font;
18 import java.awt.FontMetrics;
19 import java.awt.Graphics2D;
20 import java.awt.geom.AffineTransform;
21 import java.awt.geom.Rectangle2D;
22 import java.util.Locale;
23
24 import org.simantics.scenegraph.g2d.G2DNode;
25 import org.simantics.scenegraph.utils.DPIUtil;
26 import org.simantics.scenegraph.utils.GridUtils;
27
28 public class RulerNode extends G2DNode {
29     /**
30      * 
31      */
32     private static final long  serialVersionUID  = 2490944880914577411L;
33
34     private static final Color GRAY              = new Color(100, 100, 100);
35
36     protected Boolean          enabled           = true;
37
38     protected double           gridSize          = 1.0;
39
40     protected double           rulerSize         = 20;
41
42     @SyncField("enabled")
43     public void setEnabled(Boolean enabled) {
44         this.enabled = enabled;
45     }
46
47     @SyncField("gridSize")
48     public void setGridSize(double gridSize) {
49         if (gridSize < 1e-6)
50             gridSize = 1e-6;
51         this.gridSize = gridSize;
52     }
53
54     @SyncField("rulerSize")
55     public void setRulerSize(double rulerSize) {
56         this.rulerSize = rulerSize;
57     }
58
59     @Override
60     public void render(Graphics2D g) {
61         if (!enabled)
62             return;
63
64         AffineTransform tr = g.getTransform();
65         double scaleX = Math.abs(tr.getScaleX());
66         double scaleY = Math.abs(tr.getScaleY());
67         if (scaleX <= 0 || scaleY <= 0) {
68             // Make sure that we don't end up in an eternal loop below.
69             return;
70         }
71         double offsetX = tr.getTranslateX();
72         double offsetY = tr.getTranslateY();
73         g.setTransform(new AffineTransform());
74
75         Font rulerFont = new Font("Tahoma", Font.PLAIN, DPIUtil.upscale(9));
76
77         //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
78         g.setStroke(new BasicStroke(1));
79         g.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
80
81         Rectangle2D bounds = g.getClipBounds();
82         if(bounds == null) return; // FIXME
83
84         g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
85
86         int rulerPixelSize = (int) DPIUtil.upscale(rulerSize);
87         int u7 = DPIUtil.upscale(7);
88         int u4 = DPIUtil.upscale(4);
89         int u2 = DPIUtil.upscale(2);
90
91         Rectangle2D vertical = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()+rulerPixelSize);
92         g.fill(vertical);
93
94         Rectangle2D horizontal = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY()+rulerPixelSize, bounds.getMinX()+rulerPixelSize, bounds.getMaxY());
95         g.fill(horizontal);
96
97         // stepX and stepY should be something between 50 and 100
98         double stepX = 50;
99         double stepY = 50;
100
101         stepX = GridUtils.limitedEvenGridSpacing(stepX, scaleX, 100, gridSize, true);
102         stepY = GridUtils.limitedEvenGridSpacing(stepY, scaleY, 100, gridSize, true);
103         //while(stepX * scaleX > 100) stepX /= 2;
104         //while(stepY * scaleY > 100) stepY /= 2;
105
106         while(stepX * scaleX < 50) stepX *= 2;
107         while(stepY * scaleY < 50) stepY *= 2;
108
109         stepX *= scaleX;
110         stepY *= scaleY;
111
112         g.setColor(GRAY);
113         g.setFont(rulerFont);
114         FontMetrics fm = g.getFontMetrics();
115
116         double previousText = -100;
117
118         // Vertical ruler
119         for(double x = offsetX%stepX-stepX; x < bounds.getMaxX(); x+=stepX) {
120             if(x > rulerPixelSize) {
121                 double val = (x-offsetX)/scaleX / getTransform().getScaleX();
122                 double modifiedValue = modifyHorizontalValue(val);
123                 String str = formatValue(modifiedValue, getMaxDigits());
124                 Rectangle2D r = fm.getStringBounds(str, g);
125                 if((x-r.getWidth()/2) > previousText) {
126                     g.setColor(Color.BLACK);
127                     g.drawString(str, (int)(x-r.getWidth()/2), (int)(bounds.getMinY()+1+r.getHeight()));
128                     previousText = x+r.getWidth()/2+stepX/4;
129                 }
130
131                 g.setColor(GRAY);
132                 g.drawLine((int)x, (int)bounds.getMinY()+rulerPixelSize-1-u7, (int)x, (int)bounds.getMinY()+rulerPixelSize-1);
133             }
134             if(stepX/5 > 2) {
135                 for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) {
136                     if(x2 > rulerPixelSize) {
137                         g.drawLine((int)x2, (int)bounds.getMinY()+rulerPixelSize-1-u4, (int)x2, (int)bounds.getMinY()+rulerPixelSize-1);
138                     }
139                 }
140                 for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) {
141                     if(x2 > rulerPixelSize) {
142                         g.drawLine((int)x2, (int)bounds.getMinY()+rulerPixelSize-1-u2, (int)x2, (int)bounds.getMinY()+rulerPixelSize-1);
143                     }
144                 }
145             }
146         }
147
148         // Horizontal ruler
149         previousText = -100;
150         for(double y = offsetY%stepY-stepY; y < bounds.getMaxY(); y+=stepY) {
151             if(y > rulerPixelSize) {
152                 double val = (y-offsetY)/scaleY / getTransform().getScaleY();
153                 double modifiedValue = modifyVerticalValue(val);
154                 String str = formatValue(modifiedValue, getMaxDigits());
155                 Rectangle2D r = fm.getStringBounds(str, g);
156                 if(y-1+r.getHeight()/2 > previousText) {
157                     g.setColor(Color.BLACK);
158                     AffineTransform origTr = g.getTransform();
159                     g.translate((int)(bounds.getMinX()), (int)(y+r.getWidth()/2));
160                     g.rotate(-Math.PI / 2.0);
161                     g.drawString(str, 0, (int)r.getHeight());
162                     g.setTransform(origTr);
163                     previousText = y-1+r.getHeight();
164                 }
165                 g.setColor(GRAY);
166                 g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u7, (int)y, (int)bounds.getMinX()+rulerPixelSize-1, (int)y);
167             }
168             if(stepY/5 > 2) {
169                 for(double y2 = y+stepY/5; y2 < y+stepY; y2+=stepY/5) {
170                     if(y2 > rulerPixelSize) {
171                         g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u4, (int)y2, (int)bounds.getMinX()+rulerPixelSize-1, (int)y2);
172                     }
173                 }
174                 for(double y2 = y+stepY/10; y2 < y+stepY; y2+=stepY/5) {
175                     if(y2 > rulerPixelSize) {
176                         g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u2, (int)y2, (int)bounds.getMinX()+rulerPixelSize-1, (int)y2);
177                     }
178                 }
179             }
180         }
181
182         g.setTransform(tr);
183     }
184
185     /**
186      * A method for subclasses to alter the actual X-value of the ruler 
187      * 
188      * @param value
189      * @return possibly modified X-value 
190      */
191     protected double modifyHorizontalValue(double value) {
192         return value;
193     }
194
195     /**
196      * A method for subclasses to alter the actual Y-value of the ruler 
197      * 
198      * @param value
199      * @return possibly modified Y-value 
200      */
201     protected double modifyVerticalValue(double value) {
202         return value;
203     }
204
205     private static final transient int    MAX_DIGITS = 5;
206     private static final transient double EPSILON    = 0.01;
207     private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4);
208     private static final transient String[] SI_UNIT_LARGE_PREFIXES = {
209         "k", "M", "G", "T", "P", "E", "Z", "Y"
210     };
211     
212     protected int getMaxDigits() {
213         return MAX_DIGITS;
214     }
215
216     public static String formatValue(double value, int maxDigits) {
217         int magnitude = (int) Math.round(Math.log10(value));
218         //System.out.println("magnitude: " + magnitude + ", " + value);
219         int allowedDecimals = maxDigits;
220         allowedDecimals -= Math.abs(magnitude);
221         if (allowedDecimals < 0)
222             allowedDecimals = 0;
223
224         String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value);
225         if (allowedDecimals > 0) {
226             for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) {
227                 char ch = valueStr.charAt(trunc);
228                 if (ch == '.') {
229                     valueStr = valueStr.substring(0, trunc);
230                     break;
231                 }
232                 if (valueStr.charAt(trunc) != '0') {
233                     valueStr = valueStr.substring(0, trunc + 1);
234                     break;
235                 }
236             }
237             if (Math.abs(value) + EPSILON > TRIM_THRESHOLD_MAX_VALUE) {
238                 // Cut anything beyond a possible decimal dot out since they
239                 // should not show anyway. This is a complete hack that tries to
240                 // circumvent floating-point inaccuracy problems.
241                 int dotIndex = valueStr.lastIndexOf('.');
242                 if (dotIndex > -1) {
243                     valueStr = valueStr.substring(0, dotIndex);
244                 }
245             }
246         }
247
248         double trimValue = value;
249         if (Math.abs(value)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE) {
250             for (int i = 0; Math.abs(trimValue)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE; ++i) {
251                 double trim = trimValue / 1000;
252                 if (Math.abs(trim)-EPSILON < TRIM_THRESHOLD_MAX_VALUE) {
253                     valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3);
254                     valueStr += SI_UNIT_LARGE_PREFIXES[i];
255                     break;
256                 }
257                 trimValue = trim;
258             }
259         }
260
261         if (valueStr.equals("-0"))
262             valueStr = "0";
263
264         return valueStr;
265     }
266
267     @Override
268     public Rectangle2D getBoundsInLocal() {
269         return null;
270     }
271
272 }