/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ /* * * @author Toni Kalajainen */ package org.simantics.g2d.participant; import java.awt.geom.Point2D; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.simantics.g2d.canvas.Hints; import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.canvas.impl.HintReflection.HintListener; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseEnterEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.datastructures.hints.IHintContext.Key; /** * MouseUtil tracks position and button status of mice. * It remembers where mouse was on control and on diagram when each of its button * was pressed. * * MouseUtil generates synthetic mouse events {@link MouseClickEvent} and * {@link MouseDragBegin} for convenience. * * It also tracks distance how far mouse has moved since each of its * button was pressed (used for click). *

* There is also debug cursor painter which can be enabled/disabled with * hint KEY_CURSOR_STATUS. * * @TODO From to mouse monitor and mouse painter classes */ public class MouseUtil extends AbstractCanvasParticipant { protected @Dependency TransformUtil util; /** Mice info */ protected Map miceInfo = new HashMap(); protected Map micePressedInfo = new HashMap(); public static class MouseClickProfile { public static final MouseClickProfile DEFAULT = new MouseClickProfile(); /** Maximum time of holding a button down that counts as a click */ public long clickHoldTimeTolerance = 300L; /** Maximum number of pixels mouse may move while pressed to count a click */ public double movementTolerance = 3.5; /** Maximum time to wait for clicks to count as consecutive */ public long consecutiveToleranceTime = 380L; } protected MouseClickProfile profile = MouseClickProfile.DEFAULT; @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM") public void transformChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { if (oldValue != null && oldValue.equals(newValue)) return; sendMicePos(); } @EventHandler(priority = Integer.MAX_VALUE) public boolean handleMouseEvent(MouseEvent e) { assertDependencies(); if (e instanceof MouseEnterEvent) { Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null); // Reset mouse MouseInfo mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons); miceInfo.put(e.mouseId, mi); } else if (e instanceof MouseExitEvent) { miceInfo.remove(e.mouseId); } else if (e instanceof MouseMovedEvent) { Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null); double deltaDistance = 0; MouseInfo mi = miceInfo.get(e.mouseId); if (mi==null) { mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, 0/*e.buttons*/); miceInfo.put(e.mouseId, mi); } else { deltaDistance = e.controlPosition.distance(mi.controlPosition); mi.controlPosition = e.controlPosition; mi.canvasPosition = canvasPosition; } if (deltaDistance>0) mi.addDistanceForButtons(deltaDistance); } else if (e instanceof MouseButtonPressedEvent) { Point2D canvasPosition = util.controlToCanvas(e.controlPosition, null); MouseButtonPressedEvent me = (MouseButtonPressedEvent) e; MouseInfo mi = miceInfo.get(e.mouseId); if (mi==null) { mi = new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons); miceInfo.put(e.mouseId, mi); micePressedInfo.put(e.mouseId, mi); } else { mi.controlPosition = e.controlPosition; mi.canvasPosition = canvasPosition; micePressedInfo.put(e.mouseId, new MouseInfo(e.mouseId, e.controlPosition, canvasPosition, e.buttons)); } mi.setButtonPressed(me.button, e.stateMask, e.controlPosition, canvasPosition, e.time); } else if (e instanceof MouseButtonReleasedEvent) { MouseButtonReleasedEvent me = (MouseButtonReleasedEvent) e; Point2D canvasPosition = util.controlToCanvas(me.controlPosition, null); MouseInfo mi = miceInfo.get(me.mouseId); if (mi==null) { mi = new MouseInfo(e.mouseId, me.controlPosition, canvasPosition, 0/*me.buttons*/); miceInfo.put(me.mouseId, mi); } else { mi.controlPosition = me.controlPosition; mi.canvasPosition = canvasPosition; } ButtonInfo bi = mi.releaseButton(me.button, me.time); if (bi==null) return false; if (me.holdTime > profile.clickHoldTimeTolerance) return false; if (bi.deltaMotion>profile.movementTolerance) return false; // This is a click long timeSinceLastClick = me.time - bi.lastClickEventTime; bi.lastClickEventTime = me.time; // reset click counter if (timeSinceLastClick>profile.consecutiveToleranceTime) bi.clickCount = 1; else bi.clickCount++; MouseClickEvent ce = new MouseClickEvent(getContext(), e.time, e.mouseId, e.buttons, e.stateMask, me.button, bi.clickCount, me.controlPosition, me.screenPosition); getContext().getEventQueue().queueFirst(ce); } return false; } /** * Get last known position of a mouse * * @param mouseId mouse id * @return mouse position in canvas coordinates or null if mouse exited * canvas */ public MouseInfo getMouseInfo(int mouseId) { return miceInfo.get(mouseId); } public MouseInfo getMousePressedInfo(int mouseId) { return micePressedInfo.get(mouseId); } /** * Get button info * @param mouseId mouse id * @param buttonId button id * @return null if button has never been pressed, or button info */ public ButtonInfo getButtonInfo(int mouseId, int buttonId) { MouseInfo mi = miceInfo.get(mouseId); if (mi==null) return null; return mi.getButtonPressInfo(buttonId); } /** * Get a snapshot of statuses of all mice * @return a snapshot */ public Map getMiceInfo() { return new HashMap(miceInfo); } public int getMouseCount() { return miceInfo.size(); } public MouseClickProfile getProfile() { return profile; } public void setProfile(MouseClickProfile profile) { assert(profile!=null); this.profile = profile; } public static final class MouseInfo { public int mouseId; public Point2D controlPosition; public Point2D canvasPosition; public int buttons; public MouseInfo(int mouseId, Point2D initialControlPos, Point2D initialCanvasPosition, int initialButtonMask) { this.mouseId = mouseId; this.controlPosition = initialControlPos; this.canvasPosition = initialCanvasPosition; int buttonId = 0; while (initialButtonMask!=0) { if ((initialButtonMask & 1)==1) setButtonPressed(buttonId, 0, initialControlPos, initialCanvasPosition, Long.MIN_VALUE); initialButtonMask >>>= 1; buttonId++; } } public boolean isMouseButtonPressed(int buttonId) { ButtonInfo bi = buttonPressInfo.get(buttonId); return bi!=null && bi.down; } private void _countButtonMask() { int result = 0; for (ButtonInfo pi : buttonPressInfo.values()) { if (!pi.down) continue; result |= 1 << (pi.button-1); } this.buttons = result; } public Map buttonPressInfo = new HashMap(); public void setButtonPressed(int buttonId, int stateMask, Point2D controlPos, Point2D canvasPos, long eventTime) { ButtonInfo bi = getOrCreateButtonInfo(buttonId); bi.canvasPosition = canvasPos; bi.controlPosition = controlPos; bi.systemTime = System.currentTimeMillis(); bi.eventTime = eventTime; bi.down = true; bi.deltaMotion = 0; bi.drag = false; bi.stateMask = stateMask; _countButtonMask(); } public ButtonInfo releaseButton(int buttonId, long eventTime) { ButtonInfo bi = getButtonPressInfo(buttonId); if (bi==null) return null; bi.down = false; bi.holdTime = eventTime - bi.eventTime; _countButtonMask(); return bi; } ButtonInfo getOrCreateButtonInfo(int buttonId) { ButtonInfo bi = buttonPressInfo.get(buttonId); if (bi==null) bi = new ButtonInfo(buttonId); buttonPressInfo.put(buttonId, bi); return bi; } public ButtonInfo getButtonPressInfo(int buttonId) { return buttonPressInfo.get(buttonId); } public void addDistanceForButtons(double distance) { for (ButtonInfo bi : buttonPressInfo.values()) { if (!bi.down) continue; bi.deltaMotion += distance; } } public Collection getButtonInfos() { return buttonPressInfo.values(); } } /** Status of mouse's button press */ public static final class ButtonInfo { /** Position on press */ public Point2D controlPosition; /** Position on press */ public Point2D canvasPosition; public final int button; public int stateMask; /** System time when pressed */ public long systemTime; /** Event time when pressed */ public long eventTime; /** Hold time when released */ public long holdTime; /** Current up / down status */ public boolean down = false; /** Total movement in pixels since press */ public double deltaMotion = 0.0; /** Click Count */ public int clickCount = 0; /** Time of last click */ public long lastClickEventTime = Long.MIN_VALUE; /** Dragged (set true when possibility for click is excluded) */ public boolean drag = false; public ButtonInfo(int button) { this.button = button; } } /** * Sends mice positions as an event */ private void sendMicePos() { long time = System.currentTimeMillis(); for (Entry e : miceInfo.entrySet()) { MouseInfo mi = e.getValue(); MouseMovedEvent mme; // FIXME: screenPosition as null (requires adding screenPos into MouseInfo) mme = new MouseMovedEvent(getContext(), time, e.getKey(), mi.buttons, 0 /* FIXME ??? */, mi.controlPosition, null); getContext().getEventQueue().queueEvent(mme); } } }