1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.elementclass.slider;
\r
14 import java.awt.Graphics2D;
\r
15 import java.awt.geom.Line2D;
\r
16 import java.awt.geom.Path2D;
\r
17 import java.awt.geom.Point2D;
\r
18 import java.awt.geom.Rectangle2D;
\r
20 import org.simantics.g2d.canvas.ICanvasContext;
\r
21 import org.simantics.g2d.diagram.participant.DiagramParticipant;
\r
22 import org.simantics.g2d.element.ElementHints;
\r
23 import org.simantics.g2d.element.ElementUtils;
\r
24 import org.simantics.g2d.element.IElement;
\r
25 import org.simantics.g2d.element.SceneGraphNodeKey;
\r
26 import org.simantics.g2d.element.handler.SceneGraph;
\r
27 import org.simantics.g2d.element.handler.Stateful;
\r
28 import org.simantics.g2d.element.handler.impl.AbstractGrabbable;
\r
29 import org.simantics.scenegraph.Node;
\r
30 import org.simantics.scenegraph.g2d.G2DNode;
\r
31 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
32 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
33 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
\r
34 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
35 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
39 * TODO set Track Rectangle
\r
40 * @author Toni Kalajainen
\r
42 public class SliderHandle extends AbstractGrabbable implements SceneGraph {
\r
44 private static final long serialVersionUID = 3632511991491704966L;
\r
45 public static final Key KEY_SLIDER_COLOR_PROFILE = new KeyOf(SliderColorProfile.class);
\r
46 /** Grab position of handle in terms of element coordinates */
\r
47 public static final Key KEY_SLIDER_POSITION = ElementHints.KEY_VALUE;
\r
48 public static final Key KEY_SLIDER_MIN_VALUE = ElementHints.KEY_MIN_VALUE;
\r
49 public static final Key KEY_SLIDER_MAX_VALUE = ElementHints.KEY_MAX_VALUE;
\r
50 public static final SliderHandle INSTANCE = new SliderHandle();
\r
52 public Key positionKey = KEY_SLIDER_POSITION;
\r
54 public static final Key SG_NODE = new SceneGraphNodeKey(Node.class, "SUB_SG_NODE");
\r
56 public SliderHandle() {
\r
59 private final static Key KEY_HANDLE_GRAB_POS = new KeyOf(Double.class);
\r
62 public void cleanup(IElement e) {
\r
63 Node node = e.removeHint(SG_NODE);
\r
69 public void init(IElement e, G2DParentNode parent) {
\r
70 CustomSliderNode node = (CustomSliderNode) e.getHint(SG_NODE);
\r
72 node = parent.addNode(CustomSliderNode.class);
\r
73 e.setHint(SG_NODE, node);
\r
76 SliderColorProfile colors = e.getHint(KEY_SLIDER_COLOR_PROFILE);
\r
77 Rectangle2D rect = getBounds(e);
\r
78 boolean enabled = isEnabled(e);
\r
80 double handleWidth = getHandleWidth(e);
\r
81 double handleOffset = getHandleOffset(e);
\r
83 // FIXME: handleOffset is probably never updated..
\r
84 node.init(rect, enabled, colors, handleWidth, handleOffset);
\r
87 public static class CustomSliderNode extends G2DNode {
\r
91 private static final long serialVersionUID = 1423400213815428725L;
\r
92 Rectangle2D rect = null;
\r
93 boolean enabled = false;
\r
94 SliderColorProfile colors = null;
\r
95 double handleWidth = 0;
\r
96 double handleOffset = 0;
\r
99 public Rectangle2D getBoundsInLocal() {
\r
103 public void init(Rectangle2D rect, boolean enabled, SliderColorProfile colors, double handleWidth, double handleOffset) {
\r
105 this.enabled = enabled;
\r
106 this.colors = colors;
\r
107 this.handleWidth = handleWidth;
\r
108 this.handleOffset = handleOffset;
\r
112 public void render(Graphics2D g) {
\r
113 double height = rect.getHeight();
\r
114 Rectangle2D r = new Rectangle2D.Double();
\r
115 Line2D l = new Line2D.Double();
\r
117 height = height + 1;
\r
119 g.translate(rect.getMinX(), rect.getMinY());
\r
120 g.translate(handleOffset, 0);
\r
122 g.setColor((enabled?colors.HANDLE4:colors.DISABLED_HANDLE4));
\r
123 r.setFrame(1, 1, handleWidth-3, height-3);
\r
126 g.setColor((enabled?colors.HANDLE3:colors.DISABLED_HANDLE3));
\r
127 l.setLine(2, 1, handleWidth-3, 1);
\r
129 l.setLine(1, 2, 1, height-3);
\r
132 g.setColor((enabled?colors.HANDLE5:colors.DISABLED_HANDLE5));
\r
133 l.setLine(2, height-2, handleWidth-3, height-2);
\r
135 l.setLine(handleWidth-2, 2, handleWidth-2, height-3);
\r
138 g.setColor((enabled?colors.HANDLE2:colors.DISABLED_HANDLE2));
\r
139 Path2D p = new Path2D.Double();
\r
142 p.lineTo(handleWidth-3, 0);
\r
143 p.lineTo(handleWidth-1, 2);
\r
144 p.lineTo(handleWidth-1, height-3);
\r
145 p.lineTo(handleWidth-3, height-1);
\r
146 p.lineTo(2, height-1);
\r
147 p.lineTo(0, height-3);
\r
152 // Paint scratches (lines) on the handle
\r
153 if (handleWidth>height)
\r
155 g.translate((handleWidth-height)/2, 0);
\r
157 g.setColor((enabled?colors.HANDLE8:colors.DISABLED_HANDLE8));
\r
158 g.drawLine((int) ((height)*0.2), (int) ((height)*0.55)+1, (int) ((height)*0.4), (int) ((height)*0.35)+1);
\r
159 g.setColor((enabled?colors.HANDLE7:colors.DISABLED_HANDLE7));
\r
160 g.drawLine((int) ((height)*0.2), (int) ((height)*0.55), (int) ((height)*0.4), (int) ((height)*0.35));
\r
162 g.setColor((enabled?colors.HANDLE8:colors.DISABLED_HANDLE8));
\r
163 g.drawLine((int) ((height)*0.40), (int) ((height)*0.60)+1, (int) ((height)*0.65), (int) ((height)*0.30)+1);
\r
164 g.setColor((enabled?colors.HANDLE7:colors.DISABLED_HANDLE7));
\r
165 g.drawLine((int) ((height)*0.40), (int) ((height)*0.60), (int) ((height)*0.65), (int) ((height)*0.30));
\r
167 g.setColor((enabled?colors.HANDLE8:colors.DISABLED_HANDLE8));
\r
168 g.drawLine((int) ((height)*0.62), (int) ((height)*0.60)+1, (int) ((height)*0.8), (int) ((height)*0.40)+1);
\r
169 g.setColor((enabled?colors.HANDLE7:colors.DISABLED_HANDLE7));
\r
170 g.drawLine((int) ((height)*0.62), (int) ((height)*0.60), (int) ((height)*0.8), (int) ((height)*0.40));
\r
176 protected boolean onGrabCheck(IElement e, ICanvasContext ctx, int pointerId, Point2D pickPos) {
\r
177 // 1. Must be enabled
\r
178 if (!isEnabled(e)) return false;
\r
180 // 2. Grab must hit the handle
\r
181 Point2D mouseElementPos = ElementUtils.controlToElementCoordinate(e, ctx, pickPos, null);
\r
182 Rectangle2D bounds = getBounds(e);
\r
183 if (!bounds.contains(mouseElementPos)) return false;
\r
185 double x = mouseElementPos.getX() - bounds.getMinX();
\r
186 double y = mouseElementPos.getY() - bounds.getMinY();
\r
188 double handleOffset = getHandleOffset(e);
\r
189 double handleWidth = getHandleWidth(e);
\r
190 boolean pointerOnHandle = (x>=handleOffset && x<=handleOffset+handleWidth);
\r
191 //boolean pointerInBeginning = x < handleOffset;
\r
192 if (!pointerOnHandle) return false;
\r
194 // 3. Only one pointer may grab
\r
195 if (getGrabCount(e, ctx)>1) return false;
\r
197 // Everything checks --> OK
\r
202 protected void onDrag(GrabInfo gi, ICanvasContext ctx) {
\r
204 DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class);
\r
205 Rectangle2D bounds = getBounds(e);
\r
206 double grabPosOnHandle = dp.getElementHint(gi.e, KEY_HANDLE_GRAB_POS);
\r
208 // Get track length
\r
209 double trackWidth = getTrackWidth(e);
\r
210 // Get handle legnth
\r
211 double handleWidth = getHandleWidth(e);
\r
212 // Free space on the track == track - handle
\r
213 double varaa = trackWidth - handleWidth+1;
\r
214 // Where are we suggesting where the handle offset should be? (widget coordinates)
\r
215 double suggestedHandlePos = gi.dragPosElement.getX()-grabPosOnHandle;
\r
216 // widget coordinates -> offset 0..1
\r
217 double suggestedOffset = (suggestedHandlePos) /varaa;
\r
218 // 0..1 -> min..max
\r
219 double min = e.getHint(KEY_SLIDER_MIN_VALUE);
\r
220 double max = e.getHint(KEY_SLIDER_MAX_VALUE);
\r
221 double suggestedPosition = (suggestedOffset * (max-min))+min;
\r
222 setPosition(e, suggestedPosition);
\r
226 * Handle click on track
\r
229 public boolean handleMouseEvent(IElement e, ICanvasContext ctx, MouseEvent me) {
\r
230 boolean superResult = super.handleMouseEvent(e, ctx, me);
\r
231 if (superResult) return superResult;
\r
232 if (!(me instanceof MouseClickEvent)) return false;
\r
233 MouseClickEvent mpe = (MouseClickEvent) me;
\r
234 if (mpe.button != MouseEvent.LEFT_BUTTON) return false;
\r
236 // 1. Grab must hit the handle
\r
237 Point2D mouseElementPos = ElementUtils.controlToElementCoordinate(e, ctx, me.controlPosition, null);
\r
238 Rectangle2D rect = getBounds(e);
\r
239 double mx = mouseElementPos.getX();
\r
240 double my = mouseElementPos.getY();
\r
241 boolean onTrackRect = rect.contains(mx, my);
\r
242 if (!onTrackRect) return false;
\r
243 mx -= rect.getMinX();
\r
244 my -= rect.getMinY();
\r
246 double trackWidth = getTrackWidth(e);
\r
247 double handleOffset = getHandleOffset(e);
\r
248 double handleWidth = getHandleWidth(e);
\r
249 boolean pointerOnHandle = (mx>=handleOffset && mx<=handleOffset+handleWidth);
\r
250 boolean pointerInBeginning = mx < handleOffset;
\r
251 if (pointerOnHandle) return false;
\r
253 double min = e.getHint(KEY_SLIDER_MIN_VALUE);
\r
254 double max = e.getHint(KEY_SLIDER_MAX_VALUE);
\r
255 double pageIncrement = (max-min) / (trackWidth/handleWidth);
\r
256 if (!pointerInBeginning) pageIncrement *= -1;
\r
257 modifyPosition(e, -pageIncrement);
\r
263 protected void onGrab(GrabInfo gi, ICanvasContext ctx) {
\r
264 double handlePos = getHandleOffset(gi.e);
\r
265 double grabPosOnHandle = gi.grabPosElement.getX() - handlePos;
\r
266 DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class);
\r
267 dp.setElementHint(gi.e, KEY_HANDLE_GRAB_POS, grabPosOnHandle);
\r
271 protected void onGrabCancel(GrabInfo gi, ICanvasContext ctx) {
\r
272 DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class);
\r
273 dp.removeElementHint(gi.e, KEY_HANDLE_GRAB_POS);
\r
277 protected void onRelease(GrabInfo gi, ICanvasContext ctx) {
\r
278 DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class);
\r
279 dp.removeElementHint(gi.e, KEY_HANDLE_GRAB_POS);
\r
282 public synchronized void modifyPosition(IElement e, double modification) {
\r
283 Double position = e.getHint(positionKey);
\r
284 if (position==null) position = 0.0;
\r
285 double newPosition = position + modification;
\r
286 setPosition(e, newPosition);
\r
289 public synchronized void setPosition(IElement e, double position) {
\r
290 double min = e.getHint(KEY_SLIDER_MIN_VALUE);
\r
291 double max = e.getHint(KEY_SLIDER_MAX_VALUE);
\r
292 if (position<min) position = min;
\r
293 if (position>max) position = max;
\r
294 e.setHint(positionKey, position);
\r
297 public double getPosition(IElement e)
\r
299 Double d = e.getHint(positionKey);
\r
300 if (d==null) return 0.0;
\r
304 private double getHandleOffset(IElement e)
\r
306 double position = getPosition(e);
\r
308 double min = e.getHint(KEY_SLIDER_MIN_VALUE);
\r
309 double max = e.getHint(KEY_SLIDER_MAX_VALUE);
\r
310 double width = getTrackWidth(e);
\r
311 double handleWidth = _calcHandleLength(width, min, max);
\r
313 return _calcHandleOffset(width, handleWidth, position, min, max);
\r
316 protected double getHandleWidth(IElement e)
\r
318 double min = e.getHint(KEY_SLIDER_MIN_VALUE);
\r
319 double max = e.getHint(KEY_SLIDER_MAX_VALUE);
\r
320 double width = getTrackWidth(e);
\r
321 return _calcHandleLength(width, min, max);
\r
324 private double getTrackWidth(IElement e)
\r
326 return getBounds(e).getWidth();
\r
330 * Calculates the offset of the handle in element coordinates
\r
331 * @return offset of the handle in element coordinates
\r
333 private static double _calcHandleOffset(double trackLength, double handleLength, double position, double min, double max)
\r
335 double varaa = trackLength - handleLength+1;
\r
336 double relativePos = ((position-min))/(max-min);
\r
337 return varaa * relativePos;
\r
341 * Calculate the length of the handle
\r
343 private static double _calcHandleLength(double width, double min, double max)
\r
345 double len = width / ((max-min)+1);
\r
346 if (len<28) len = 28;
\r
350 public boolean isEnabled(IElement e) {
\r
351 Stateful enabled = e.getElementClass().getAtMostOneItemOfClass(Stateful.class);
\r
352 if (enabled==null) return true;
\r
353 return enabled.isEnabled(e);
\r
356 protected Rectangle2D getBounds(IElement e)
\r
358 return ElementUtils.getElementBounds(e);
\r