1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
14 * @author Toni Kalajainen
16 package org.simantics.g2d.participant;
18 import java.awt.geom.Point2D;
19 import java.util.Collection;
20 import java.util.HashMap;
22 import java.util.Map.Entry;
24 import org.simantics.g2d.canvas.Hints;
25 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
26 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
27 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
28 import org.simantics.scenegraph.g2d.events.MouseEvent;
29 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
30 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
31 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
32 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
33 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
34 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;
35 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;
36 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
37 import org.simantics.utils.datastructures.hints.IHintObservable;
38 import org.simantics.utils.datastructures.hints.IHintContext.Key;
41 * MouseUtil tracks position and button status of mice.
42 * It remembers where mouse was on control and on diagram when each of its button
45 * MouseUtil generates synthetic mouse events {@link MouseClickEvent} and
46 * {@link MouseDragBegin} for convenience.
48 * It also tracks distance how far mouse has moved since each of its
49 * button was pressed (used for click).
51 * There is also debug cursor painter which can be enabled/disabled with
52 * hint KEY_CURSOR_STATUS.
54 * @TODO From to mouse monitor and mouse painter classes
56 public class MouseUtil extends AbstractCanvasParticipant {
58 protected @Dependency TransformUtil util;
61 protected Map<Integer, MouseInfo> miceInfo = new HashMap<Integer, MouseInfo>();
62 protected Map<Integer, MouseInfo> micePressedInfo = new HashMap<Integer, MouseInfo>();
64 public static class MouseClickProfile {
65 public static final MouseClickProfile DEFAULT = new MouseClickProfile();
66 /** Maximum time of holding a button down that counts as a click */
67 public long clickHoldTimeTolerance = 300L;
68 /** Maximum number of pixels mouse may move while pressed to count a click */
69 public double movementTolerance = 3.5;
70 /** Maximum time to wait for clicks to count as consecutive */
71 public long consecutiveToleranceTime = 380L;
74 protected MouseClickProfile profile = MouseClickProfile.DEFAULT;
76 @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")
77 public void transformChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
78 if (oldValue != null && oldValue.equals(newValue)) return;
82 @EventHandler(priority = Integer.MAX_VALUE)
83 public boolean handleMouseEvent(MouseEvent e)
86 if (e instanceof MouseEnterEvent)
88 Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
90 MouseInfo mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);
91 miceInfo.put(e.mouseId, mi);
93 if (e instanceof MouseExitEvent)
95 miceInfo.remove(e.mouseId);
97 if (e instanceof MouseMovedEvent)
99 Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
100 double deltaDistance = 0;
101 MouseInfo mi = miceInfo.get(e.mouseId);
103 mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, 0/*e.buttons*/);
104 miceInfo.put(e.mouseId, mi);
106 deltaDistance = e.controlPosition.distance(mi.controlPosition);
107 mi.controlPosition = e.controlPosition;
108 mi.canvasPosition = canvasPosition;
112 mi.addDistanceForButtons(deltaDistance);
115 if (e instanceof MouseButtonPressedEvent)
117 Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
118 MouseButtonPressedEvent me = (MouseButtonPressedEvent) e;
119 MouseInfo mi = miceInfo.get(e.mouseId);
121 mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);
122 miceInfo.put(e.mouseId, mi);
123 micePressedInfo.put(e.mouseId, mi);
125 mi.controlPosition = e.controlPosition;
126 mi.canvasPosition = canvasPosition;
127 micePressedInfo.put(e.mouseId,
128 new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons));
130 mi.setButtonPressed(me.button, e.stateMask, e.controlPosition, canvasPosition, e.time);
131 } else if (e instanceof MouseButtonReleasedEvent) {
132 MouseButtonReleasedEvent me = (MouseButtonReleasedEvent) e;
133 Point2D canvasPosition = util.controlToCanvas(me.controlPosition, null);
134 MouseInfo mi = miceInfo.get(me.mouseId);
136 mi = new MouseInfo(e.mouseId, me.controlPosition, canvasPosition, 0/*me.buttons*/);
137 miceInfo.put(me.mouseId, mi);
139 mi.controlPosition = me.controlPosition;
140 mi.canvasPosition = canvasPosition;
142 ButtonInfo bi = mi.releaseButton(me.button, me.time);
143 if (bi==null) return false;
145 if (me.holdTime > profile.clickHoldTimeTolerance) return false;
146 if (bi.deltaMotion>profile.movementTolerance) return false;
149 long timeSinceLastClick = me.time - bi.lastClickEventTime;
150 bi.lastClickEventTime = me.time;
152 // reset click counter
153 if (timeSinceLastClick>profile.consecutiveToleranceTime)
159 new MouseClickEvent(getContext(), e.time, e.mouseId, e.buttons, e.stateMask, me.button, bi.clickCount, me.controlPosition, me.screenPosition);
160 getContext().getEventQueue().queueFirst(ce);
166 * Get last known position of a mouse
168 * @param mouseId mouse id
169 * @return mouse position in canvas coordinates or null if mouse exited
172 public MouseInfo getMouseInfo(int mouseId) {
173 return miceInfo.get(mouseId);
176 public MouseInfo getMousePressedInfo(int mouseId) {
177 return micePressedInfo.get(mouseId);
182 * @param mouseId mouse id
183 * @param buttonId button id
184 * @return null if button has never been pressed, or button info
186 public ButtonInfo getButtonInfo(int mouseId, int buttonId)
188 MouseInfo mi = miceInfo.get(mouseId);
189 if (mi==null) return null;
190 return mi.getButtonPressInfo(buttonId);
194 * Get a snapshot of statuses of all mice
197 public Map<Integer, MouseInfo> getMiceInfo()
199 return new HashMap<Integer, MouseInfo>(miceInfo);
202 public int getMouseCount()
204 return miceInfo.size();
207 public MouseClickProfile getProfile() {
211 public void setProfile(MouseClickProfile profile) {
212 assert(profile!=null);
213 this.profile = profile;
216 public static final class MouseInfo {
218 public Point2D controlPosition;
219 public Point2D canvasPosition;
221 public MouseInfo(int mouseId, Point2D initialControlPos, Point2D initialCanvasPosition, int initialButtonMask) {
222 this.mouseId = mouseId;
223 this.controlPosition = initialControlPos;
224 this.canvasPosition = initialCanvasPosition;
226 while (initialButtonMask!=0) {
227 if ((initialButtonMask & 1)==1)
228 setButtonPressed(buttonId, 0, initialControlPos, initialCanvasPosition, Long.MIN_VALUE);
229 initialButtonMask >>>= 1;
234 public boolean isMouseButtonPressed(int buttonId)
236 ButtonInfo bi = buttonPressInfo.get(buttonId);
237 return bi!=null && bi.down;
239 private void _countButtonMask() {
241 for (ButtonInfo pi : buttonPressInfo.values())
243 if (!pi.down) continue;
244 result |= 1 << (pi.button-1);
246 this.buttons = result;
248 public Map<Integer, ButtonInfo> buttonPressInfo = new HashMap<Integer, ButtonInfo>();
249 public void setButtonPressed(int buttonId, int stateMask, Point2D controlPos, Point2D canvasPos, long eventTime) {
250 ButtonInfo bi = getOrCreateButtonInfo(buttonId);
251 bi.canvasPosition = canvasPos;
252 bi.controlPosition = controlPos;
253 bi.systemTime = System.currentTimeMillis();
254 bi.eventTime = eventTime;
258 bi.stateMask = stateMask;
261 public ButtonInfo releaseButton(int buttonId, long eventTime) {
262 ButtonInfo bi = getButtonPressInfo(buttonId);
263 if (bi==null) return null;
265 bi.holdTime = eventTime - bi.eventTime;
269 ButtonInfo getOrCreateButtonInfo(int buttonId)
271 ButtonInfo bi = buttonPressInfo.get(buttonId);
272 if (bi==null) bi = new ButtonInfo(buttonId);
273 buttonPressInfo.put(buttonId, bi);
276 public ButtonInfo getButtonPressInfo(int buttonId) {
277 return buttonPressInfo.get(buttonId);
279 public void addDistanceForButtons(double distance) {
280 for (ButtonInfo bi : buttonPressInfo.values())
282 if (!bi.down) continue;
283 bi.deltaMotion += distance;
286 public Collection<ButtonInfo> getButtonInfos() {
287 return buttonPressInfo.values();
291 /** Status of mouse's button press */
292 public static final class ButtonInfo {
293 /** Position on press */
294 public Point2D controlPosition;
295 /** Position on press */
296 public Point2D canvasPosition;
297 public final int button;
298 public int stateMask;
299 /** System time when pressed */
300 public long systemTime;
301 /** Event time when pressed */
302 public long eventTime;
303 /** Hold time when released */
304 public long holdTime;
305 /** Current up / down status */
306 public boolean down = false;
307 /** Total movement in pixels since press */
308 public double deltaMotion = 0.0;
310 public int clickCount = 0;
311 /** Time of last click */
312 public long lastClickEventTime = Long.MIN_VALUE;
313 /** Dragged (set true when possibility for click is excluded) */
314 public boolean drag = false;
315 public ButtonInfo(int button) {
316 this.button = button;
321 * Sends mice positions as an event
323 private void sendMicePos()
325 long time = System.currentTimeMillis();
326 for (Entry<Integer, MouseInfo> e : miceInfo.entrySet()) {
327 MouseInfo mi = e.getValue();
329 // FIXME: screenPosition as null (requires adding screenPos into MouseInfo)
330 mme = new MouseMovedEvent(getContext(), time, e.getKey(), mi.buttons, 0 /* FIXME ??? */, mi.controlPosition, null);
331 getContext().getEventQueue().queueEvent(mme);