/******************************************************************************* * 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.Composite; import java.awt.Graphics2D; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import javax.imageio.ImageIO; 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; /** * @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); } } static BufferedImage cross; static BufferedImage cut; static { cross = safeReadImage("cross.png"); cut = safeReadImage("cut.png"); } public static final Stroke STROKE = new BasicStroke(0.1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); public static final AlphaComposite COMPOSITE = AlphaComposite.SrcOver.derive(0.6f); public static final double DEGENERATED_LINE_LENGTH = 1; public static final double CUT_DIST_FROM_END = 0.5; RouteGraph rg; transient Collection lhs = new ArrayList(); transient AffineTransform transform = new AffineTransform(); transient AffineTransform transform2 = new AffineTransform(); transient Rectangle2D rect = new Rectangle2D.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; lhs.clear(); rg.getLineHalves(lhs); AffineTransform preTr = g.getTransform(); double viewScale = 1.0 / getScale(preTr); transform2.setToScale(viewScale, viewScale); Composite originalComposite = g.getComposite(); g.setComposite(COMPOSITE); Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY); if (!simpleConnection) { double crossW = cross.getWidth()*viewScale*.5; double crossH = cross.getHeight()*viewScale*.5; // Render line removal markers for (RouteLineHalf lh : lhs) { // if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH) // continue; // if (!lh.getLine().isTransient()) // continue; if (lh.getLine().getTerminal() == null) continue; 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; } boolean hit = pick.matches(Action.REMOVE, lh); if (hit) g.setComposite(originalComposite); transform.setToTranslation(x-crossW, y-crossH); g.transform(transform); g.drawImage(cross, transform2, null); g.setTransform(preTr); if (hit) g.setComposite(COMPOSITE); } } // Render reconnection markers if the connection is branched. if (branchedConnection) { double cutW = cut.getWidth()*viewScale*.5; double cutH = cut.getHeight()*viewScale*.5; final double dist = CUT_DIST_FROM_END; for (RouteLineHalf lh : lhs) { if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3) continue; 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; } boolean hit = pick.matches(Action.RECONNECT, lh); if (hit) g.setComposite(originalComposite); transform.setToTranslation(x-cutW, y-cutH); if (!lh.getLine().isHorizontal()) { transform.rotate(Math.PI/2, cutW, cutH); } g.transform(transform); g.drawImage(cut, transform2, null); g.setTransform(preTr); if (hit) g.setComposite(COMPOSITE); } } 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; lhs.clear(); rg.getLineHalves(lhs); RouteLineHalf selected = null; Action selectedAction = null; double nearest = Double.MAX_VALUE; double viewScale = 1.0 / getScale(viewTr); if (!simpleConnection) { double crossW = cross.getWidth()*viewScale*.5; double crossH = cross.getHeight()*viewScale*.5; // Render line removal markers for (RouteLineHalf lh : lhs) { // if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH) // continue; // if (!lh.getLine().isTransient()) // continue; if (lh.getLine().getTerminal() == null) continue; 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; } rect.setFrameFromCenter(x, y, x-crossW, y-crossH); 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; } } } } // Render reconnection markers if the connection is branched. if (branchedConnection) { double cutW = cut.getWidth()*viewScale*.5; double cutH = cut.getHeight()*viewScale*.5; final double dist = CUT_DIST_FROM_END; for (RouteLineHalf lh : lhs) { if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3) continue; 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; } rect.setFrameFromCenter(x, y, x-cutW, y-cutH); boolean hit = rect.contains(mouseX, mouseY); if (hit) { double distSq = distSq(x, y, mouseX, mouseY); if (distSq < nearest) { nearest = dist; selected = lh; selectedAction = Action.RECONNECT; } } } } return selected == null ? Pick.MISS : new Pick(selectedAction, selected); } 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)); } private static BufferedImage safeReadImage(String name) { try { URL url = HighlightActionPointsAction.class.getResource(name); return ImageIO.read(url); } catch (IOException e) { return null; } } }