/******************************************************************************* * 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.elementclass.slider; import java.awt.Graphics2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.diagram.participant.DiagramParticipant; import org.simantics.g2d.element.ElementHints; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.SceneGraphNodeKey; import org.simantics.g2d.element.handler.SceneGraph; import org.simantics.g2d.element.handler.Stateful; import org.simantics.g2d.element.handler.impl.AbstractGrabbable; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.g2d.G2DNode; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; /** * * TODO set Track Rectangle * @author Toni Kalajainen */ public class SliderHandle extends AbstractGrabbable implements SceneGraph { private static final long serialVersionUID = 3632511991491704966L; public static final Key KEY_SLIDER_COLOR_PROFILE = new KeyOf(SliderColorProfile.class); /** Grab position of handle in terms of element coordinates */ public static final Key KEY_SLIDER_POSITION = ElementHints.KEY_VALUE; public static final Key KEY_SLIDER_MIN_VALUE = ElementHints.KEY_MIN_VALUE; public static final Key KEY_SLIDER_MAX_VALUE = ElementHints.KEY_MAX_VALUE; public static final SliderHandle INSTANCE = new SliderHandle(); public Key positionKey = KEY_SLIDER_POSITION; public static final Key SG_NODE = new SceneGraphNodeKey(Node.class, "SUB_SG_NODE"); public SliderHandle() { super(1000.0); } private final static Key KEY_HANDLE_GRAB_POS = new KeyOf(Double.class); @Override public void cleanup(IElement e) { Node node = e.removeHint(SG_NODE); if (node != null) node.remove(); } @Override public void init(IElement e, G2DParentNode parent) { CustomSliderNode node = (CustomSliderNode) e.getHint(SG_NODE); if (node == null) { node = parent.addNode(CustomSliderNode.class); e.setHint(SG_NODE, node); } SliderColorProfile colors = e.getHint(KEY_SLIDER_COLOR_PROFILE); Rectangle2D rect = getBounds(e); boolean enabled = isEnabled(e); double handleWidth = getHandleWidth(e); double handleOffset = getHandleOffset(e); // FIXME: handleOffset is probably never updated.. node.init(rect, enabled, colors, handleWidth, handleOffset); } public static class CustomSliderNode extends G2DNode { /** * */ private static final long serialVersionUID = 1423400213815428725L; Rectangle2D rect = null; boolean enabled = false; SliderColorProfile colors = null; double handleWidth = 0; double handleOffset = 0; @Override public Rectangle2D getBoundsInLocal() { return rect; } public void init(Rectangle2D rect, boolean enabled, SliderColorProfile colors, double handleWidth, double handleOffset) { this.rect = rect; this.enabled = enabled; this.colors = colors; this.handleWidth = handleWidth; this.handleOffset = handleOffset; } @Override public void render(Graphics2D g) { double height = rect.getHeight(); Rectangle2D r = new Rectangle2D.Double(); Line2D l = new Line2D.Double(); height = height + 1; g.translate(rect.getMinX(), rect.getMinY()); g.translate(handleOffset, 0); g.setColor((enabled?colors.HANDLE4:colors.DISABLED_HANDLE4)); r.setFrame(1, 1, handleWidth-3, height-3); g.fill(r); g.setColor((enabled?colors.HANDLE3:colors.DISABLED_HANDLE3)); l.setLine(2, 1, handleWidth-3, 1); g.draw(l); l.setLine(1, 2, 1, height-3); g.draw(l); g.setColor((enabled?colors.HANDLE5:colors.DISABLED_HANDLE5)); l.setLine(2, height-2, handleWidth-3, height-2); g.draw(l); l.setLine(handleWidth-2, 2, handleWidth-2, height-3); g.draw(l); g.setColor((enabled?colors.HANDLE2:colors.DISABLED_HANDLE2)); Path2D p = new Path2D.Double(); p.moveTo(0, 2); p.lineTo(2, 0); p.lineTo(handleWidth-3, 0); p.lineTo(handleWidth-1, 2); p.lineTo(handleWidth-1, height-3); p.lineTo(handleWidth-3, height-1); p.lineTo(2, height-1); p.lineTo(0, height-3); p.lineTo(0, 2); p.closePath(); g.draw(p); // Paint scratches (lines) on the handle if (handleWidth>height) { g.translate((handleWidth-height)/2, 0); g.setColor((enabled?colors.HANDLE8:colors.DISABLED_HANDLE8)); g.drawLine((int) ((height)*0.2), (int) ((height)*0.55)+1, (int) ((height)*0.4), (int) ((height)*0.35)+1); g.setColor((enabled?colors.HANDLE7:colors.DISABLED_HANDLE7)); g.drawLine((int) ((height)*0.2), (int) ((height)*0.55), (int) ((height)*0.4), (int) ((height)*0.35)); g.setColor((enabled?colors.HANDLE8:colors.DISABLED_HANDLE8)); g.drawLine((int) ((height)*0.40), (int) ((height)*0.60)+1, (int) ((height)*0.65), (int) ((height)*0.30)+1); g.setColor((enabled?colors.HANDLE7:colors.DISABLED_HANDLE7)); g.drawLine((int) ((height)*0.40), (int) ((height)*0.60), (int) ((height)*0.65), (int) ((height)*0.30)); g.setColor((enabled?colors.HANDLE8:colors.DISABLED_HANDLE8)); g.drawLine((int) ((height)*0.62), (int) ((height)*0.60)+1, (int) ((height)*0.8), (int) ((height)*0.40)+1); g.setColor((enabled?colors.HANDLE7:colors.DISABLED_HANDLE7)); g.drawLine((int) ((height)*0.62), (int) ((height)*0.60), (int) ((height)*0.8), (int) ((height)*0.40)); } } } @Override protected boolean onGrabCheck(IElement e, ICanvasContext ctx, int pointerId, Point2D pickPos) { // 1. Must be enabled if (!isEnabled(e)) return false; // 2. Grab must hit the handle Point2D mouseElementPos = ElementUtils.controlToElementCoordinate(e, ctx, pickPos, null); Rectangle2D bounds = getBounds(e); if (!bounds.contains(mouseElementPos)) return false; double x = mouseElementPos.getX() - bounds.getMinX(); double y = mouseElementPos.getY() - bounds.getMinY(); double handleOffset = getHandleOffset(e); double handleWidth = getHandleWidth(e); boolean pointerOnHandle = (x>=handleOffset && x<=handleOffset+handleWidth); //boolean pointerInBeginning = x < handleOffset; if (!pointerOnHandle) return false; // 3. Only one pointer may grab if (getGrabCount(e, ctx)>1) return false; // Everything checks --> OK return true; } @Override protected void onDrag(GrabInfo gi, ICanvasContext ctx) { IElement e = gi.e; DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class); Rectangle2D bounds = getBounds(e); double grabPosOnHandle = dp.getElementHint(gi.e, KEY_HANDLE_GRAB_POS); // Get track length double trackWidth = getTrackWidth(e); // Get handle legnth double handleWidth = getHandleWidth(e); // Free space on the track == track - handle double varaa = trackWidth - handleWidth+1; // Where are we suggesting where the handle offset should be? (widget coordinates) double suggestedHandlePos = gi.dragPosElement.getX()-grabPosOnHandle; // widget coordinates -> offset 0..1 double suggestedOffset = (suggestedHandlePos) /varaa; // 0..1 -> min..max double min = e.getHint(KEY_SLIDER_MIN_VALUE); double max = e.getHint(KEY_SLIDER_MAX_VALUE); double suggestedPosition = (suggestedOffset * (max-min))+min; setPosition(e, suggestedPosition); } /** * Handle click on track */ @Override public boolean handleMouseEvent(IElement e, ICanvasContext ctx, MouseEvent me) { boolean superResult = super.handleMouseEvent(e, ctx, me); if (superResult) return superResult; if (!(me instanceof MouseClickEvent)) return false; MouseClickEvent mpe = (MouseClickEvent) me; if (mpe.button != MouseEvent.LEFT_BUTTON) return false; // 1. Grab must hit the handle Point2D mouseElementPos = ElementUtils.controlToElementCoordinate(e, ctx, me.controlPosition, null); Rectangle2D rect = getBounds(e); double mx = mouseElementPos.getX(); double my = mouseElementPos.getY(); boolean onTrackRect = rect.contains(mx, my); if (!onTrackRect) return false; mx -= rect.getMinX(); my -= rect.getMinY(); double trackWidth = getTrackWidth(e); double handleOffset = getHandleOffset(e); double handleWidth = getHandleWidth(e); boolean pointerOnHandle = (mx>=handleOffset && mx<=handleOffset+handleWidth); boolean pointerInBeginning = mx < handleOffset; if (pointerOnHandle) return false; double min = e.getHint(KEY_SLIDER_MIN_VALUE); double max = e.getHint(KEY_SLIDER_MAX_VALUE); double pageIncrement = (max-min) / (trackWidth/handleWidth); if (!pointerInBeginning) pageIncrement *= -1; modifyPosition(e, -pageIncrement); return true; } @Override protected void onGrab(GrabInfo gi, ICanvasContext ctx) { double handlePos = getHandleOffset(gi.e); double grabPosOnHandle = gi.grabPosElement.getX() - handlePos; DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class); dp.setElementHint(gi.e, KEY_HANDLE_GRAB_POS, grabPosOnHandle); } @Override protected void onGrabCancel(GrabInfo gi, ICanvasContext ctx) { DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class); dp.removeElementHint(gi.e, KEY_HANDLE_GRAB_POS); } @Override protected void onRelease(GrabInfo gi, ICanvasContext ctx) { DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class); dp.removeElementHint(gi.e, KEY_HANDLE_GRAB_POS); } public synchronized void modifyPosition(IElement e, double modification) { Double position = e.getHint(positionKey); if (position==null) position = 0.0; double newPosition = position + modification; setPosition(e, newPosition); } public synchronized void setPosition(IElement e, double position) { double min = e.getHint(KEY_SLIDER_MIN_VALUE); double max = e.getHint(KEY_SLIDER_MAX_VALUE); if (positionmax) position = max; e.setHint(positionKey, position); } public double getPosition(IElement e) { Double d = e.getHint(positionKey); if (d==null) return 0.0; return d; } private double getHandleOffset(IElement e) { double position = getPosition(e); double min = e.getHint(KEY_SLIDER_MIN_VALUE); double max = e.getHint(KEY_SLIDER_MAX_VALUE); double width = getTrackWidth(e); double handleWidth = _calcHandleLength(width, min, max); return _calcHandleOffset(width, handleWidth, position, min, max); } protected double getHandleWidth(IElement e) { double min = e.getHint(KEY_SLIDER_MIN_VALUE); double max = e.getHint(KEY_SLIDER_MAX_VALUE); double width = getTrackWidth(e); return _calcHandleLength(width, min, max); } private double getTrackWidth(IElement e) { return getBounds(e).getWidth(); } /** * Calculates the offset of the handle in element coordinates * @return offset of the handle in element coordinates */ private static double _calcHandleOffset(double trackLength, double handleLength, double position, double min, double max) { double varaa = trackLength - handleLength+1; double relativePos = ((position-min))/(max-min); return varaa * relativePos; } /** * Calculate the length of the handle */ private static double _calcHandleLength(double width, double min, double max) { double len = width / ((max-min)+1); if (len<28) len = 28; return len; } public boolean isEnabled(IElement e) { Stateful enabled = e.getElementClass().getAtMostOneItemOfClass(Stateful.class); if (enabled==null) return true; return enabled.isEnabled(e); } protected Rectangle2D getBounds(IElement e) { return ElementUtils.getElementBounds(e); } }