]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/participant/MouseUtil.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / participant / MouseUtil.java
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
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 /*\r
13  *\r
14  * @author Toni Kalajainen\r
15  */\r
16 package org.simantics.g2d.participant;\r
17 \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
23 \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
39 \r
40 /**\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
43  * was pressed. \r
44  * \r
45  * MouseUtil generates synthetic mouse events {@link MouseClickEvent} and\r
46  * {@link MouseDragBegin} for convenience. \r
47  * \r
48  * It also tracks distance how far mouse has moved since each of its \r
49  * button was pressed (used for click).\r
50  * <p>\r
51  * There is also debug cursor painter which can be enabled/disabled with \r
52  * hint KEY_CURSOR_STATUS.\r
53  * \r
54  * @TODO From to mouse monitor and mouse painter classes\r
55  */\r
56 public class MouseUtil extends AbstractCanvasParticipant {      \r
57 \r
58     protected @Dependency TransformUtil util;\r
59 \r
60     /** Mice info */\r
61     protected  Map<Integer, MouseInfo> miceInfo = new HashMap<Integer, MouseInfo>();\r
62     protected  Map<Integer, MouseInfo> micePressedInfo = new HashMap<Integer, MouseInfo>();\r
63 \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
72     }\r
73 \r
74     protected MouseClickProfile profile = MouseClickProfile.DEFAULT;\r
75 \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
79         sendMicePos();\r
80     }\r
81 \r
82     @EventHandler(priority = Integer.MAX_VALUE)\r
83     public boolean handleMouseEvent(MouseEvent e) \r
84     {\r
85         assertDependencies();\r
86         if (e instanceof MouseEnterEvent)\r
87         {\r
88             Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);\r
89             // Reset mouse\r
90             MouseInfo mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);\r
91             miceInfo.put(e.mouseId, mi);\r
92         } else \r
93             if (e instanceof MouseExitEvent)\r
94             {\r
95                 miceInfo.remove(e.mouseId);\r
96             } else\r
97                 if (e instanceof MouseMovedEvent)\r
98                 {\r
99                     Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);\r
100                     double deltaDistance = 0;\r
101                     MouseInfo mi = miceInfo.get(e.mouseId);\r
102                     if (mi==null) {\r
103                         mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, 0/*e.buttons*/);\r
104                         miceInfo.put(e.mouseId, mi);\r
105                     } else {\r
106                         deltaDistance = e.controlPosition.distance(mi.controlPosition);\r
107                         mi.controlPosition = e.controlPosition;\r
108                         mi.canvasPosition = canvasPosition;\r
109                     }\r
110 \r
111                     if (deltaDistance>0)\r
112                         mi.addDistanceForButtons(deltaDistance);\r
113 \r
114                     // Send mouse drag events.\r
115                     for (ButtonInfo bi : mi.buttonPressInfo.values())\r
116                     {\r
117                         if (!bi.down) continue;\r
118                         if (bi.deltaMotion<=profile.movementTolerance) continue;\r
119                         if (bi.drag) continue;\r
120                         bi.drag = true;\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
125                                 );\r
126                         getContext().getEventQueue().queueFirst(db);\r
127                     }\r
128 \r
129                 } else\r
130                     if (e instanceof MouseButtonPressedEvent)\r
131                     {\r
132                         Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);\r
133                         MouseButtonPressedEvent me = (MouseButtonPressedEvent) e;\r
134                         MouseInfo mi = miceInfo.get(e.mouseId);\r
135                         if (mi==null) {\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
139                         } else {\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
144                         }                       \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
150                         if (mi==null) {\r
151                             mi = new MouseInfo(e.mouseId, me.controlPosition, canvasPosition, 0/*me.buttons*/);\r
152                             miceInfo.put(me.mouseId, mi);\r
153                         } else {\r
154                             mi.controlPosition = me.controlPosition;\r
155                             mi.canvasPosition = canvasPosition;\r
156                         }\r
157                         ButtonInfo bi = mi.releaseButton(me.button, me.time);\r
158                         if (bi==null) return false;\r
159 \r
160                         if (me.holdTime > profile.clickHoldTimeTolerance) return false;\r
161                         if (bi.deltaMotion>profile.movementTolerance) return false;\r
162                         // This is a click\r
163 \r
164                         long timeSinceLastClick = me.time - bi.lastClickEventTime;\r
165                         bi.lastClickEventTime = me.time;\r
166 \r
167                         // reset click counter\r
168                         if (timeSinceLastClick>profile.consecutiveToleranceTime) \r
169                             bi.clickCount = 1;\r
170                         else\r
171                             bi.clickCount++;\r
172 \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
176                     }\r
177         return false;\r
178     }\r
179 \r
180     /**\r
181      * Get last known position of a mouse\r
182      * \r
183      * @param mouseId mouse id\r
184      * @return mouse position in canvas coordinates or null if mouse exited\r
185      *         canvas\r
186      */\r
187     public MouseInfo getMouseInfo(int mouseId) {\r
188         return miceInfo.get(mouseId);\r
189     }\r
190 \r
191     public MouseInfo getMousePressedInfo(int mouseId) {\r
192         return micePressedInfo.get(mouseId);\r
193     }\r
194 \r
195     /**\r
196      * Get button info\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
200      */\r
201     public ButtonInfo getButtonInfo(int mouseId, int buttonId)\r
202     {\r
203         MouseInfo mi = miceInfo.get(mouseId);\r
204         if (mi==null) return null;\r
205         return mi.getButtonPressInfo(buttonId);\r
206     }\r
207 \r
208     /**\r
209      * Get a snapshot of statuses of all mice\r
210      * @return a snapshot\r
211      */\r
212     public Map<Integer, MouseInfo> getMiceInfo()\r
213     {\r
214         return new HashMap<Integer, MouseInfo>(miceInfo);\r
215     }\r
216 \r
217     public int getMouseCount()\r
218     {\r
219         return miceInfo.size();\r
220     }\r
221 \r
222     public MouseClickProfile getProfile() {\r
223         return profile;\r
224     }\r
225 \r
226     public void setProfile(MouseClickProfile profile) {\r
227         assert(profile!=null);\r
228         this.profile = profile;\r
229     }\r
230 \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
240             int buttonId = 0;\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
245                 buttonId++;\r
246             }\r
247         }\r
248 \r
249         public boolean isMouseButtonPressed(int buttonId)\r
250         {\r
251             ButtonInfo bi = buttonPressInfo.get(buttonId);\r
252             return bi!=null && bi.down;\r
253         }\r
254         private void _countButtonMask() {\r
255             int result = 0;\r
256             for (ButtonInfo pi : buttonPressInfo.values())\r
257             {\r
258                 if (!pi.down) continue;\r
259                 result |= 1 << (pi.button-1);\r
260             }\r
261             this.buttons = result;\r
262         }\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
270             bi.down = true;\r
271             bi.deltaMotion = 0;\r
272             bi.drag = false;\r
273             bi.stateMask = stateMask;\r
274             _countButtonMask();\r
275         }\r
276         public ButtonInfo releaseButton(int buttonId, long eventTime) {\r
277             ButtonInfo bi = getButtonPressInfo(buttonId);\r
278             if (bi==null) return null;\r
279             bi.down = false;\r
280             bi.holdTime = eventTime - bi.eventTime;\r
281             _countButtonMask();\r
282             return bi;\r
283         }\r
284         ButtonInfo getOrCreateButtonInfo(int buttonId)\r
285         {\r
286             ButtonInfo bi = buttonPressInfo.get(buttonId);\r
287             if (bi==null) bi = new ButtonInfo(buttonId);\r
288             buttonPressInfo.put(buttonId, bi);\r
289             return bi;\r
290         }\r
291         public ButtonInfo getButtonPressInfo(int buttonId) {\r
292             return buttonPressInfo.get(buttonId);\r
293         }\r
294         public void addDistanceForButtons(double distance) {\r
295             for (ButtonInfo bi : buttonPressInfo.values())\r
296             {\r
297                 if (!bi.down) continue;\r
298                 bi.deltaMotion += distance;\r
299             }\r
300         }\r
301         public Collection<ButtonInfo> getButtonInfos() {\r
302             return buttonPressInfo.values();\r
303         }\r
304     }\r
305 \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
324         /** Click Count */\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
332         }\r
333     }\r
334 \r
335     /**\r
336      * Sends mice positions as an event\r
337      */\r
338     private void sendMicePos()\r
339     {\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
347         }\r
348     }\r
349 \r
350 }\r