package org.bbssh.ui.components;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.TouchGesture;
import net.rim.device.api.ui.UiApplication;
import org.bbssh.keybinding.KeyBindingHelper;
import org.bbssh.session.RemoteSessionInstance;
import org.bbssh.terminal.TerminalStateData;
import org.bbssh.util.Logger;
/**
* This extension of the 4.6 implementation adds touchscreen support for bindable key handling.
*
* @author marc
*
*/
public class TerminalField_47 extends TerminalField {
// @todo make this configurable
private static final int SECOND_TOUCH_ACTION_DELAY = 200;
protected boolean inTouchEvent = false;
protected boolean processTouchEvent = true;
private int eventOneTime;
private int eventTwoTime;
private int event;
private int monitorId;
private int repeatCount = 0;
private int hoverCount = 0;
public TerminalField_47() {
super();
resetSavedEvent();
}
protected final boolean touchEvent(TouchEvent message) {
int eventId = message.getEvent();
if (eventId == TouchEvent.DOWN) {
inTouchEvent = true;
return false;
}
if (eventId == TouchEvent.UP || eventId == TouchEvent.CANCEL) {
inTouchEvent = false;
// All repeating hover events (click_repeat and hover) WILL terminate
// once we receive an UP or CANCEL
hoverCount = 0;
if (eventId == TouchEvent.CANCEL) {
resetSavedEvent();
}
return true;
// return super.touchEvent(message);
}
if (touchEventImpl(message)) {
return true;
}
return true; // super.touchEvent(message);
}
protected void resetSavedEvent() {
eventOneTime = 0;
eventTwoTime = 0;
event = 0;
monitorId = -1;
repeatCount = 0;
}
Runnable secondTouchEventMonitor = new Runnable() {
public void run() {
// If the two times match, then the first event is the only one we received
// that's relevant. (It's possible we received otehrs, but they would
// be unrelated to the first if this condition is true.)
if (eventTwoTime == eventOneTime) {
keyEvent(event, 0, eventTwoTime, false);
resetSavedEvent();
}
}
};
protected boolean handleRepeatingEvent(TouchEvent m, int mappedEvent, int multiplier) {
if (monitorId > -1) {
// Make sure we don't have more than one running at a time -
// timers are a tightly limited resource
try {
//
UiApplication.getUiApplication().cancelInvokeLater(monitorId);
} catch (IllegalArgumentException e) {
// This means that the second instance already ran - invalidating the timer.
// Technically should not be psosible but that's no excuse to crash...
Logger.error("handleRepeatingEvent: unexpected IllegalArgumentException");
resetSavedEvent();
return true;
} finally {
monitorId = -1;
}
}
repeatCount++;
if (repeatCount == 1) {
event = mappedEvent + (KeyBindingHelper.KEY_MODE_ADJUST * multiplier);
eventOneTime = eventTwoTime = m.getTime();
monitorId = UiApplication.getUiApplication().invokeLater(secondTouchEventMonitor,
SECOND_TOUCH_ACTION_DELAY, false);
return true;
} else if (repeatCount == 2) {
boolean result = keyEvent(event + KeyBindingHelper.KEY_MODE_ADJUST, 0, eventTwoTime, false);
resetSavedEvent();
return result;
}
return false;
}
boolean dragging;
int selAnchorX = 0;
int selAnchorY = 0;
int prevX = -1;
int prevY = -1;
/*
*
* (non-Javadoc)
* @see net.rim.device.api.ui.Screen#touchEvent(net.rim.device.api.ui.TouchEvent)
*/
protected boolean touchEventImpl(TouchEvent message) {
int eventId = message.getEvent();
int key = 0;
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null || rsi.state == null)
return true;
TouchGesture g = message.getGesture();
if (rsi.state.typingMode == TerminalStateData.TYPING_MODE_SELECT) {
if (eventId == TouchEvent.DOWN) {
dragging = true;
selAnchorX = message.getX(1);
selAnchorY = message.getY(1);
rsi.state.selectionCursorX = selAnchorX;
rsi.state.selectionCursorY = selAnchorY;
} else if (eventId == TouchEvent.UP) {
dragging = false;
} else if (eventId == TouchEvent.MOVE) {
if (dragging) {
int x = message.getX(1);
// int y = message.getY(1);
if (x == prevX) {
} if (x < prevX) {
} if (x > prevX) {
}
}
}
if (dragging) {
}
redraw(true);
return true;
}
if (eventId == TouchEvent.CLICK) {
return handleRepeatingEvent(message, mapTouchEvent(message.getX(1), message.getY(1)),
KeyBindingHelper.KEY_MODE_MULTIPLIER_CLICK);
} else if (eventId == TouchEvent.UNCLICK) {
} else if (eventId == TouchEvent.GESTURE) {
switch (g.getEvent()) {
case TouchGesture.HOVER:
if (++hoverCount == 1) { // prevent flooding - hover ONCE.
key = mapTouchEvent(message.getX(1), message.getY(1))
+ (KeyBindingHelper.KEY_MODE_ADJUST * KeyBindingHelper.KEY_MODE_MULTIPLIER_HOVER);
}
break;
case TouchGesture.CLICK_REPEAT:
// We can't use CLICK_REPAET because it's always preceded by a CLICK
break;
case TouchGesture.TAP:
return handleRepeatingEvent(message, mapTouchEvent(message.getX(1), message.getY(1)),
KeyBindingHelper.KEY_MODE_MULTIPLIER_TAP);
case TouchGesture.SWIPE:
if (rsi.state.typingMode == TerminalStateData.TYPING_MODE_LOCAL_SCROLL) {
int dir = g.getSwipeDirection();
if ((dir & TouchGesture.SWIPE_NORTH) > 0) {
rsi.scrollViewVertical(0, false);
} else if ((dir & TouchGesture.SWIPE_SOUTH) > 0) {
rsi.scrollViewVertical(0, true);
} else if ((dir & TouchGesture.SWIPE_EAST) > 0) {
rsi.scrollViewHorizontal(0, false);
} else if ((dir & TouchGesture.SWIPE_WEST) > 0) {
rsi.scrollViewHorizontal(0, true);
}
} else {
// CLICK *may* precede SWIPE in Storm - so we
// need to make sure it doesn't get processed for event binding when we receive a swipe
// @todo - do we want to regionalize swipe based on end location?
// @todo What about user defeined swipe region sequences? e
resetSavedEvent();
key = resolveSwipeDirection(g.getSwipeDirection());
}
break;
}
}
// Note that touch events do NOT reset the on-screen alt status, etc - so if keyboard is out
// this must also not reset the artifical alt status so that we can stay in sync.
if (key > 0 && keyEvent(key, 0, message.getTime(), false)) {
return true;
}
return false;
}
public int mapTouchEvent(int x, int y) {
// Divide the screen into a 9-cell grid.
int w = getWidth() / 3;
int h = getHeight() / 3;
boolean east = false;
boolean vcenter = false;
// Find out which range our value falls into.
if (x <= w) {
// default false
} else if (x <= w * 2) {
east = false;
vcenter = true;
} else {
east = true;
}
// There's likely a much more elegant way of doing this - for example,
// bitmask the values; or even build an array of these values and mod
// the x/y into an array index [0-8]
if (y <= h) {
if (vcenter) {
return KeyBindingHelper.KEY_TOUCH_TAP_NORTH;
} else if (east) {
return KeyBindingHelper.KEY_TOUCH_TAP_NORTHEAST;
} else {
return KeyBindingHelper.KEY_TOUCH_TAP_NORTHWEST;
}
} else if (y <= h * 2) {
if (vcenter) {
return KeyBindingHelper.KEY_TOUCH_TAP_CENTER;
} else if (east) {
return KeyBindingHelper.KEY_TOUCH_TAP_EAST;
} else {
return KeyBindingHelper.KEY_TOUCH_TAP_WEST;
}
} else {
if (vcenter) {
return KeyBindingHelper.KEY_TOUCH_TAP_SOUTH;
} else if (east) {
return KeyBindingHelper.KEY_TOUCH_TAP_SOUTHEAST;
} else {
return KeyBindingHelper.KEY_TOUCH_TAP_SOUTHWEST;
}
}
}
public static int resolveSwipeDirection(int swipeDirection) {
// @todo this is more of a static utility function...but can't go in KeybindingHelper which isn't
// aware of the touch screen (it's at the base version....)
// Map to our own constants, which are present across platform versions.
switch (swipeDirection) {
case TouchGesture.SWIPE_EAST:
return KeyBindingHelper.KEY_TOUCH_SWIPE_EAST;
case TouchGesture.SWIPE_NORTH:
return KeyBindingHelper.KEY_TOUCH_SWIPE_NORTH;
case TouchGesture.SWIPE_SOUTH:
return KeyBindingHelper.KEY_TOUCH_SWIPE_SOUTH;
case TouchGesture.SWIPE_WEST:
return KeyBindingHelper.KEY_TOUCH_SWIPE_WEST;
case (TouchGesture.SWIPE_SOUTH | TouchGesture.SWIPE_EAST):
return KeyBindingHelper.KEY_TOUCH_SWIPE_SOUTHEAST;
case (TouchGesture.SWIPE_SOUTH | TouchGesture.SWIPE_WEST):
return KeyBindingHelper.KEY_TOUCH_SWIPE_SOUTHWEST;
case (TouchGesture.SWIPE_NORTH | TouchGesture.SWIPE_EAST):
return KeyBindingHelper.KEY_TOUCH_SWIPE_NORTHEAST;
case (TouchGesture.SWIPE_NORTH | TouchGesture.SWIPE_WEST):
return KeyBindingHelper.KEY_TOUCH_SWIPE_NORTHWEST;
}
return 0;
}
}