X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2Fg2d%2Fnodes%2FRulerNode.java;fp=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2Fg2d%2Fnodes%2FRulerNode.java;h=ef6f2fc7cdefd62c39a0395dc5044c0563467adf;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java new file mode 100644 index 000000000..ef6f2fc7c --- /dev/null +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java @@ -0,0 +1,241 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.scenegraph.g2d.nodes; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.Locale; + +import org.simantics.scenegraph.g2d.G2DNode; +import org.simantics.scenegraph.utils.GridUtils; + +public class RulerNode extends G2DNode { + /** + * + */ + private static final long serialVersionUID = 2490944880914577411L; + + /** + * FIXME: this is a hack for the map UI that has to be solved some other way. + */ + private static final boolean MAP_Y_SCALING = false; + + private static final Color GRAY = new Color(100, 100, 100); + + protected Boolean enabled = true; + + protected double gridSize = 1.0; + + @SyncField("enabled") + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @SyncField("gridSize") + public void setGridSize(double gridSize) { + if (gridSize < 1e-6) + gridSize = 1e-6; + this.gridSize = gridSize; + } + + @Override + public void render(Graphics2D g) { + if (!enabled) + return; + + AffineTransform tr = g.getTransform(); + double scaleX = Math.abs(tr.getScaleX()); + double scaleY = Math.abs(tr.getScaleY()); + if (scaleX <= 0 || scaleY <= 0) { + // Make sure that we don't end up in an eternal loop below. + return; + } + double offsetX = tr.getTranslateX(); + double offsetY = tr.getTranslateY(); + g.setTransform(new AffineTransform()); + + Font rulerFont = new Font("Tahoma", Font.PLAIN, 9); + + //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setStroke(new BasicStroke(1)); + g.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f)); + + Rectangle2D bounds = g.getClipBounds(); + if(bounds == null) return; // FIXME + + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f)); + + Rectangle2D vertical = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()+20); + g.fill(vertical); + + Rectangle2D horizontal = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY()+20, bounds.getMinX()+20, bounds.getMaxY()); + g.fill(horizontal); + + // stepX and stepY should be something between 50 and 100 + double stepX = 50; + double stepY = 50; + + stepX = GridUtils.limitedEvenGridSpacing(stepX, scaleX, 100, gridSize, true); + stepY = GridUtils.limitedEvenGridSpacing(stepY, scaleY, 100, gridSize, true); + //while(stepX * scaleX > 100) stepX /= 2; + //while(stepY * scaleY > 100) stepY /= 2; + + while(stepX * scaleX < 50) stepX *= 2; + while(stepY * scaleY < 50) stepY *= 2; + + stepX *= scaleX; + stepY *= scaleY; + + g.setColor(GRAY); + g.setFont(rulerFont); + + double previousText = -100; + + // Vertical ruler + for(double x = offsetX%stepX-stepX; x < bounds.getMaxX(); x+=stepX) { + if(x > 20) { + String str = formatValue((x-offsetX)/scaleX); + FontMetrics fm = g.getFontMetrics(); + Rectangle2D r = fm.getStringBounds(str, g); + if((x-r.getWidth()/2) > previousText) { + g.setColor(Color.BLACK); + g.drawString(str, (int)(x-r.getWidth()/2), (int)(bounds.getMinY()+1+r.getHeight())); + previousText = x+r.getWidth()/2+stepX/4; + } + + g.setColor(GRAY); + g.drawLine((int)x, (int)bounds.getMinY()+12, (int)x, (int)bounds.getMinY()+19); + } + if(stepX/5 > 2) { + for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) { + if(x2 > 20) { + g.drawLine((int)x2, (int)bounds.getMinY()+15, (int)x2, (int)bounds.getMinY()+19); + } + } + for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) { + if(x2 > 20) { + g.drawLine((int)x2, (int)bounds.getMinY()+17, (int)x2, (int)bounds.getMinY()+19); + } + } + + } + } + + // Horizontal ruler + previousText = -100; + for(double y = offsetY%stepY-stepY; y < bounds.getMaxY(); y+=stepY) { + if(y > 20) { + double val = (y-offsetY)/scaleY; + if (MAP_Y_SCALING) + val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val)))); + String str = formatValue(val); + FontMetrics fm = g.getFontMetrics(); + Rectangle2D r = fm.getStringBounds(str, g); + if(y-1+r.getHeight()/2 > previousText) { + g.setColor(Color.BLACK); + AffineTransform origTr = g.getTransform(); + g.translate((int)(bounds.getMinX()), (int)(y+r.getWidth()/2)); + g.rotate(-Math.PI / 2.0); + g.drawString(str, 0, (int)r.getHeight()); + g.setTransform(origTr); + previousText = y-1+r.getHeight(); + } + g.setColor(GRAY); + g.drawLine((int)bounds.getMinX()+12, (int)y, (int)bounds.getMinX()+19, (int)y); + } + if(stepY/5 > 2) { + for(double y2 = y+stepY/5; y2 < y+stepY; y2+=stepY/5) { + if(y2 > 20) { + g.drawLine((int)bounds.getMinX()+15, (int)y2, (int)bounds.getMinX()+19, (int)y2); + } + } + for(double y2 = y+stepY/10; y2 < y+stepY; y2+=stepY/5) { + if(y2 > 20) { + g.drawLine((int)bounds.getMinX()+17, (int)y2, (int)bounds.getMinX()+19, (int)y2); + } + } + } + } + + g.setTransform(tr); + } + + private static final transient int MAX_DIGITS = 5; + private static final transient double EPSILON = 0.01; + private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4); + private static final transient String[] SI_UNIT_LARGE_PREFIXES = { + "k", "M", "G", "T", "P", "E", "Z", "Y" + }; + + public static String formatValue(double value) { + int magnitude = (int) Math.round(Math.log10(value)); + //System.out.println("magnitude: " + magnitude + ", " + value); + int allowedDecimals = MAX_DIGITS; + allowedDecimals -= Math.abs(magnitude); + if (allowedDecimals < 0) + allowedDecimals = 0; + + String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value); + if (allowedDecimals > 0) { + for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) { + char ch = valueStr.charAt(trunc); + if (ch == '.') { + valueStr = valueStr.substring(0, trunc); + break; + } + if (valueStr.charAt(trunc) != '0') { + valueStr = valueStr.substring(0, trunc + 1); + break; + } + } + if (Math.abs(value) + EPSILON > TRIM_THRESHOLD_MAX_VALUE) { + // Cut anything beyond a possible decimal dot out since they + // should not show anyway. This is a complete hack that tries to + // circumvent floating-point inaccuracy problems. + int dotIndex = valueStr.lastIndexOf('.'); + if (dotIndex > -1) { + valueStr = valueStr.substring(0, dotIndex); + } + } + } + + double trimValue = value; + if (Math.abs(value)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE) { + for (int i = 0; Math.abs(trimValue)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE; ++i) { + double trim = trimValue / 1000; + if (Math.abs(trim)-EPSILON < TRIM_THRESHOLD_MAX_VALUE) { + valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3); + valueStr += SI_UNIT_LARGE_PREFIXES[i]; + break; + } + trimValue = trim; + } + } + + if (valueStr.equals("-0")) + valueStr = "0"; + + return valueStr; + } + + @Override + public Rectangle2D getBoundsInLocal() { + return null; + } + +}