/******************************************************************************* * 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.g2d.diagram.participant; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.List; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor; import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil; import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo; import org.simantics.g2d.participant.MouseUtil; import org.simantics.g2d.participant.TransformUtil; import org.simantics.g2d.participant.MouseUtil.MouseInfo; import org.simantics.g2d.utils.GeometryUtils; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; import org.simantics.scenegraph.g2d.nodes.ShapeNode; import org.simantics.scenegraph.utils.ColorUtil; import org.simantics.utils.datastructures.hints.HintListenerAdapter; import org.simantics.utils.datastructures.hints.IHintListener; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Paints terminals of elements. * * @author Toni Kalajainen */ public class TerminalPainter extends AbstractDiagramParticipant { private static final Logger LOGGER = LoggerFactory.getLogger(TerminalPainter.class); public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 10; public interface TerminalHoverStrategy { /** * * @return true if highlighting is enabled at the moment in * general. This may depend on the current modifier key state * for example. */ boolean highlightEnabled(); /** * Checks whether the specified terminal should be highlighted at the * moment or not. Whether to highlight or not may depend for example on * the current modifier key state. * * @param ti * @return */ boolean highlight(TerminalInfo ti); }; public static abstract class ChainedHoverStrategy implements TerminalHoverStrategy { TerminalHoverStrategy orig; public ChainedHoverStrategy(TerminalHoverStrategy orig) { this.orig = orig; } @Override public boolean highlightEnabled() { return orig == null ? false : orig.highlightEnabled(); } @Override public final boolean highlight(TerminalInfo ti) { boolean ret = canHighlight(ti); return (ret || orig == null) ? ret : orig.highlight(ti); } public abstract boolean canHighlight(TerminalInfo ti); } /** * If this hint is set to a Callable, the terminal painter will use * the callable to evaluate whether it should highlight terminal hovers or * not. */ public static final Key TERMINAL_HOVER_STRATEGY = new KeyOf(TerminalHoverStrategy.class); // private static final Stroke STROKE1 = new BasicStroke(1.0f); // private static final Stroke STROKE15 = new BasicStroke(1.5f); private static final Stroke STROKE25 = new BasicStroke(2.5f); // private final static Stroke TERMINAL_STROKE = new BasicStroke(1.0f); public static final Shape TERMINAL_SHAPE; @Dependency protected TransformUtil util; @Dependency protected MouseUtil mice; @Dependency protected PointerInteractor pointerInteractor; protected boolean paintPointTerminals; protected boolean paintAreaTerminals; protected boolean paintHoverPointTerminals; protected boolean paintHoverAreaTerminals; public TerminalPainter(boolean paintPointTerminals, boolean paintHoverPointTerminals, boolean paintAreaTerminals, boolean paintHoverAreaTerminals) { this.paintAreaTerminals = paintAreaTerminals; this.paintPointTerminals = paintPointTerminals; this.paintHoverAreaTerminals = paintHoverAreaTerminals; this.paintHoverPointTerminals = paintHoverPointTerminals; } @Override public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener); } @Override public void removedFromContext(ICanvasContext ctx) { ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), TERMINAL_HOVER_STRATEGY, hoverStrategyListener); super.removedFromContext(ctx); } public boolean highlightEnabled() { TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY); return strategy != null ? strategy.highlightEnabled() : true; } public boolean highlightTerminal(TerminalInfo ti) { TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY); return strategy != null ? strategy.highlight(ti) : true; } @EventHandler(priority = 0) public boolean handleMove(MouseMovedEvent me) { if ( (paintHoverAreaTerminals && paintAreaTerminals) || (paintHoverPointTerminals && paintPointTerminals) ) { update(highlightEnabled()); } return false; } protected G2DParentNode node = null; @SGInit public void initSG(G2DParentNode parent) { node = parent.addNode("hovering terminals", G2DParentNode.class); node.setZIndex(PAINT_PRIORITY); } @SGCleanup public void cleanupSG() { node.remove(); node = null; } public void update(boolean enabled) { if (isRemoved()) return; boolean repaint = false; if(node == null) return; if(node.getNodeCount() > 0) { node.removeNodes(); repaint = true; } if (enabled) { // Paint terminals normally if (paintAreaTerminals || paintPointTerminals) { List pickedTerminals = TerminalUtil.pickTerminals(getContext(), diagram, null, paintPointTerminals, paintAreaTerminals); paintTerminals(node, Color.BLUE, diagram, null, pickedTerminals, null); if(pickedTerminals.size() > 0) repaint = true; } if (paintHoverAreaTerminals || paintHoverPointTerminals) { TerminalHoverStrategy strategy = getHint(TERMINAL_HOVER_STRATEGY); AffineTransform invTx = util.getInverseTransform(); if (invTx == null) { LOGGER.warn("NO CANVAS TRANSFORM INVERSE AVAILABLE, CANVAS TRANSFORM IS: " + util.getTransform()); return; } // Pick terminals for (MouseInfo mi : mice.getMiceInfo().values()) { Rectangle2D controlPickRect = getPickRectangle(mi.controlPosition.getX(), mi.controlPosition.getY()); Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, invTx); List tis = TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals); paintTerminals(node, Color.RED, diagram, canvasPickRect.getBounds2D(), tis, strategy); if(tis.size() > 0) repaint = true; } } } if (repaint) { setDirty(); } } public void paintTerminals(G2DParentNode parent, Color color, IDiagram diagram, Rectangle2D pickRect, Collection tis, TerminalHoverStrategy strategy) { if (tis.isEmpty()) { return; } G2DParentNode node = parent.getOrCreateNode(""+tis.hashCode(), G2DParentNode.class); double minDist = Double.MAX_VALUE; double maxDist = 0; TerminalInfo nearest = null; if (pickRect != null) { for (TerminalInfo ti : tis) { double dx = ti.posDia.getTranslateX() - pickRect.getCenterX(); double dy = ti.posDia.getTranslateY() - pickRect.getCenterY(); double dist = Math.sqrt(dx*dx+dy*dy); if (dist > maxDist) { maxDist = dist; } if (dist < minDist) { minDist = dist; nearest = ti; } } } for (TerminalInfo ti : tis) { if (strategy != null && !strategy.highlight(ti)) continue; Shape shape = ti.shape != null ? ti.shape : getTerminalShape(); //System.out.println("painting terminal " + ti + ": " + shape); ShapeNode sn = node.getOrCreateNode("terminal_"+ti.hashCode(), ShapeNode.class); sn.setShape(shape); sn.setStroke(STROKE25); sn.setScaleStroke(true); if (pickRect != null) { Color blendedColor = color; if (ti != nearest) { double dx = ti.posDia.getTranslateX() - pickRect.getCenterX(); double dy = ti.posDia.getTranslateY() - pickRect.getCenterY(); double dist = Math.sqrt(dx*dx+dy*dy); double normalizedDistance = dist / maxDist; final double maxFade = 0.5; float alpha = (float)(1 - normalizedDistance*maxFade); blendedColor = ColorUtil.withAlpha(ColorUtil.blend(color, Color.WHITE, normalizedDistance*maxFade), alpha); } sn.setColor(blendedColor); } else { sn.setColor(color); } sn.setTransform(ti.posDia); sn.setFill(false); } } public Rectangle2D getTerminalShape() { double pickDist = pointerInteractor.getPickDistance(); return new Rectangle2D.Double(-pickDist - 0.5, -pickDist - 0.5, pickDist * 2 + 1, pickDist * 2 + 1); } public Rectangle2D getPickRectangle(double x, double y) { double pickDist = pointerInteractor.getPickDistance(); Rectangle2D controlPickRect = new Rectangle2D.Double(x-pickDist, y-pickDist, pickDist*2+1, pickDist*2+1); return controlPickRect; } static { Path2D.Double cross = new Path2D.Double(); double s = 2; cross.moveTo(-s, -s); cross.lineTo(s, s); cross.moveTo(-s, s); cross.lineTo(s, -s); TERMINAL_SHAPE = cross; } IHintListener hoverStrategyListener = new HintListenerAdapter() { @Override public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { hoverStrategyChanged((TerminalHoverStrategy) newValue); } }; protected void hoverStrategyChanged(TerminalHoverStrategy strategy) { update(strategy != null ? strategy.highlightEnabled() : false); } }