]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/participant/MouseUtil.java
Minor refactorings related to SCL constructors
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / participant / MouseUtil.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 /*
13  *
14  * @author Toni Kalajainen
15  */
16 package org.simantics.g2d.participant;
17
18 import java.awt.geom.Point2D;
19 import java.util.Collection;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.Map.Entry;
23
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;
39
40 /**
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
43  * was pressed. 
44  * 
45  * MouseUtil generates synthetic mouse events {@link MouseClickEvent} and
46  * {@link MouseDragBegin} for convenience. 
47  * 
48  * It also tracks distance how far mouse has moved since each of its 
49  * button was pressed (used for click).
50  * <p>
51  * There is also debug cursor painter which can be enabled/disabled with 
52  * hint KEY_CURSOR_STATUS.
53  * 
54  * @TODO From to mouse monitor and mouse painter classes
55  */
56 public class MouseUtil extends AbstractCanvasParticipant {      
57
58     protected @Dependency TransformUtil util;
59
60     /** Mice info */
61     protected  Map<Integer, MouseInfo> miceInfo = new HashMap<Integer, MouseInfo>();
62     protected  Map<Integer, MouseInfo> micePressedInfo = new HashMap<Integer, MouseInfo>();
63
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;
72     }
73
74     protected MouseClickProfile profile = MouseClickProfile.DEFAULT;
75
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;
79         sendMicePos();
80     }
81
82     @EventHandler(priority = Integer.MAX_VALUE)
83     public boolean handleMouseEvent(MouseEvent e) 
84     {
85         assertDependencies();
86         if (e instanceof MouseEnterEvent)
87         {
88             Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
89             // Reset mouse
90             MouseInfo mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);
91             miceInfo.put(e.mouseId, mi);
92         } else 
93             if (e instanceof MouseExitEvent)
94             {
95                 miceInfo.remove(e.mouseId);
96             } else
97                 if (e instanceof MouseMovedEvent)
98                 {
99                     Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
100                     double deltaDistance = 0;
101                     MouseInfo mi = miceInfo.get(e.mouseId);
102                     if (mi==null) {
103                         mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, 0/*e.buttons*/);
104                         miceInfo.put(e.mouseId, mi);
105                     } else {
106                         deltaDistance = e.controlPosition.distance(mi.controlPosition);
107                         mi.controlPosition = e.controlPosition;
108                         mi.canvasPosition = canvasPosition;
109                     }
110
111                     if (deltaDistance>0)
112                         mi.addDistanceForButtons(deltaDistance);
113
114                     // Send mouse drag events.
115                     for (ButtonInfo bi : mi.buttonPressInfo.values())
116                     {
117                         if (!bi.down) continue;
118                         if (bi.deltaMotion<=profile.movementTolerance) continue;
119                         if (bi.drag) continue;
120                         bi.drag = true;
121                         MouseDragBegin db = new MouseDragBegin(
122                                 this, e.time, e.mouseId, e.buttons, e.stateMask, bi.button,
123                                 bi.canvasPosition, bi.controlPosition,
124                                 e.controlPosition, e.screenPosition
125                                 );
126                         getContext().getEventQueue().queueFirst(db);
127                     }
128
129                 } else
130                     if (e instanceof MouseButtonPressedEvent)
131                     {
132                         Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);
133                         MouseButtonPressedEvent me = (MouseButtonPressedEvent) e;
134                         MouseInfo mi = miceInfo.get(e.mouseId);
135                         if (mi==null) {
136                             mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);
137                             miceInfo.put(e.mouseId, mi);
138                             micePressedInfo.put(e.mouseId, mi);
139                         } else {
140                             mi.controlPosition = e.controlPosition;
141                             mi.canvasPosition = canvasPosition;
142                             micePressedInfo.put(e.mouseId, 
143                                     new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons));
144                         }                       
145                         mi.setButtonPressed(me.button, e.stateMask, e.controlPosition, canvasPosition, e.time);
146                     } else if (e instanceof MouseButtonReleasedEvent) {
147                         MouseButtonReleasedEvent me = (MouseButtonReleasedEvent) e;
148                         Point2D canvasPosition = util.controlToCanvas(me.controlPosition, null);
149                         MouseInfo mi = miceInfo.get(me.mouseId);
150                         if (mi==null) {
151                             mi = new MouseInfo(e.mouseId, me.controlPosition, canvasPosition, 0/*me.buttons*/);
152                             miceInfo.put(me.mouseId, mi);
153                         } else {
154                             mi.controlPosition = me.controlPosition;
155                             mi.canvasPosition = canvasPosition;
156                         }
157                         ButtonInfo bi = mi.releaseButton(me.button, me.time);
158                         if (bi==null) return false;
159
160                         if (me.holdTime > profile.clickHoldTimeTolerance) return false;
161                         if (bi.deltaMotion>profile.movementTolerance) return false;
162                         // This is a click
163
164                         long timeSinceLastClick = me.time - bi.lastClickEventTime;
165                         bi.lastClickEventTime = me.time;
166
167                         // reset click counter
168                         if (timeSinceLastClick>profile.consecutiveToleranceTime) 
169                             bi.clickCount = 1;
170                         else
171                             bi.clickCount++;
172
173                         MouseClickEvent ce = 
174                                 new MouseClickEvent(getContext(), e.time, e.mouseId, e.buttons, e.stateMask, me.button, bi.clickCount, me.controlPosition, me.screenPosition);                          
175                         getContext().getEventQueue().queueFirst(ce);
176                     }
177         return false;
178     }
179
180     /**
181      * Get last known position of a mouse
182      * 
183      * @param mouseId mouse id
184      * @return mouse position in canvas coordinates or null if mouse exited
185      *         canvas
186      */
187     public MouseInfo getMouseInfo(int mouseId) {
188         return miceInfo.get(mouseId);
189     }
190
191     public MouseInfo getMousePressedInfo(int mouseId) {
192         return micePressedInfo.get(mouseId);
193     }
194
195     /**
196      * Get button info
197      * @param mouseId mouse id
198      * @param buttonId button id
199      * @return null if button has never been pressed, or button info
200      */
201     public ButtonInfo getButtonInfo(int mouseId, int buttonId)
202     {
203         MouseInfo mi = miceInfo.get(mouseId);
204         if (mi==null) return null;
205         return mi.getButtonPressInfo(buttonId);
206     }
207
208     /**
209      * Get a snapshot of statuses of all mice
210      * @return a snapshot
211      */
212     public Map<Integer, MouseInfo> getMiceInfo()
213     {
214         return new HashMap<Integer, MouseInfo>(miceInfo);
215     }
216
217     public int getMouseCount()
218     {
219         return miceInfo.size();
220     }
221
222     public MouseClickProfile getProfile() {
223         return profile;
224     }
225
226     public void setProfile(MouseClickProfile profile) {
227         assert(profile!=null);
228         this.profile = profile;
229     }
230
231     public static final class MouseInfo {
232         public int mouseId;
233         public Point2D controlPosition;
234         public Point2D canvasPosition;
235         public int buttons;
236         public MouseInfo(int mouseId, Point2D initialControlPos, Point2D initialCanvasPosition, int initialButtonMask) {
237             this.mouseId = mouseId;
238             this.controlPosition = initialControlPos;
239             this.canvasPosition = initialCanvasPosition;
240             int buttonId = 0;
241             while (initialButtonMask!=0) {
242                 if ((initialButtonMask & 1)==1)
243                     setButtonPressed(buttonId, 0, initialControlPos, initialCanvasPosition, Long.MIN_VALUE);
244                 initialButtonMask >>>= 1;
245                 buttonId++;
246             }
247         }
248
249         public boolean isMouseButtonPressed(int buttonId)
250         {
251             ButtonInfo bi = buttonPressInfo.get(buttonId);
252             return bi!=null && bi.down;
253         }
254         private void _countButtonMask() {
255             int result = 0;
256             for (ButtonInfo pi : buttonPressInfo.values())
257             {
258                 if (!pi.down) continue;
259                 result |= 1 << (pi.button-1);
260             }
261             this.buttons = result;
262         }
263         public  Map<Integer, ButtonInfo> buttonPressInfo = new HashMap<Integer, ButtonInfo>();
264         public void setButtonPressed(int buttonId, int stateMask, Point2D controlPos, Point2D canvasPos, long eventTime) {
265             ButtonInfo bi = getOrCreateButtonInfo(buttonId); 
266             bi.canvasPosition = canvasPos;
267             bi.controlPosition = controlPos;
268             bi.systemTime = System.currentTimeMillis();
269             bi.eventTime = eventTime;
270             bi.down = true;
271             bi.deltaMotion = 0;
272             bi.drag = false;
273             bi.stateMask = stateMask;
274             _countButtonMask();
275         }
276         public ButtonInfo releaseButton(int buttonId, long eventTime) {
277             ButtonInfo bi = getButtonPressInfo(buttonId);
278             if (bi==null) return null;
279             bi.down = false;
280             bi.holdTime = eventTime - bi.eventTime;
281             _countButtonMask();
282             return bi;
283         }
284         ButtonInfo getOrCreateButtonInfo(int buttonId)
285         {
286             ButtonInfo bi = buttonPressInfo.get(buttonId);
287             if (bi==null) bi = new ButtonInfo(buttonId);
288             buttonPressInfo.put(buttonId, bi);
289             return bi;
290         }
291         public ButtonInfo getButtonPressInfo(int buttonId) {
292             return buttonPressInfo.get(buttonId);
293         }
294         public void addDistanceForButtons(double distance) {
295             for (ButtonInfo bi : buttonPressInfo.values())
296             {
297                 if (!bi.down) continue;
298                 bi.deltaMotion += distance;
299             }
300         }
301         public Collection<ButtonInfo> getButtonInfos() {
302             return buttonPressInfo.values();
303         }
304     }
305
306     /** Status of mouse's button press */
307     public static final class ButtonInfo {
308         /** Position on press */
309         public Point2D controlPosition;
310         /** Position on press */
311         public Point2D canvasPosition;
312         public final int button;
313         public int stateMask;
314         /** System time when pressed */
315         public long systemTime;
316         /** Event time when pressed */
317         public long eventTime;
318         /** Hold time when released */
319         public long holdTime;
320         /** Current up / down status */
321         public boolean down = false;
322         /** Total movement in pixels since press */ 
323         public double deltaMotion = 0.0;
324         /** Click Count */
325         public int clickCount = 0;
326         /** Time of last click */
327         public long lastClickEventTime = Long.MIN_VALUE;
328         /** Dragged (set true when possibility for click is excluded) */
329         public boolean drag = false;
330         public ButtonInfo(int button) {
331             this.button = button;
332         }
333     }
334
335     /**
336      * Sends mice positions as an event
337      */
338     private void sendMicePos()
339     {
340         long time = System.currentTimeMillis();
341         for (Entry<Integer, MouseInfo> e : miceInfo.entrySet()) {
342             MouseInfo mi = e.getValue();
343             MouseMovedEvent mme;
344             // FIXME: screenPosition as null (requires adding screenPos into MouseInfo)
345             mme = new MouseMovedEvent(getContext(), time, e.getKey(), mi.buttons, 0 /* FIXME ??? */, mi.controlPosition, null);
346             getContext().getEventQueue().queueEvent(mme);
347         }
348     }
349
350 }