X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2Fparticipant%2FTerminalPainter.java;fp=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2Fparticipant%2FTerminalPainter.java;h=54e4a8eb7b9b7031e02948a563064b03ed98f15c;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java new file mode 100644 index 000000000..54e4a8eb7 --- /dev/null +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java @@ -0,0 +1,304 @@ +/******************************************************************************* + * 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; + +/** + * Paints terminals of elements. + * + * @author Toni Kalajainen + */ +public class TerminalPainter extends AbstractDiagramParticipant { + + 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(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) { + System.err.println("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(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); + } + +}