/******************************************************************************* * Copyright (c) 2007, 2011 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.connection; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import org.simantics.diagram.connection.RouteGraph; import org.simantics.diagram.connection.RouteLineHalf; import org.simantics.diagram.connection.actions.IAction; import org.simantics.diagram.connection.rendering.IRouteGraphRenderer; import org.simantics.scenegraph.utils.Quality; import org.simantics.scenegraph.utils.QualityHints; /** * @author Tuukka Lehtonen */ public class HighlightActionPointsAction implements IAction { public static enum Action { REMOVE, RECONNECT } public static class Pick { public static final Pick MISS = new Pick(null, null); Action action; RouteLineHalf line; public Pick(Action action, RouteLineHalf line) { this.action = action; this.line = line; } public boolean hasAction(Action action) { return this.action == action; } public boolean hasResult() { return action != null && line != null; } public boolean matches(Action action, RouteLineHalf line) { if (this.action == null || this.line == null) return false; return this.action.equals(action) && this.line.equals(line); } } private static final Shape CROSS_SHAPE = ActionShapes.CROSS_SHAPE; private static final Shape SCISSOR_SHAPE = ActionShapes.transformShape(ActionShapes.SCISSOR_SHAPE, 1, 1, 0, 0, -Math.PI/2); private static final Color CROSS_COLOR = new Color(0xe4, 0x40, 0x61); private static final Color SCISSOR_COLOR = new Color(20, 20, 20); public static final Stroke STROKE = new BasicStroke(0.1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); public static final AlphaComposite NO_HIT_COMPOSITE = AlphaComposite.SrcOver.derive(0.8f); public static final AlphaComposite HIT_COMPOSITE = AlphaComposite.SrcOver.derive(0.2f); public static final double DEGENERATED_LINE_LENGTH = 1; public static final double CUT_DIST_FROM_END = 0.75; RouteGraph rg; transient Collection lhs = new ArrayList(); transient AffineTransform transform = new AffineTransform(); transient Rectangle2D rect = new Rectangle2D.Double(); transient Point2D point = new Point2D.Double(); public HighlightActionPointsAction(RouteGraph rg) { this.rg = rg; } public void setRouteGraph(RouteGraph rg) { this.rg = rg; } @Override public void render(Graphics2D g, IRouteGraphRenderer renderer, double mouseX, double mouseY) { // Cannot perform cut or delete segment actions // on connections between 2 terminals. boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2); boolean branchedConnection = rg.getTerminals().size() > 2; if (!branchedConnection || simpleConnection) return; AffineTransform preTr = g.getTransform(); double realViewScale = 1.0 / getScale(preTr); //System.out.println(realViewScale); // Don't render any of the actions if they could not be seen anyway. if (realViewScale > 0.7) return; lhs.clear(); rg.getLineHalves(lhs); Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY); Composite originalComposite = g.getComposite(); Composite basicComposite = pick.action != null ? HIT_COMPOSITE : NO_HIT_COMPOSITE; g.setComposite(basicComposite); // Always render these in high quality because otherwise the shapes // will render with ugly artifacts when zoom level is a bit higher. QualityHints origQualityHints = QualityHints.getQuality(g); QualityHints.getHints(Quality.HIGH).setQuality(g); // Render line removal markers if (!simpleConnection) { g.setPaint(CROSS_COLOR); for (RouteLineHalf lh : lhs) { if (removeLocation(lh, point) == null) continue; boolean hit = pick.matches(Action.REMOVE, lh); if (hit) g.setComposite(originalComposite); g.translate(point.getX(), point.getY()); g.fill(CROSS_SHAPE); g.setTransform(preTr); if (hit) g.setComposite(basicComposite); } } // Render reconnection markers if the connection is branched. if (branchedConnection) { g.setPaint(SCISSOR_COLOR); for (RouteLineHalf lh : lhs) { if (reconnectLocation(lh, point) == null) continue; boolean hit = pick.matches(Action.RECONNECT, lh); if (hit) g.setComposite(originalComposite); transform.setToTranslation(point.getX(), point.getY()); if (!lh.getLine().isHorizontal()) transform.rotate(Math.PI/2); transform.translate(0, 0.35); g.transform(transform); g.fill(SCISSOR_SHAPE); g.setTransform(preTr); if (hit) g.setComposite(basicComposite); } } origQualityHints.setQuality(g); g.setComposite(originalComposite); } public Pick pickAction(RouteGraph rg, AffineTransform viewTr, double mouseX, double mouseY) { boolean branchedConnection = rg.getTerminals().size() > 2; boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2); if (!branchedConnection || simpleConnection) return null; lhs.clear(); return pickAction(rg, rg.getLineHalves(lhs), viewTr, mouseX, mouseY); } public Pick pickAction(RouteGraph rg, Collection lhs, AffineTransform viewTr, double mouseX, double mouseY) { boolean branchedConnection = rg.getTerminals().size() > 2; boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2); if (!branchedConnection || simpleConnection || viewTr == null) return Pick.MISS; double viewScale = 1.0 / getScale(viewTr); if (viewScale > 0.7) return Pick.MISS; double nearest = Double.MAX_VALUE; RouteLineHalf selected = null; Action selectedAction = null; // Pick line removal markers if (!simpleConnection) { double s = ActionShapes.CROSS_WIDTH * 0.25; for (RouteLineHalf lh : lhs) { if (removeLocation(lh, point) == null) continue; double x = point.getX(); double y = point.getY(); rect.setFrameFromCenter(x, y, x-s, y-s); boolean hit = rect.contains(mouseX, mouseY); if (hit) { double distSq = distSq(x, y, mouseX, mouseY); if (distSq < nearest) { nearest = distSq; selected = lh; selectedAction = Action.REMOVE; } } } } // Pick reconnection markers if the connection is branched. if (branchedConnection) { double w = ActionShapes.SCISSOR_HEIGHT * 0.4; double h = ActionShapes.SCISSOR_WIDTH * 0.3; for (RouteLineHalf lh : lhs) { if (reconnectLocation(lh, point) == null) continue; double x = point.getX(); double y = point.getY(); rect.setFrameFromCenter(x, y, x-w, y-h); boolean hit = rect.contains(mouseX, mouseY); if (hit) { double distSq = distSq(x, y, mouseX, mouseY); if (distSq < nearest) { nearest = distSq; selected = lh; selectedAction = Action.RECONNECT; } } } } return selected == null ? Pick.MISS : new Pick(selectedAction, selected); } private static Point2D removeLocation(RouteLineHalf lh, Point2D p) { if (lh.getLine().getTerminal() == null) return null; double x = lh.getLink().getX(); double y = lh.getLink().getY(); if (lh.getLine().isHorizontal()) { x = (lh.getLine().getBegin().getX() + lh.getLine().getEnd().getX()) * .5; } else { y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5; } p.setLocation(x, y); return p; } private static Point2D reconnectLocation(RouteLineHalf lh, Point2D p) { if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3) return null; final double dist = CUT_DIST_FROM_END; double x = lh.getLink().getX(); double y = lh.getLink().getY(); if (lh.getLine().isHorizontal()) { if (lh.getLink() == lh.getLine().getBegin()) x += dist*2; else x -= dist*2; } else { if (lh.getLink() == lh.getLine().getBegin()) y += dist*2; else y -= dist*2; } p.setLocation(x, y); return p; } private static double distSq(double x1, double y1, double x2, double y2) { double dx = x2 - x1; double dy = y2 - y1; return dx * dx + dy * dy; } private static double getScale(AffineTransform at) { double m00 = at.getScaleX(); double m11 = at.getScaleY(); double m10 = at.getShearY(); double m01 = at.getShearX(); return Math.sqrt(Math.abs(m00*m11 - m10*m01)); } }