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
14 * @author Toni Kalajainen
\r
16 package org.simantics.g2d.participant;
\r
18 import java.awt.geom.Point2D;
\r
19 import java.util.Collection;
\r
20 import java.util.HashMap;
\r
21 import java.util.Map;
\r
22 import java.util.Map.Entry;
\r
24 import org.simantics.g2d.canvas.Hints;
\r
25 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
\r
26 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
27 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
\r
28 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
29 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
\r
30 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
31 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
\r
32 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
\r
33 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
34 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;
\r
35 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
\r
36 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
37 import org.simantics.utils.datastructures.hints.IHintObservable;
\r
38 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
41 * MouseUtil tracks position and button status of mice.
\r
42 * It remembers where mouse was on control and on diagram when each of its button
\r
45 * MouseUtil generates synthetic mouse events {@link MouseClickEvent} and
\r
46 * {@link MouseDragBegin} for convenience.
\r
48 * It also tracks distance how far mouse has moved since each of its
\r
49 * button was pressed (used for click).
\r
51 * There is also debug cursor painter which can be enabled/disabled with
\r
52 * hint KEY_CURSOR_STATUS.
\r
54 * @TODO From to mouse monitor and mouse painter classes
\r
56 public class MouseUtil extends AbstractCanvasParticipant {
\r
58 protected @Dependency TransformUtil util;
\r
61 protected Map<Integer, MouseInfo> miceInfo = new HashMap<Integer, MouseInfo>();
\r
62 protected Map<Integer, MouseInfo> micePressedInfo = new HashMap<Integer, MouseInfo>();
\r
64 public static class MouseClickProfile {
\r
65 public static final MouseClickProfile DEFAULT = new MouseClickProfile();
\r
66 /** Maximum time of holding a button down that counts as a click */
\r
67 public long clickHoldTimeTolerance = 300L;
\r
68 /** Maximum number of pixels mouse may move while pressed to count a click */
\r
69 public double movementTolerance = 3.5;
\r
70 /** Maximum time to wait for clicks to count as consecutive */
\r
71 public long consecutiveToleranceTime = 380L;
\r
74 protected MouseClickProfile profile = MouseClickProfile.DEFAULT;
\r
76 @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")
\r
77 public void transformChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
78 if (oldValue != null && oldValue.equals(newValue)) return;
\r
82 @EventHandler(priority = Integer.MAX_VALUE)
\r
83 public boolean handleMouseEvent(MouseEvent e)
\r
85 assertDependencies();
\r
86 if (e instanceof MouseEnterEvent)
\r
88 Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
\r
90 MouseInfo mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);
\r
91 miceInfo.put(e.mouseId, mi);
\r
93 if (e instanceof MouseExitEvent)
\r
95 miceInfo.remove(e.mouseId);
\r
97 if (e instanceof MouseMovedEvent)
\r
99 Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
\r
100 double deltaDistance = 0;
\r
101 MouseInfo mi = miceInfo.get(e.mouseId);
\r
103 mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, 0/*e.buttons*/);
\r
104 miceInfo.put(e.mouseId, mi);
\r
106 deltaDistance = e.controlPosition.distance(mi.controlPosition);
\r
107 mi.controlPosition = e.controlPosition;
\r
108 mi.canvasPosition = canvasPosition;
\r
111 if (deltaDistance>0)
\r
112 mi.addDistanceForButtons(deltaDistance);
\r
114 // Send mouse drag events.
\r
115 for (ButtonInfo bi : mi.buttonPressInfo.values())
\r
117 if (!bi.down) continue;
\r
118 if (bi.deltaMotion<=profile.movementTolerance) continue;
\r
119 if (bi.drag) continue;
\r
121 MouseDragBegin db = new MouseDragBegin(
\r
122 this, e.time, e.mouseId, e.buttons, e.stateMask, bi.button,
\r
123 bi.canvasPosition, bi.controlPosition,
\r
124 e.controlPosition, e.screenPosition
\r
126 getContext().getEventQueue().queueFirst(db);
\r
130 if (e instanceof MouseButtonPressedEvent)
\r
132 Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
\r
133 MouseButtonPressedEvent me = (MouseButtonPressedEvent) e;
\r
134 MouseInfo mi = miceInfo.get(e.mouseId);
\r
136 mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);
\r
137 miceInfo.put(e.mouseId, mi);
\r
138 micePressedInfo.put(e.mouseId, mi);
\r
140 mi.controlPosition = e.controlPosition;
\r
141 mi.canvasPosition = canvasPosition;
\r
142 micePressedInfo.put(e.mouseId,
\r
143 new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons));
\r
145 mi.setButtonPressed(me.button, e.stateMask, e.controlPosition, canvasPosition, e.time);
\r
146 } else if (e instanceof MouseButtonReleasedEvent) {
\r
147 MouseButtonReleasedEvent me = (MouseButtonReleasedEvent) e;
\r
148 Point2D canvasPosition = util.controlToCanvas(me.controlPosition, null);
\r
149 MouseInfo mi = miceInfo.get(me.mouseId);
\r
151 mi = new MouseInfo(e.mouseId, me.controlPosition, canvasPosition, 0/*me.buttons*/);
\r
152 miceInfo.put(me.mouseId, mi);
\r
154 mi.controlPosition = me.controlPosition;
\r
155 mi.canvasPosition = canvasPosition;
\r
157 ButtonInfo bi = mi.releaseButton(me.button, me.time);
\r
158 if (bi==null) return false;
\r
160 if (me.holdTime > profile.clickHoldTimeTolerance) return false;
\r
161 if (bi.deltaMotion>profile.movementTolerance) return false;
\r
164 long timeSinceLastClick = me.time - bi.lastClickEventTime;
\r
165 bi.lastClickEventTime = me.time;
\r
167 // reset click counter
\r
168 if (timeSinceLastClick>profile.consecutiveToleranceTime)
\r
173 MouseClickEvent ce =
\r
174 new MouseClickEvent(getContext(), e.time, e.mouseId, e.buttons, e.stateMask, me.button, bi.clickCount, me.controlPosition, me.screenPosition);
\r
175 getContext().getEventQueue().queueFirst(ce);
\r
181 * Get last known position of a mouse
\r
183 * @param mouseId mouse id
\r
184 * @return mouse position in canvas coordinates or null if mouse exited
\r
187 public MouseInfo getMouseInfo(int mouseId) {
\r
188 return miceInfo.get(mouseId);
\r
191 public MouseInfo getMousePressedInfo(int mouseId) {
\r
192 return micePressedInfo.get(mouseId);
\r
197 * @param mouseId mouse id
\r
198 * @param buttonId button id
\r
199 * @return null if button has never been pressed, or button info
\r
201 public ButtonInfo getButtonInfo(int mouseId, int buttonId)
\r
203 MouseInfo mi = miceInfo.get(mouseId);
\r
204 if (mi==null) return null;
\r
205 return mi.getButtonPressInfo(buttonId);
\r
209 * Get a snapshot of statuses of all mice
\r
210 * @return a snapshot
\r
212 public Map<Integer, MouseInfo> getMiceInfo()
\r
214 return new HashMap<Integer, MouseInfo>(miceInfo);
\r
217 public int getMouseCount()
\r
219 return miceInfo.size();
\r
222 public MouseClickProfile getProfile() {
\r
226 public void setProfile(MouseClickProfile profile) {
\r
227 assert(profile!=null);
\r
228 this.profile = profile;
\r
231 public static final class MouseInfo {
\r
232 public int mouseId;
\r
233 public Point2D controlPosition;
\r
234 public Point2D canvasPosition;
\r
235 public int buttons;
\r
236 public MouseInfo(int mouseId, Point2D initialControlPos, Point2D initialCanvasPosition, int initialButtonMask) {
\r
237 this.mouseId = mouseId;
\r
238 this.controlPosition = initialControlPos;
\r
239 this.canvasPosition = initialCanvasPosition;
\r
241 while (initialButtonMask!=0) {
\r
242 if ((initialButtonMask & 1)==1)
\r
243 setButtonPressed(buttonId, 0, initialControlPos, initialCanvasPosition, Long.MIN_VALUE);
\r
244 initialButtonMask >>>= 1;
\r
249 public boolean isMouseButtonPressed(int buttonId)
\r
251 ButtonInfo bi = buttonPressInfo.get(buttonId);
\r
252 return bi!=null && bi.down;
\r
254 private void _countButtonMask() {
\r
256 for (ButtonInfo pi : buttonPressInfo.values())
\r
258 if (!pi.down) continue;
\r
259 result |= 1 << (pi.button-1);
\r
261 this.buttons = result;
\r
263 public Map<Integer, ButtonInfo> buttonPressInfo = new HashMap<Integer, ButtonInfo>();
\r
264 public void setButtonPressed(int buttonId, int stateMask, Point2D controlPos, Point2D canvasPos, long eventTime) {
\r
265 ButtonInfo bi = getOrCreateButtonInfo(buttonId);
\r
266 bi.canvasPosition = canvasPos;
\r
267 bi.controlPosition = controlPos;
\r
268 bi.systemTime = System.currentTimeMillis();
\r
269 bi.eventTime = eventTime;
\r
271 bi.deltaMotion = 0;
\r
273 bi.stateMask = stateMask;
\r
274 _countButtonMask();
\r
276 public ButtonInfo releaseButton(int buttonId, long eventTime) {
\r
277 ButtonInfo bi = getButtonPressInfo(buttonId);
\r
278 if (bi==null) return null;
\r
280 bi.holdTime = eventTime - bi.eventTime;
\r
281 _countButtonMask();
\r
284 ButtonInfo getOrCreateButtonInfo(int buttonId)
\r
286 ButtonInfo bi = buttonPressInfo.get(buttonId);
\r
287 if (bi==null) bi = new ButtonInfo(buttonId);
\r
288 buttonPressInfo.put(buttonId, bi);
\r
291 public ButtonInfo getButtonPressInfo(int buttonId) {
\r
292 return buttonPressInfo.get(buttonId);
\r
294 public void addDistanceForButtons(double distance) {
\r
295 for (ButtonInfo bi : buttonPressInfo.values())
\r
297 if (!bi.down) continue;
\r
298 bi.deltaMotion += distance;
\r
301 public Collection<ButtonInfo> getButtonInfos() {
\r
302 return buttonPressInfo.values();
\r
306 /** Status of mouse's button press */
\r
307 public static final class ButtonInfo {
\r
308 /** Position on press */
\r
309 public Point2D controlPosition;
\r
310 /** Position on press */
\r
311 public Point2D canvasPosition;
\r
312 public final int button;
\r
313 public int stateMask;
\r
314 /** System time when pressed */
\r
315 public long systemTime;
\r
316 /** Event time when pressed */
\r
317 public long eventTime;
\r
318 /** Hold time when released */
\r
319 public long holdTime;
\r
320 /** Current up / down status */
\r
321 public boolean down = false;
\r
322 /** Total movement in pixels since press */
\r
323 public double deltaMotion = 0.0;
\r
325 public int clickCount = 0;
\r
326 /** Time of last click */
\r
327 public long lastClickEventTime = Long.MIN_VALUE;
\r
328 /** Dragged (set true when possibility for click is excluded) */
\r
329 public boolean drag = false;
\r
330 public ButtonInfo(int button) {
\r
331 this.button = button;
\r
336 * Sends mice positions as an event
\r
338 private void sendMicePos()
\r
340 long time = System.currentTimeMillis();
\r
341 for (Entry<Integer, MouseInfo> e : miceInfo.entrySet()) {
\r
342 MouseInfo mi = e.getValue();
\r
343 MouseMovedEvent mme;
\r
344 // FIXME: screenPosition as null (requires adding screenPos into MouseInfo)
\r
345 mme = new MouseMovedEvent(getContext(), time, e.getKey(), mi.buttons, 0 /* FIXME ??? */, mi.controlPosition, null);
\r
346 getContext().getEventQueue().queueEvent(mme);
\r