/******************************************************************************* * 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.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.apache.batik.ext.awt.geom.Polygon2D; import org.simantics.scenegraph.g2d.G2DNode; import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.scenegraph.utils.NodeUtil; public class SelectionNode extends G2DNode implements Decoration { /** * */ private static final long serialVersionUID = -2879575230419873230L; public transient static final BasicStroke SELECTION_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE, 10.0f, new float[] { 5.0f, 5.0f }, 0.0f); protected Rectangle2D bounds = null; protected Color color = null; protected transient Rectangle2D rect; protected transient BasicStroke scaledStroke; protected transient double previousScaleRecip = Double.NaN; protected boolean ignore = false; protected int selectionId; public int getSelectionId() { return selectionId; } public void setIgnore(boolean value) { ignore = value; } /** * @deprecated in favor of G2DSceneGraph.PICK_DISTANCE */ public void setPaddingFactor(double factor) { } @SyncField({"transform", "bounds", "color"}) public void init(int selectionId, AffineTransform transform, Rectangle2D bounds, Color color) { this.selectionId = selectionId; this.transform = transform; this.bounds = bounds; this.color = color; } public void init(AffineTransform transform, Rectangle2D bounds, Color color) { init(0, transform, bounds, color); } @Override public void render(Graphics2D g) { if (bounds == null) return; if (ignore) return; // Prevent exceptions during rendering. if (transform.getDeterminant() == 0) return; NavigationNode nn = NodeUtil.findNearestParentNode(this, NavigationNode.class); double scale = 1.0; if (nn != null) { scale = GeometryUtils.getScale(nn.getTransform()); } double scaleRecip = 1 / scale; // Prevent stroke reallocation while panning. // Zooming will trigger reallocation. if (scaledStroke == null || scaleRecip != previousScaleRecip) { scaledStroke = GeometryUtils.scaleStroke( SELECTION_STROKE, (float) scaleRecip); previousScaleRecip = scaleRecip; } G2DSceneGraph sg = NodeUtil.getRootNode(nn); double padding = sg.getGlobalProperty(G2DSceneGraph.PICK_DISTANCE, 0.0) / scale; g.setStroke(scaledStroke); g.setColor(color); Shape selectionShape = transformAndExpand(bounds, transform, padding); g.draw(selectionShape); if (rect == null) rect = new Rectangle2D.Double(); Rectangle2D r = transform.createTransformedShape(bounds).getBounds2D(); rect.setFrame(r.getMinX() - padding, r.getMinY() - padding, r.getWidth() + 2.0*padding, r.getHeight() + 2.0*padding); } private static Shape transformAndExpand(Rectangle2D r, AffineTransform t, double padding) { if ((t.getShearX() == 0 && t.getShearY() == 0) || t.getScaleX() == 0 && t.getScaleY() == 0) { // Simple case for axis-aligned selection Rectangle2D result = t.createTransformedShape(r).getBounds2D(); result.setRect(result.getMinX() - padding, result.getMinY() - padding, result.getWidth() + 2 * padding, result.getHeight() + 2 * padding); return result; } else { // General case Point2D.Double corners[] = new Point2D.Double[4]; corners[0] = new Point2D.Double(r.getMinX(), r.getMinY()); corners[1] = new Point2D.Double(r.getMinX(), r.getMaxY()); corners[2] = new Point2D.Double(r.getMaxX(), r.getMaxY()); corners[3] = new Point2D.Double(r.getMaxX(), r.getMinY()); t.transform(corners, 0, corners, 0, 4); double det = t.getDeterminant(); double step = Math.PI / 2; double diagonalPadding = padding * Math.sqrt(2); Polygon2D poly = new Polygon2D(); for (int edge = 0; edge < 4; edge++) { int p0 = edge % 4; int p1 = (edge + 1) % 4; int p2 = (edge + 2) % 4; double a1 = Math.atan2(corners[p1].y - corners[p0].y, corners[p1].x - corners[p0].x); double a2 = Math.atan2(corners[p2].y - corners[p1].y, corners[p2].x - corners[p1].x); if (det > 0) { if (a1 < a2) { a1 += Math.PI * 2; } int dir1 = (int)Math.ceil(a1 / step); int dir2 = (int)Math.floor(a2 / step); for (int dir = dir1; dir > dir2; dir--) { poly.addPoint(new Point2D.Double( corners[p1].x + Math.cos((dir + 0.5) * step) * diagonalPadding, corners[p1].y + Math.sin((dir + 0.5) * step) * diagonalPadding)); } } else { if (a1 > a2) { a2 += Math.PI * 2; } int dir1 = (int)Math.floor(a1 / step); int dir2 = (int)Math.ceil(a2 / step); for (int dir = dir1; dir < dir2; dir++) { poly.addPoint(new Point2D.Double( corners[p1].x + Math.cos((dir - 0.5) * step) * diagonalPadding, corners[p1].y + Math.sin((dir - 0.5) * step) * diagonalPadding)); } } } return poly; } } public Rectangle2D getRect() { return rect; } @Override public Rectangle2D getBoundsInLocal() { return bounds; } }