/*******************************************************************************
* 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);
}
}