/***********************************************************************
* mt4j Copyright (c) 2008 - 2009 Christopher Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package org.mt4j.input.inputProcessors.componentProcessors.tapAndHoldProcessor;
import java.util.ArrayList;
import org.mt4j.components.MTCanvas;
import org.mt4j.components.interfaces.IMTComponent3D;
import org.mt4j.input.inputData.InputCursor;
import org.mt4j.input.inputData.MTFingerInputEvt;
import org.mt4j.input.inputProcessors.IInputProcessor;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor;
import org.mt4j.util.math.Vector3D;
import processing.core.PApplet;
/**
* The Class TapAndHoldProcessor. Multi-Touch gesture which is triggered
* after touching and resting the finger on the same spot for some time.
* Fires TapAndHoldEvent gesture events.
*
* @author Christopher Ruff
*/
public class TapAndHoldProcessor extends AbstractCursorProcessor {
/** The applet. */
private PApplet applet;
/** The max finger up dist. */
private float maxFingerUpDist;
/** The un used cursors. */
private ArrayList<InputCursor> unUsedCursors;
/** The locked cursors. */
private ArrayList<InputCursor> lockedCursors;
/** The button down screen pos. */
private Vector3D buttonDownScreenPos;
/** The tap start time. */
private long tapStartTime;
/** The tap time. */
private int holdTime;
//TODO atm this only allows 1 tap on 1 component
//if we want more we have to do different (save each cursor to each start time etc, dont relock other cursors)
/**
* Instantiates a new tap processor.
* @param pa the pa
*/
public TapAndHoldProcessor(PApplet pa) {
this(pa, 1800);
}
/**
* Instantiates a new tap and hold processor.
* @param pa the pa
* @param duration the duration
*/
public TapAndHoldProcessor(PApplet pa, int duration) {
super();
this.applet = pa;
this.maxFingerUpDist = 17.0f;
this.holdTime = duration;
this.unUsedCursors = new ArrayList<InputCursor>();
this.lockedCursors = new ArrayList<InputCursor>();
this.setLockPriority(1);
this.setDebug(false);
// logger.setLevel(Level.DEBUG);
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorStarted(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.AbstractCursorInputEvt)
*/
@Override
public void cursorStarted(InputCursor c, MTFingerInputEvt positionEvent) {
if (lockedCursors.size() >= 1){ //We assume that gesture is already in progress and add this new cursor to the unUsedList
unUsedCursors.add(c);
}else{
if (unUsedCursors.size() == 0){ //Only start drag if no other finger on the component yet
if (this.canLock(c)){//See if we can obtain a lock on this cursor (depends on the priority)
this.getLock(c);
logger.debug(this.getName() + " successfully locked cursor (id:" + c.getId() + ")");
lockedCursors.add(c);
buttonDownScreenPos = c.getPosition();
tapStartTime = System.currentTimeMillis();
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_DETECTED, positionEvent.getTargetComponent(), c, false, c.getPosition(), this.holdTime, 0, 0));
try {
applet.registerPre(this);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}else{
unUsedCursors.add(c);
}
}else{
unUsedCursors.add(c);
}
}
}
//problem: mouse does only send update evt if mouse is actually dragged
//idea: registerPre and check if we have alocked cursor and the times is up
//then do the checking
/**
* Pre.
*/
public void pre(){
if (lockedCursors.size() == 1){
IMTComponent3D comp = lockedCursors.get(0).getTarget();
InputCursor c = lockedCursors.get(0);
long nowTime = System.currentTimeMillis();
long elapsedTime = nowTime - this.tapStartTime;
Vector3D screenPos = c.getPosition();
float normalized = (float)elapsedTime / (float)this.holdTime;
if (elapsedTime >= holdTime){
normalized = 1;
logger.debug("TIME PASSED!");
Vector3D intersection = getIntersection(applet, comp, c);
//logger.debug("Distance between buttondownScreenPos: " + buttonDownScreenPos + " and upScrPos: " + buttonUpScreenPos + " is: " + Vector3D.distance(buttonDownScreenPos, buttonUpScreenPos));
if ( (intersection != null || comp instanceof MTCanvas) //FIXME hack - at canvas no intersection..
&&
Vector3D.distance2D(buttonDownScreenPos, screenPos) <= this.maxFingerUpDist
){
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_ENDED, comp, c, true, screenPos, this.holdTime, elapsedTime, normalized));
}else{
logger.debug("DISTANCE TOO FAR OR NO INTERSECTION");
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_ENDED, comp, c, false, screenPos, this.holdTime, elapsedTime, normalized));
}
lockedCursors.remove(c);
this.unLock(c);
try {
applet.unregisterPre(this);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}else{
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_UPDATED, comp, c, false, screenPos, this.holdTime, elapsedTime, normalized));
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorUpdated(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.AbstractCursorInputEvt)
*/
@Override
public void cursorUpdated(InputCursor c, MTFingerInputEvt positionEvent) {
if (lockedCursors.contains(c)){ //cursor is a actual used cursor
IMTComponent3D comp = lockedCursors.get(0).getTarget();
long nowTime = System.currentTimeMillis();
long elapsedTime = nowTime - this.tapStartTime;
Vector3D screenPos = c.getPosition();
float normalized = (float)elapsedTime / (float)this.holdTime;
//logger.debug("Distance between buttondownScreenPos: " + buttonDownScreenPos + " and upScrPos: " + buttonUpScreenPos + " is: " + Vector3D.distance(buttonDownScreenPos, buttonUpScreenPos));
if (Vector3D.distance2D(buttonDownScreenPos, screenPos) > this.maxFingerUpDist){
logger.debug("DISTANCE TOO FAR OR NO INTERSECTION");
lockedCursors.remove(c);
this.unLock(c);
try {
applet.unregisterPre(this);
} catch (Exception e) {
System.err.println(e.getMessage());
}
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_ENDED, comp, c, false, screenPos, this.holdTime, elapsedTime, normalized));
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractCursorProcessor#cursorEnded(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputData.AbstractCursorInputEvt)
*/
@Override
public void cursorEnded(InputCursor c, MTFingerInputEvt positionEvent) {
logger.debug(this.getName() + " INPUT_ENDED RECIEVED - MOTION: " + c.getId());
if (lockedCursors.contains(c)){ //cursor was a actual used cursor
lockedCursors.remove(c);
long nowTime = System.currentTimeMillis();
long elapsedTime = nowTime - this.tapStartTime;
float normalized = (float)elapsedTime / (float)this.holdTime;
if (unUsedCursors.size() > 0){ //check if there are other cursors on the component, we could use
InputCursor otherCursor = unUsedCursors.get(0);
if (this.canLock(otherCursor)
&&
Vector3D.distance2D(buttonDownScreenPos, otherCursor.getPosition()) <= this.maxFingerUpDist)
{ //Check if we have the priority to use this other cursor and if cursor is in range
this.getLock(otherCursor);
unUsedCursors.remove(otherCursor);
lockedCursors.add(otherCursor);
buttonDownScreenPos = otherCursor.getPosition();
}else{
//Other cursor has higher prior -> end this gesture
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_ENDED, c.getTarget(), c, false, c.getPosition(), this.holdTime, elapsedTime, normalized));
try {
applet.unregisterPre(this);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}else{
//We have no other cursor to continure gesture -> end
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_ENDED, c.getTarget(), c, false, c.getPosition(), this.holdTime, elapsedTime, normalized));
try {
applet.unregisterPre(this);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
this.unLock(c);
}else{ //cursor was not used here
if (unUsedCursors.contains(c)){
unUsedCursors.remove(c);
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#cursorLocked(org.mt4j.input.inputData.InputCursor, org.mt4j.input.inputAnalyzers.IInputAnalyzer)
*/
@Override
public void cursorLocked(InputCursor c, IInputProcessor lockingAnalyzer) {
if (lockingAnalyzer instanceof AbstractComponentProcessor){
logger.debug(this.getName() + " Recieved MOTION LOCKED by (" + ((AbstractComponentProcessor)lockingAnalyzer).getName() + ") - cursor ID: " + c.getId());
}else{
logger.debug(this.getName() + " Recieved MOTION LOCKED by higher priority signal - cursor ID: " + c.getId());
}
if (lockedCursors.contains(c)){ //cursor was in use here
lockedCursors.remove(c);
long nowTime = System.currentTimeMillis();
long elapsedTime = nowTime - this.tapStartTime;
float normalized = (float)elapsedTime / (float)this.holdTime;
this.fireGestureEvent(new TapAndHoldEvent(this, MTGestureEvent.GESTURE_ENDED, c.getTarget(), c, false, c.getPosition(), this.holdTime, elapsedTime, normalized));
try {
applet.unregisterPre(this);
} catch (Exception e) {
System.err.println(e.getMessage());
}
unUsedCursors.add(c);
logger.debug(this.getName() + " cursor:" + c.getId() + " MOTION LOCKED. Was an active cursor in this gesture!");
}else{ //TODO remove else, it is pretty useless
if (unUsedCursors.contains(c)){
logger.debug(this.getName() + " MOTION LOCKED. But it was NOT an active cursor in this gesture!");
}
}
}
/* (non-Javadoc)
* @see org.mt4j.input.inputAnalyzers.IInputAnalyzer#cursorUnlocked(org.mt4j.input.inputData.InputCursor)
*/
@Override
public void cursorUnlocked(InputCursor c) {
//TAP AND HOLD IS NOT RESUMABLE
/*
logger.debug(this.getName() + " Recieved UNLOCKED signal for cursor ID: " + m.getId());
if (lockedCursors.size() >= 1){ //we dont need the unlocked cursor, gesture still in progress
logger.debug(this.getName() + " still in progress - we dont need the unlocked cursor" );
return;
}
if (unUsedCursors.contains(m)){
if (this.canLock(m)){
this.getLock(m);
unUsedCursors.remove(m);
lockedCursors.add(m);
//TODO fire started? maybe not.. do we have to?
logger.debug(this.getName() + " can resume its gesture with cursor: " + m.getId());
}
}
*/
}
/**
* Gets the max finger up dist.
*
* @return the max finger up dist
*/
public float getMaxFingerUpDist() {
return maxFingerUpDist;
}
/**
* Sets the maximum allowed distance of the position
* of the finger_down event and the finger_up event
* that fires a click event
* <br>This ensures that a click event is only raised
* if the finger didnt move that far during the click.
*
* @param maxFingerUpDist the max finger up dist
*/
public void setMaxFingerUpDist(float maxFingerUpDist) {
this.maxFingerUpDist = maxFingerUpDist;
}
/**
* Gets the time (in ms.) needed to hold to successfully tap&hold.
*
* @return the Hold time
*/
public long getHoldTime() {
return this.holdTime;
}
/**
* Sets the holding time for the gesture.
*
* @param tapTime the new hold time
*/
public void setHoldTime(int tapTime) {
this.holdTime = tapTime;
}
/* (non-Javadoc)
* @see org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor#getName()
*/
@Override
public String getName() {
return "tap and hold processor";
}
}