--- /dev/null
+/*******************************************************************************\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