Combination of Simantics-platform related changes and fixes for district
[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.GridUtils;
26
27 public class RulerNode extends G2DNode {
28     /**
29      * 
30      */
31     private static final long  serialVersionUID  = 2490944880914577411L;
32
33     /**
34      * FIXME: this is a hack for the map UI that has to be solved some other way.
35      */
36     private static final boolean MAP_Y_SCALING = false;
37
38     private static final Color GRAY              = new Color(100, 100, 100);
39
40     protected Boolean          enabled           = true;
41
42     protected double           gridSize          = 1.0;
43
44     @SyncField("enabled")
45     public void setEnabled(Boolean enabled) {
46         this.enabled = enabled;
47     }
48
49     @SyncField("gridSize")
50     public void setGridSize(double gridSize) {
51         if (gridSize < 1e-6)
52             gridSize = 1e-6;
53         this.gridSize = gridSize;
54     }
55
56     @Override
57     public void render(Graphics2D g) {
58         if (!enabled)
59             return;
60         
61         AffineTransform tr = g.getTransform();
62         double scaleX = Math.abs(tr.getScaleX());
63         double scaleY = Math.abs(tr.getScaleY());
64         if (scaleX <= 0 || scaleY <= 0) {
65             // Make sure that we don't end up in an eternal loop below.
66             return;
67         }
68         double offsetX = tr.getTranslateX();
69         double offsetY = tr.getTranslateY();
70         g.setTransform(new AffineTransform());
71
72         Font rulerFont = new Font("Tahoma", Font.PLAIN, 9);
73
74         //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
75         g.setStroke(new BasicStroke(1));
76         g.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
77
78         Rectangle2D bounds = g.getClipBounds();
79         if(bounds == null) return; // FIXME
80
81         g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
82
83         Rectangle2D vertical = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()+20);
84         g.fill(vertical);
85
86         Rectangle2D horizontal = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY()+20, bounds.getMinX()+20, bounds.getMaxY());
87         g.fill(horizontal);
88
89         // stepX and stepY should be something between 50 and 100
90         double stepX = 50;
91         double stepY = 50;
92
93         stepX = GridUtils.limitedEvenGridSpacing(stepX, scaleX, 100, gridSize, true);
94         stepY = GridUtils.limitedEvenGridSpacing(stepY, scaleY, 100, gridSize, true);
95         //while(stepX * scaleX > 100) stepX /= 2;
96         //while(stepY * scaleY > 100) stepY /= 2;
97
98         while(stepX * scaleX < 50) stepX *= 2;
99         while(stepY * scaleY < 50) stepY *= 2;
100
101         stepX *= scaleX;
102         stepY *= scaleY;
103
104         g.setColor(GRAY);
105         g.setFont(rulerFont);
106
107         double previousText = -100;
108
109         // Vertical ruler
110         for(double x = offsetX%stepX-stepX; x < bounds.getMaxX(); x+=stepX) {
111             if(x > 20) {
112                 double val = (x-offsetX)/scaleX / getTransform().getScaleX();
113                 double modifiedValue = modifyHorizontalValue(val);
114                 String str = formatValue(modifiedValue);
115                 FontMetrics fm = g.getFontMetrics();
116                 Rectangle2D r = fm.getStringBounds(str, g);
117                 if((x-r.getWidth()/2) > previousText) {
118                     g.setColor(Color.BLACK);
119                     g.drawString(str, (int)(x-r.getWidth()/2), (int)(bounds.getMinY()+1+r.getHeight()));
120                     previousText = x+r.getWidth()/2+stepX/4;
121                 }
122
123                 g.setColor(GRAY);
124                 g.drawLine((int)x, (int)bounds.getMinY()+12, (int)x, (int)bounds.getMinY()+19);
125             }
126             if(stepX/5 > 2) {
127                 for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) {
128                     if(x2 > 20) {
129                         g.drawLine((int)x2, (int)bounds.getMinY()+15, (int)x2, (int)bounds.getMinY()+19);
130                     }
131                 }
132                 for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) {
133                     if(x2 > 20) {
134                         g.drawLine((int)x2, (int)bounds.getMinY()+17, (int)x2, (int)bounds.getMinY()+19);
135                     }
136                 }
137
138             }
139         }
140
141         // Horizontal ruler
142         previousText = -100;
143         for(double y = offsetY%stepY-stepY; y < bounds.getMaxY(); y+=stepY) {
144             if(y > 20) {
145                 double val = (y-offsetY)/scaleY / getTransform().getScaleY();
146                 double modifiedValue = modifyVerticalValue(val);
147                 String str = formatValue(modifiedValue);
148                 FontMetrics fm = g.getFontMetrics();
149                 Rectangle2D r = fm.getStringBounds(str, g);
150                 if(y-1+r.getHeight()/2 > previousText) {
151                     g.setColor(Color.BLACK);
152                     AffineTransform origTr = g.getTransform();
153                     g.translate((int)(bounds.getMinX()), (int)(y+r.getWidth()/2));
154                     g.rotate(-Math.PI / 2.0);
155                     g.drawString(str, 0, (int)r.getHeight());
156                     g.setTransform(origTr);
157                     previousText = y-1+r.getHeight();
158                 }
159                 g.setColor(GRAY);
160                 g.drawLine((int)bounds.getMinX()+12, (int)y, (int)bounds.getMinX()+19, (int)y);
161             }
162             if(stepY/5 > 2) {
163                 for(double y2 = y+stepY/5; y2 < y+stepY; y2+=stepY/5) {
164                     if(y2 > 20) {
165                         g.drawLine((int)bounds.getMinX()+15, (int)y2, (int)bounds.getMinX()+19, (int)y2);
166                     }
167                 }
168                 for(double y2 = y+stepY/10; y2 < y+stepY; y2+=stepY/5) {
169                     if(y2 > 20) {
170                         g.drawLine((int)bounds.getMinX()+17, (int)y2, (int)bounds.getMinX()+19, (int)y2);
171                     }
172                 }
173             }
174         }
175
176         g.setTransform(tr);
177     }
178
179     /**
180      * A method for subclasses to alter the actual X-value of the ruler 
181      * 
182      * @param value
183      * @return possibly modified X-value 
184      */
185     protected double modifyHorizontalValue(double value) {
186         return value;
187     }
188
189     /**
190      * A method for subclasses to alter the actual Y-value of the ruler 
191      * 
192      * @param value
193      * @return possibly modified Y-value 
194      */
195     protected double modifyVerticalValue(double value) {
196         return value;
197     }
198
199     private static final transient int    MAX_DIGITS = 5;
200     private static final transient double EPSILON    = 0.01;
201     private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4);
202     private static final transient String[] SI_UNIT_LARGE_PREFIXES = {
203         "k", "M", "G", "T", "P", "E", "Z", "Y"
204     };
205
206     public static String formatValue(double value) {
207         int magnitude = (int) Math.round(Math.log10(value));
208         //System.out.println("magnitude: " + magnitude + ", " + value);
209         int allowedDecimals = MAX_DIGITS;
210         allowedDecimals -= Math.abs(magnitude);
211         if (allowedDecimals < 0)
212             allowedDecimals = 0;
213
214         String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value);
215         if (allowedDecimals > 0) {
216             for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) {
217                 char ch = valueStr.charAt(trunc);
218                 if (ch == '.') {
219                     valueStr = valueStr.substring(0, trunc);
220                     break;
221                 }
222                 if (valueStr.charAt(trunc) != '0') {
223                     valueStr = valueStr.substring(0, trunc + 1);
224                     break;
225                 }
226             }
227             if (Math.abs(value) + EPSILON > TRIM_THRESHOLD_MAX_VALUE) {
228                 // Cut anything beyond a possible decimal dot out since they
229                 // should not show anyway. This is a complete hack that tries to
230                 // circumvent floating-point inaccuracy problems.
231                 int dotIndex = valueStr.lastIndexOf('.');
232                 if (dotIndex > -1) {
233                     valueStr = valueStr.substring(0, dotIndex);
234                 }
235             }
236         }
237
238         double trimValue = value;
239         if (Math.abs(value)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE) {
240             for (int i = 0; Math.abs(trimValue)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE; ++i) {
241                 double trim = trimValue / 1000;
242                 if (Math.abs(trim)-EPSILON < TRIM_THRESHOLD_MAX_VALUE) {
243                     valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3);
244                     valueStr += SI_UNIT_LARGE_PREFIXES[i];
245                     break;
246                 }
247                 trimValue = trim;
248             }
249         }
250
251         if (valueStr.equals("-0"))
252             valueStr = "0";
253
254         return valueStr;
255     }
256
257     @Override
258     public Rectangle2D getBoundsInLocal() {
259         return null;
260     }
261
262 }