/*******************************************************************************
* 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);
// Send mouse drag events.
for (ButtonInfo bi : mi.buttonPressInfo.values())
{
if (!bi.down) continue;
if (bi.deltaMotion<=profile.movementTolerance) continue;
if (bi.drag) continue;
bi.drag = true;
MouseDragBegin db = new MouseDragBegin(
this, e.time, e.mouseId, e.buttons, e.stateMask, bi.button,
bi.canvasPosition, bi.controlPosition,
e.controlPosition, e.screenPosition
);
getContext().getEventQueue().queueFirst(db);
}
} 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);
}
}
}