]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - 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
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/MouseUtil.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/MouseUtil.java
new file mode 100644 (file)
index 0000000..8ebfb56
--- /dev/null
@@ -0,0 +1,350 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+/*\r
+ *\r
+ * @author Toni Kalajainen\r
+ */\r
+package org.simantics.g2d.participant;\r
+\r
+import java.awt.geom.Point2D;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+\r
+import org.simantics.g2d.canvas.Hints;\r
+import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
+import org.simantics.g2d.canvas.impl.HintReflection.HintListener;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
+import org.simantics.utils.datastructures.hints.IHintObservable;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+\r
+/**\r
+ * MouseUtil tracks position and button status of mice.\r
+ * It remembers where mouse was on control and on diagram when each of its button\r
+ * was pressed. \r
+ * \r
+ * MouseUtil generates synthetic mouse events {@link MouseClickEvent} and\r
+ * {@link MouseDragBegin} for convenience. \r
+ * \r
+ * It also tracks distance how far mouse has moved since each of its \r
+ * button was pressed (used for click).\r
+ * <p>\r
+ * There is also debug cursor painter which can be enabled/disabled with \r
+ * hint KEY_CURSOR_STATUS.\r
+ * \r
+ * @TODO From to mouse monitor and mouse painter classes\r
+ */\r
+public class MouseUtil extends AbstractCanvasParticipant {     \r
+\r
+    protected @Dependency TransformUtil util;\r
+\r
+    /** Mice info */\r
+    protected  Map<Integer, MouseInfo> miceInfo = new HashMap<Integer, MouseInfo>();\r
+    protected  Map<Integer, MouseInfo> micePressedInfo = new HashMap<Integer, MouseInfo>();\r
+\r
+    public static class MouseClickProfile {\r
+        public static final MouseClickProfile DEFAULT = new MouseClickProfile(); \r
+        /** Maximum time of holding a button down that counts as a click */ \r
+        public long clickHoldTimeTolerance = 300L;\r
+        /** Maximum number of pixels mouse may move while pressed to count a click */ \r
+        public double movementTolerance = 3.5;\r
+        /** Maximum time to wait for clicks to count as consecutive */\r
+        public long consecutiveToleranceTime = 380L;\r
+    }\r
+\r
+    protected MouseClickProfile profile = MouseClickProfile.DEFAULT;\r
+\r
+    @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")\r
+    public void transformChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
+        if (oldValue != null && oldValue.equals(newValue)) return;\r
+        sendMicePos();\r
+    }\r
+\r
+    @EventHandler(priority = Integer.MAX_VALUE)\r
+    public boolean handleMouseEvent(MouseEvent e) \r
+    {\r
+        assertDependencies();\r
+        if (e instanceof MouseEnterEvent)\r
+        {\r
+            Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);\r
+            // Reset mouse\r
+            MouseInfo mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);\r
+            miceInfo.put(e.mouseId, mi);\r
+        } else \r
+            if (e instanceof MouseExitEvent)\r
+            {\r
+                miceInfo.remove(e.mouseId);\r
+            } else\r
+                if (e instanceof MouseMovedEvent)\r
+                {\r
+                    Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);\r
+                    double deltaDistance = 0;\r
+                    MouseInfo mi = miceInfo.get(e.mouseId);\r
+                    if (mi==null) {\r
+                        mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, 0/*e.buttons*/);\r
+                        miceInfo.put(e.mouseId, mi);\r
+                    } else {\r
+                        deltaDistance = e.controlPosition.distance(mi.controlPosition);\r
+                        mi.controlPosition = e.controlPosition;\r
+                        mi.canvasPosition = canvasPosition;\r
+                    }\r
+\r
+                    if (deltaDistance>0)\r
+                        mi.addDistanceForButtons(deltaDistance);\r
+\r
+                    // Send mouse drag events.\r
+                    for (ButtonInfo bi : mi.buttonPressInfo.values())\r
+                    {\r
+                        if (!bi.down) continue;\r
+                        if (bi.deltaMotion<=profile.movementTolerance) continue;\r
+                        if (bi.drag) continue;\r
+                        bi.drag = true;\r
+                        MouseDragBegin db = new MouseDragBegin(\r
+                                this, e.time, e.mouseId, e.buttons, e.stateMask, bi.button,\r
+                                bi.canvasPosition, bi.controlPosition,\r
+                                e.controlPosition, e.screenPosition\r
+                                );\r
+                        getContext().getEventQueue().queueFirst(db);\r
+                    }\r
+\r
+                } else\r
+                    if (e instanceof MouseButtonPressedEvent)\r
+                    {\r
+                        Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null);\r
+                        MouseButtonPressedEvent me = (MouseButtonPressedEvent) e;\r
+                        MouseInfo mi = miceInfo.get(e.mouseId);\r
+                        if (mi==null) {\r
+                            mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons);\r
+                            miceInfo.put(e.mouseId, mi);\r
+                            micePressedInfo.put(e.mouseId, mi);\r
+                        } else {\r
+                            mi.controlPosition = e.controlPosition;\r
+                            mi.canvasPosition = canvasPosition;\r
+                            micePressedInfo.put(e.mouseId, \r
+                                    new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons));\r
+                        }                      \r
+                        mi.setButtonPressed(me.button, e.stateMask, e.controlPosition, canvasPosition, e.time);\r
+                    } else if (e instanceof MouseButtonReleasedEvent) {\r
+                        MouseButtonReleasedEvent me = (MouseButtonReleasedEvent) e;\r
+                        Point2D canvasPosition = util.controlToCanvas(me.controlPosition, null);\r
+                        MouseInfo mi = miceInfo.get(me.mouseId);\r
+                        if (mi==null) {\r
+                            mi = new MouseInfo(e.mouseId, me.controlPosition, canvasPosition, 0/*me.buttons*/);\r
+                            miceInfo.put(me.mouseId, mi);\r
+                        } else {\r
+                            mi.controlPosition = me.controlPosition;\r
+                            mi.canvasPosition = canvasPosition;\r
+                        }\r
+                        ButtonInfo bi = mi.releaseButton(me.button, me.time);\r
+                        if (bi==null) return false;\r
+\r
+                        if (me.holdTime > profile.clickHoldTimeTolerance) return false;\r
+                        if (bi.deltaMotion>profile.movementTolerance) return false;\r
+                        // This is a click\r
+\r
+                        long timeSinceLastClick = me.time - bi.lastClickEventTime;\r
+                        bi.lastClickEventTime = me.time;\r
+\r
+                        // reset click counter\r
+                        if (timeSinceLastClick>profile.consecutiveToleranceTime) \r
+                            bi.clickCount = 1;\r
+                        else\r
+                            bi.clickCount++;\r
+\r
+                        MouseClickEvent ce = \r
+                                new MouseClickEvent(getContext(), e.time, e.mouseId, e.buttons, e.stateMask, me.button, bi.clickCount, me.controlPosition, me.screenPosition);                         \r
+                        getContext().getEventQueue().queueFirst(ce);\r
+                    }\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Get last known position of a mouse\r
+     * \r
+     * @param mouseId mouse id\r
+     * @return mouse position in canvas coordinates or null if mouse exited\r
+     *         canvas\r
+     */\r
+    public MouseInfo getMouseInfo(int mouseId) {\r
+        return miceInfo.get(mouseId);\r
+    }\r
+\r
+    public MouseInfo getMousePressedInfo(int mouseId) {\r
+        return micePressedInfo.get(mouseId);\r
+    }\r
+\r
+    /**\r
+     * Get button info\r
+     * @param mouseId mouse id\r
+     * @param buttonId button id\r
+     * @return null if button has never been pressed, or button info\r
+     */\r
+    public ButtonInfo getButtonInfo(int mouseId, int buttonId)\r
+    {\r
+        MouseInfo mi = miceInfo.get(mouseId);\r
+        if (mi==null) return null;\r
+        return mi.getButtonPressInfo(buttonId);\r
+    }\r
+\r
+    /**\r
+     * Get a snapshot of statuses of all mice\r
+     * @return a snapshot\r
+     */\r
+    public Map<Integer, MouseInfo> getMiceInfo()\r
+    {\r
+        return new HashMap<Integer, MouseInfo>(miceInfo);\r
+    }\r
+\r
+    public int getMouseCount()\r
+    {\r
+        return miceInfo.size();\r
+    }\r
+\r
+    public MouseClickProfile getProfile() {\r
+        return profile;\r
+    }\r
+\r
+    public void setProfile(MouseClickProfile profile) {\r
+        assert(profile!=null);\r
+        this.profile = profile;\r
+    }\r
+\r
+    public static final class MouseInfo {\r
+        public int mouseId;\r
+        public Point2D controlPosition;\r
+        public Point2D canvasPosition;\r
+        public int buttons;\r
+        public MouseInfo(int mouseId, Point2D initialControlPos, Point2D initialCanvasPosition, int initialButtonMask) {\r
+            this.mouseId = mouseId;\r
+            this.controlPosition = initialControlPos;\r
+            this.canvasPosition = initialCanvasPosition;\r
+            int buttonId = 0;\r
+            while (initialButtonMask!=0) {\r
+                if ((initialButtonMask & 1)==1)\r
+                    setButtonPressed(buttonId, 0, initialControlPos, initialCanvasPosition, Long.MIN_VALUE);\r
+                initialButtonMask >>>= 1;\r
+                buttonId++;\r
+            }\r
+        }\r
+\r
+        public boolean isMouseButtonPressed(int buttonId)\r
+        {\r
+            ButtonInfo bi = buttonPressInfo.get(buttonId);\r
+            return bi!=null && bi.down;\r
+        }\r
+        private void _countButtonMask() {\r
+            int result = 0;\r
+            for (ButtonInfo pi : buttonPressInfo.values())\r
+            {\r
+                if (!pi.down) continue;\r
+                result |= 1 << (pi.button-1);\r
+            }\r
+            this.buttons = result;\r
+        }\r
+        public  Map<Integer, ButtonInfo> buttonPressInfo = new HashMap<Integer, ButtonInfo>();\r
+        public void setButtonPressed(int buttonId, int stateMask, Point2D controlPos, Point2D canvasPos, long eventTime) {\r
+            ButtonInfo bi = getOrCreateButtonInfo(buttonId); \r
+            bi.canvasPosition = canvasPos;\r
+            bi.controlPosition = controlPos;\r
+            bi.systemTime = System.currentTimeMillis();\r
+            bi.eventTime = eventTime;\r
+            bi.down = true;\r
+            bi.deltaMotion = 0;\r
+            bi.drag = false;\r
+            bi.stateMask = stateMask;\r
+            _countButtonMask();\r
+        }\r
+        public ButtonInfo releaseButton(int buttonId, long eventTime) {\r
+            ButtonInfo bi = getButtonPressInfo(buttonId);\r
+            if (bi==null) return null;\r
+            bi.down = false;\r
+            bi.holdTime = eventTime - bi.eventTime;\r
+            _countButtonMask();\r
+            return bi;\r
+        }\r
+        ButtonInfo getOrCreateButtonInfo(int buttonId)\r
+        {\r
+            ButtonInfo bi = buttonPressInfo.get(buttonId);\r
+            if (bi==null) bi = new ButtonInfo(buttonId);\r
+            buttonPressInfo.put(buttonId, bi);\r
+            return bi;\r
+        }\r
+        public ButtonInfo getButtonPressInfo(int buttonId) {\r
+            return buttonPressInfo.get(buttonId);\r
+        }\r
+        public void addDistanceForButtons(double distance) {\r
+            for (ButtonInfo bi : buttonPressInfo.values())\r
+            {\r
+                if (!bi.down) continue;\r
+                bi.deltaMotion += distance;\r
+            }\r
+        }\r
+        public Collection<ButtonInfo> getButtonInfos() {\r
+            return buttonPressInfo.values();\r
+        }\r
+    }\r
+\r
+    /** Status of mouse's button press */\r
+    public static final class ButtonInfo {\r
+        /** Position on press */\r
+        public Point2D controlPosition;\r
+        /** Position on press */\r
+        public Point2D canvasPosition;\r
+        public final int button;\r
+        public int stateMask;\r
+        /** System time when pressed */\r
+        public long systemTime;\r
+        /** Event time when pressed */\r
+        public long eventTime;\r
+        /** Hold time when released */\r
+        public long holdTime;\r
+        /** Current up / down status */\r
+        public boolean down = false;\r
+        /** Total movement in pixels since press */ \r
+        public double deltaMotion = 0.0;\r
+        /** Click Count */\r
+        public int clickCount = 0;\r
+        /** Time of last click */\r
+        public long lastClickEventTime = Long.MIN_VALUE;\r
+        /** Dragged (set true when possibility for click is excluded) */\r
+        public boolean drag = false;\r
+        public ButtonInfo(int button) {\r
+            this.button = button;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Sends mice positions as an event\r
+     */\r
+    private void sendMicePos()\r
+    {\r
+        long time = System.currentTimeMillis();\r
+        for (Entry<Integer, MouseInfo> e : miceInfo.entrySet()) {\r
+            MouseInfo mi = e.getValue();\r
+            MouseMovedEvent mme;\r
+            // FIXME: screenPosition as null (requires adding screenPos into MouseInfo)\r
+            mme = new MouseMovedEvent(getContext(), time, e.getKey(), mi.buttons, 0 /* FIXME ??? */, mi.controlPosition, null);\r
+            getContext().getEventQueue().queueEvent(mme);\r
+        }\r
+    }\r
+\r
+}\r