package org.bbssh.ui.components;
import net.rim.blackberry.api.phone.Phone;
import net.rim.device.api.applicationcontrol.ApplicationPermissions;
import net.rim.device.api.applicationcontrol.ApplicationPermissionsManager;
import net.rim.device.api.i18n.ResourceBundle;
import net.rim.device.api.system.Alert;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Characters;
import net.rim.device.api.system.KeyListener;
import net.rim.device.api.system.KeypadListener;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Keypad;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.XYPoint;
import net.rim.device.api.ui.XYRect;
import org.bbssh.exceptions.FontNotFoundException;
import org.bbssh.i18n.BBSSHResource;
import org.bbssh.keybinding.BoundCommand;
import org.bbssh.keybinding.KeyBindingHelper;
import org.bbssh.model.ConnectionProperties;
import org.bbssh.model.FontSettings;
import org.bbssh.model.KeyBindingManager;
import org.bbssh.model.SettingsManager;
import org.bbssh.platform.PlatformServicesProvider;
import org.bbssh.session.RemoteSessionInstance;
import org.bbssh.session.SessionManager;
import org.bbssh.terminal.TerminalStateData;
import org.bbssh.terminal.VT320;
import org.bbssh.terminal.fonts.BBSSHFontManager;
import org.bbssh.terminal.fonts.FontRenderer;
import org.bbssh.ui.screens.TerminalScreen;
import org.bbssh.util.Logger;
import org.bbssh.util.Tools;
public class TerminalField extends Field {
// This will change when the keyboard is shown/hidden, so we'll track this
// to know
protected int width, height;
ResourceBundle bundle = ResourceBundle.getBundle(BBSSHResource.BUNDLE_ID,
BBSSHResource.BUNDLE_NAME);
private FontRenderer renderer;
private int lastKeyControlTS;
int lastNavTime = 0;
private FontSettings oldFontSettings = null;
private String expiringMessage;
XYRect statusBarRegionRect = new XYRect(0, 0, 0, 0);
private boolean statusInvalidated = false;
private boolean processChars = true;
private Font statusFont;
SessionManager sessionMgr;
private int messageTimer = -1;
Runnable expireMessageTask = new Runnable() {
public void run() {
if (expiringMessage != null) {
synchronized (expiringMessage) {
messageTimer = -1; // no longer valid now that the task has
// executed.
expiringMessage = null;
invalidateStatusIcons();
}
}
}
};
public void attachInstance(RemoteSessionInstance rsi)
throws FontNotFoundException {
updateFontSettings(rsi.state.fs);
sizeChanged(rsi.termInitPending);
rsi.termInitPending = false;
lastKeyControlTS = 0;
oldFontSettings = null;
expiringMessage = null;
statusInvalidated = false;
}
public TerminalField() {
Font f = getFont();
sessionMgr = SessionManager.getInstance();
statusFont = f
.derive(Font.ITALIC, (int) ((float) f.getHeight() * 0.65));
processChars = !PlatformServicesProvider.getInstance()
.isReducedLayout();
}
protected void layout(int width, int height) {
setPosition(0, 0);
setExtent(width, height);
this.width = width;
this.height = height;
statusBarRegionRect.height = statusFont.getHeight() + 16;
statusBarRegionRect.width = (width / 10) * 9;
statusBarRegionRect.x = width - statusBarRegionRect.width;
statusBarRegionRect.y = 0;
}
/**
* Internal method invoked to reflect any size changes to the display region
* (includign that at initial load.) This initializes character grid
* height/width, and (if it occurs after session is launched) sends update
* to the remote server of dimension changes.
*
* @param updateRemote
* if true, also send a request to remote terminal requesting
* term size change
*/
public void sizeChanged(boolean updateRemote) {
// Okay, this is messy - basically don't process anything if we
// haven'tinitialized. .
if (width == 0 || height == 0)
return;
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null)
return;
XYPoint dim = renderer.getFontDimensions();
rsi.state.numColsVisible = width / dim.x;
rsi.state.numRows = height / dim.y;
rsi.backingStore = new Bitmap(width, height);
rsi.backingStoreGR = PlatformServicesProvider.getInstance()
.getGraphicsObjectForBitmap(rsi.backingStore);
rsi.backingStoreGR.setColor(Tools.color[rsi.session.getProperties()
.getBackgroundColorIndex()]);
// Our font might not take us up to teh edges of the screen - so provide
// a proper fill
rsi.backingStoreGR.fillRect(0, 0, width, height);
int virtualCols = rsi.state.numColsVisible;
int virtualRows = rsi.state.numRows;
boolean colOverride = false, rowOverride = false;
if (rsi.state.settings.getTerminalCols() != 0) {
// don't force a resize as we're overriding screen-based terminal
// size.
colOverride = true;
virtualCols = rsi.state.settings.getTerminalCols();
}
if (rsi.state.settings.getTerminalRows() != 0) {
// don't force a resize as we're overriding screen-based
// terminal size.
rowOverride = true;
virtualRows = rsi.state.settings.getTerminalRows();
}
if (colOverride && rowOverride)
updateRemote = false;
rsi.emulator.setScreenSize(virtualCols, virtualRows, updateRemote);
rsi.state.maxHeight = rsi.emulator.getTerminalHeight();
rsi.state.maxWidth = rsi.emulator.getTerminalWidth();
redraw(true);
}
protected void paintBackground(Graphics graphics) {
RemoteSessionInstance rsi = SessionManager.getInstance().activeSession;
int color = 0;
if (rsi != null)
color = rsi.session.getProperties().getBackgroundColorIndex();
graphics.setBackgroundColor(color);
graphics.clear();
}
protected void paint(Graphics g) {
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null || rsi.backingStoreGR == null) {
g.drawText("No session connected", 0, 0);
g.pushRegion(statusBarRegionRect, 0, 0);
paintStatusIcons(g);
statusInvalidated = false;
g.popContext();
return;
}
rsi.state.debugPaintCount++;
// If our terminal has been updated, this will refresh our bitmap.
redrawBackingStore(rsi.backingStoreGR);
// Now render the bitmap to screen.
XYRect r = g.getClippingRect();
g.drawBitmap(r, rsi.backingStore, r.x, r.y);
// save current alpha so that we can restore it later.
int alpha = g.getGlobalAlpha();
g.pushRegion(statusBarRegionRect, 0, 0);
paintStatusIcons(g);
statusInvalidated = false;
g.popContext();
// g.setGlobalAlpha(175);
// paintChild(g, overlayFields);
g.setGlobalAlpha(alpha);
}
private void paintStatusIcons(Graphics g) {
g.setGlobalAlpha(200);
XYRect r = g.getClippingRect();
// offset from the right by 10% - this will allow the standard
// indicators
// to be displayed without overwriting our own indicators
int xpos = (int) (r.width - (r.width * .10));
int ypos = 2;
g.setFont(statusFont);
if (sessionMgr.activeSession == null) {
return;
}
if (sessionMgr.activeSession.state.settings.isCaptureEnabled()) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_IND_RECORDING),
Color.RED);
}
TerminalStateData state = sessionMgr.activeSession.state;
if (state.typingMode == TerminalStateData.TYPING_MODE_LOCAL_SCROLL) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_IND_SCROLL_MODE));
}
if (state.ctrlPressed) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_CTRL_PRESSED));
}
if (state.altPressed) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_ALT_PRESSED));
}
// Display the fake alt/shift notification icons only if the standard OS
// versions won't be displayed -
PlatformServicesProvider psp = PlatformServicesProvider.getInstance();
if (state.isExternalArtificialStatusUpdate()
|| (psp.hasTouchscreen() && (!psp.hasHardwareKeyboard() || !psp
.isSliderExtended()))) {
int status = state.getArtificialStatus(false);
if ((status & KeypadListener.STATUS_ALT) > 0) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_IND_BB_ALT));
}
if ((status & KeypadListener.STATUS_SHIFT_LEFT) > 0) {
if (psp.hasShiftX()) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_IND_BB_SHIFT));
} else {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_IND_BB_SHIFT_LEFT));
}
} else if ((status & KeypadListener.STATUS_SHIFT_RIGHT) > 0) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_IND_BB_SHIFT_RIGHT));
} else if ((status & KeypadListener.STATUS_SHIFT) > 0) {
xpos = drawIndicator(
g,
xpos,
ypos,
Tools.getStringResource(BBSSHResource.STATUS_MSG_IND_BB_SHIFT));
}
}
int orientation = sessionMgr.activeSession.state.orientationMode;
// For now only portrait/landscape locks are supported. east/west
// not yet implemented - as this may require a more platform specific
// implementation
if (orientation != TerminalStateData.DIRECTION_ALL) {
if (orientation == TerminalStateData.DIRECTION_NORTH
|| orientation == TerminalStateData.DIRECTION_PORTRAIT) {
xpos = drawIndicator(g, xpos, ypos, "P");
} else {
xpos = drawIndicator(g, xpos, ypos, "L");
}
}
String message = getExpiringMessage();
if (message != null) {
xpos = drawIndicator(g, xpos, ypos, message);
}
}
private int drawIndicator(Graphics g, int xpos, int ypos, String text,
int color) {
int size = statusFont.getAdvance(text) + 8;
int h = statusFont.getHeight() + 8;
xpos -= size;
g.setColor(Color.SLATEGRAY);
g.fillRoundRect(xpos, ypos, size, h, 14, 14);
g.setColor(Color.BLACK);
g.drawRoundRect(xpos, ypos, size, h, 14, 14);
g.setColor(color);
g.drawText(text, xpos + 4, ypos + 4);
return xpos - 4;
}
private int drawIndicator(Graphics g, int xpos, int ypos, String text) {
return drawIndicator(g, xpos, ypos, text, Color.GOLD);
}
protected boolean keyEvent(int keyCode, int status, int time) {
return keyEvent(keyCode, status, time, true);
}
/**
* Handle any/all user-generated events by looking up custom event mappings
* and dispatching to them if they exist.
*/
protected boolean keyEvent(int keyCode, int status, int time,
boolean resetVirtualStatus) {
// Logger.debug("keyCode: " + keyCode + " status: " + status);
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null)
return false;
// If we're using a virtual keyboard, and this is an alted character,
// then remove the alt flag - this will prevent situations where some
// virt keyboard characters (such as 0 or currency) are only available
// "alted" on the virt keyboard - and so become unavailable compeltely
// if the user has bound them w/ alt, eg alt+0, alt+currency
if (((TerminalScreen) getScreen()).isVirtualKeyboardVisible()) {
if ((status & KeypadListener.STATUS_ALT) > 0
&& Keypad.getUnaltedChar(Keypad.map(keyCode, status)) == 0) {
status &= ~KeypadListener.STATUS_ALT;
}
}
// If the caller tells us that we can reset virtual status, then we'll
// just do so. Otherwise we'll decide for ourselves - if we're a
// touchscreen device without a keyboard; or but it's a retracted
// slider... then we'll not reset it. This mirrors the actual behavior
// of (example) ALT not getting reset in resposne to a touch event,
// keeping our internal state consistent with device state. It's ugly,
// but until RIM gives us a way to simpyl query the Alt/Shift key
// status.
if (!resetVirtualStatus) {
PlatformServicesProvider psp = PlatformServicesProvider
.getInstance();
resetVirtualStatus = psp.isReducedLayout();
resetVirtualStatus = resetVirtualStatus
|| (psp.hasTouchscreen() && (!psp.hasHardwareKeyboard() || !psp
.isSliderExtended()));
resetVirtualStatus = resetVirtualStatus
|| rsi.state.isExternalArtificialStatusUpdate();
}
int s = rsi.state.getArtificialStatus(resetVirtualStatus);
int newStatus = (status | s)
& KeyBindingHelper.SUPPORTED_KEYPAD_MODIFIERS;
BoundCommand mapping = KeyBindingManager.getInstance().getKeyBinding(
keyCode, newStatus);
boolean ret = false;
if (mapping != null) {
if (!mapping.execute(sessionMgr.activeSession, true)) {
Logger.error("Command failed to execute: " + mapping);
}
// Even if the mapping failed to execute, the mapping *existed* - so
// we don't want to
// double-process this keystroke.
ret = true;
}
invalidateStatusIcons();
return ret;
}
protected boolean trackwheelClick(int status, int time) {
boolean result = keyEvent(KeyBindingHelper.KEY_NAV_CLICK, status, time);
if (result == false) {
result = super.trackwheelClick(status, time);
}
return result;
}
/**
* Override of navigationClick that passes control to custom keyhandler. If
* unhandled, the default implementation is invoked.
*
* @param status
* @param time
* @return
*/
protected boolean navigationClick(int status, int time) {
boolean result = keyEvent(KeyBindingHelper.KEY_NAV_CLICK, status, time);
if (!result) {
result = super.navigationClick(status, time);
}
return result;
}
/**
* Override of navigationMovement that passes control to custom keyhandler.
* If unhandled, the default implemention is currently NOT invoked.
*
* @param dx
* delta in X
* @param dy
* delty in Y
* @param status
* @param time
* @return
*/
protected boolean navigationMovement(int dx, int dy, int status, int time) {
if (sessionMgr.activeSession == null)
return true;
if (sessionMgr.activeSession.state.typingMode == TerminalStateData.TYPING_MODE_LOCAL_SCROLL) {
if (dy != 0) {
if ((status & KeyListener.STATUS_ALT) > 0) {
sessionMgr.activeSession.scrollViewVertical(0, dy > 0);
} else {
sessionMgr.activeSession.scrollViewVertical(Math.abs(dy),
dy > 0);
}
}
if (dx != 0) {
if ((status & KeyListener.STATUS_ALT) > 0) {
sessionMgr.activeSession.scrollViewHorizontal(0, dx > 0);
} else {
sessionMgr.activeSession.scrollViewHorizontal(Math.abs(dx),
dx > 0);
}
}
} else {
// Rate-limit to prevent flooding if this is a 4-way devices
if ((status & KeyListener.STATUS_FOUR_WAY) > 0) {
if (time - lastNavTime < 100) {
return true;
}
lastNavTime = time;
}
if (dy < 0) {
keyEvent(KeyBindingHelper.KEY_NAV_UP, status, time);
} else if (dy > 0) {
keyEvent(KeyBindingHelper.KEY_NAV_DOWN, status, time);
}
if (dx < 0) {
keyEvent(KeyBindingHelper.KEY_NAV_LEFT, status, time);
} else if (dx > 0) {
keyEvent(KeyBindingHelper.KEY_NAV_RIGHT, status, time);
}
}
// in no case do we want default directional handling.
return true;
}
protected boolean keyChar(char c, int status, int time) {
// Logger.debug("keyChar: " + c);
if (!processChars)
return true;
if (sessionMgr.activeSession == null
|| sessionMgr.activeSession.state == null)
return super.keyChar(c, status, time);
TerminalStateData state = sessionMgr.activeSession.state;
if (state.typingMode == TerminalStateData.TYPING_MODE_LOCAL_SCROLL) {
showExpiringAlertMessage(BBSSHResource.STATUS_MSG_IN_SCROLL_MODE);
return true;
}
if (keyEvent(c, status, time))
return true;
state.setArtificialStatus(0);
// If we're in direct mode; or we're in "special key" mode where
// CTRL or ALT mode is enabled , then send text straight through.
// even in hybrid mode, we won't show the editor window in this
// scenario.
if (state.typingMode == TerminalStateData.TYPING_MODE_DIRECT
|| state.altPressed || state.ctrlPressed) {
sessionMgr.activeSession.emulator.keyTyped(0, c,
state.getModifierKeyState(status, true));
// This will change alt status, etc as well as outbound buffer size.
invalidateStatusIcons();
return true;
}
// If we're in hybrid mode, then display the input overlay and popup the
// value
String value = null;
if (state.typingMode == TerminalStateData.TYPING_MODE_HYBRID) {
if (c != '\n' && c != '\r') {
value = new String(new char[] { c });
}
((TerminalScreen) getScreen()).showInputOverlay(value);
}
return true;
}
protected boolean keyControl(char c, int status, int time) {
// Even a single volume press for some reason results in two volume
// events.
// This will filter those duplicates out while not limiting rate too
// much.
if ((time - lastKeyControlTS) < 50) {
return true;
}
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null)
return true;
lastKeyControlTS = time;
if (c == Characters.CONTROL_VOLUME_DOWN
|| c == Characters.CONTROL_VOLUME_UP) {
if ((status & rsi.state.getArtificialStatus(false) & KeyBindingHelper.SUPPORTED_KEYPAD_MODIFIERS) == 0
&& suppressBinding()) {
return super.keyControl(c, status, time);
}
keyEvent(c, status, time);
return true;
}
rsi.state.setArtificialStatus(0);
invalidateStatusIcons();
return super.keyControl(c, status, time);
}
protected boolean suppressBinding() {
// If we are going to get an exception for tryign .... don't try - we
// can't suppress.
if (ApplicationPermissionsManager.getInstance().getPermission(
ApplicationPermissions.PERMISSION_PHONE) == ApplicationPermissions.VALUE_DENY)
return false;
if (Phone.getActiveCall() != null) {
if (SettingsManager.getSettings().getDisableKeybindWhenOnCall()) {
showExpiringAlertMessage(BBSSHResource.TERMINAL_MSG_BINDING_DISABLED);
return true;
}
}
return false;
}
protected boolean keyDown(int keycode, int time) {
int key = Keypad.key(keycode);
int stat = Keypad.status(keycode);
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null)
return super.keyDown(keycode, time);
// Volume Down and Volume Up are delivered only in 5.0+ - but the
// keyControl
// version of the same works for all versions including 5 & 6. Skip
// processing it
// here , and pick it up there.
if (key == Keypad.KEY_VOLUME_DOWN || key == Keypad.KEY_VOLUME_UP)
return super.keyDown(keycode, time);
// For media and phone keys, we will need to ignore them if a call is in
// progress.
if (key == Keypad.KEY_SEND || key == Keypad.KEY_END
|| key == Keypad.KEY_SPEAKERPHONE) {
if ((stat & rsi.state.getArtificialStatus(false) & KeyBindingHelper.SUPPORTED_KEYPAD_MODIFIERS) == 0
&& suppressBinding())
return super.keyDown(keycode, time);
}
if (keyEvent(key, stat, time)) {
return true;
}
return super.keyDown(keycode, time);
}
protected void onObscured() {
sessionMgr.notifyActiveSessionObscured();
if (sessionMgr.activeSession != null)
sessionMgr.activeSession.state.setArtificialStatus(0);
super.onObscured();
}
protected void onExposed() {
sessionMgr.notifyActiveSessionExposed();
if (sessionMgr.activeSession != null)
sessionMgr.activeSession.state.setArtificialStatus(0);
super.onExposed();
}
public void redraw(boolean fullRefresh) {
if (renderer == null)
return;
// cache local reference because we use this many times in this method.
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null) {
return;
}
// Ensure that the backing store doesn't get modified at the same time
// we're
// requesting a refresh. This indirectly guarantees that the drawing
// operation
// can't occur while modifications to the underlyin gbuffer are still in
// progress.
rsi.state.refreshRequired = true;
if (fullRefresh) {
rsi.state.debugFullRefreshCount++;
rsi.state.fullRefreshRequired = true;
rsi.state.numColsVisible = rsi.emulator.getWidth();
invalidate();
return;
} else {
rsi.state.debugPartialRefreshCount++;
}
XYPoint fontSize = renderer.getFontDimensions();
int bottomTermRow = Math.min(rsi.state.topTermRow
+ rsi.emulator.screenBase + rsi.state.numRows,
rsi.emulator.bufSize);
int drawRow = 0;
// Find out which rows are invalid, so that we don't repaint the entire
// screen if we don't need to.
// We will also always invalidate the status bar region.
invalidateStatusIcons();
int first = -1;
int last = -1;
for (int line = rsi.state.topTermRow + rsi.emulator.screenBase; line < bottomTermRow; line++, drawRow += fontSize.y) {
if (rsi.emulator.isLineDirty(line)) {
if (first == -1) {
first = drawRow;
}
last = drawRow;
}
}
this.invalidate(0, first, getWidth(), (last + fontSize.y) - first);
}
protected void redrawBackingStoreImpl(Graphics g) {
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null)
return;
TerminalStateData state = rsi.state;
XYPoint fontSize = renderer.getFontDimensions();
rsi.state.debugPaintBackStoreCount++;
ConnectionProperties prop = rsi.session.getProperties();
int bgcoloridx = prop.getBackgroundColorIndex();
int fgcoloridx = prop.getForegroundColorIndex();
int bottomTermRow = Math.min(rsi.state.topTermRow + rsi.state.numRows,
rsi.emulator.getBufferSize());
int rightTermCol = Math.min(rsi.emulator.getTerminalWidth(),
(state.left + state.numColsVisible));
// Declare these outside the loops so they don't continually get
// re-pushed onto the stack.
int x, termCol, count, fg, bg, currAttr;
int drawRow = 0, drawCol = 0;
long[] row;
for (int line = state.topTermRow, index = rsi.emulator.windowBase
+ state.topTermRow; line < bottomTermRow; line++, index++, drawRow += fontSize.y) {
state.debugLineEvalCount++;
if (!state.fullRefreshRequired && !rsi.emulator.isLineDirty(index)) {
continue;
}
state.debugLinePaintCount++;
rsi.emulator.setLineClean(line);
drawCol = 0;
row = rsi.emulator.terminalData[index];
count = 0;
for (termCol = rsi.state.left; termCol < rightTermCol; termCol += count) {
currAttr = (int) (row[termCol] >> 32);
if ((state.typingMode == TerminalStateData.TYPING_MODE_SELECT && (currAttr & VT320.XATTR_SELECTED) != 0)) {
bg = 0xAAAAAA;
fg = 0x111111;
} else {
fg = getColor(currAttr, fgcoloridx, true);
bg = getColor(currAttr, bgcoloridx, false);
}
// We're not going to do an actual blink (that requires timed
// regular repaints, which we want to
// avoid for perf reasons on a mobile platform. Instead, treat
// as invert.
if ((currAttr & VT320.BLINK) != 0) {
currAttr |= VT320.INVERT;
}
if ((currAttr & VT320.INVERT) != 0) {
// Logger.debug("Attribute: INVERT");
int t = bg;
bg = fg;
fg = t;
}
// How many cols can we draw in this pass? Look to see how many
// have identical attributes.
for (count = 0, x = termCol; x < rightTermCol
&& x < state.maxWidth; x++, count++) {
// @todo - any risk of breakage if a col has an attr value
// but no text? eg we should
// clear it but are not?
if (row[x] < ' ') {
row[x] = ' ';
}
if ((row[x] >> 32) != currAttr)
break;
}
// Logger.debug(" ** drawing from " + colPos + " to " + (addr -
// 1));
g.setColor(fg);
g.setBackgroundColor(bg);
// Fill the background...
g.clear(drawCol, drawRow, fontSize.x * count, fontSize.y);
renderer.drawChars(g, fg, bg, row, termCol, count, drawCol,
drawRow);
if ((currAttr & VT320.UNDERLINE) > 0) {
g.setColor(fg);
// offset by two lines if we're inverted, otherwise one --
// this ensures that it will show up.
int yVal = drawRow + fontSize.y
- ((currAttr & VT320.INVERT) > 0 ? 2 : 1);
g.drawLine(drawCol, yVal, drawCol + (fontSize.x * count),
yVal);
}
if (count == 0) {
Logger.error("Char Draw Count == 0, should not have happened - rendering may be corrupt");
count++;
}
drawCol += fontSize.x * count;
}
}
// If we've gone through all visible rows but still have unaccounted for
// height,
// then fix this now by filling in anythign remainign.
if (drawRow < height) {
g.setBackgroundColor(Tools.color[bgcoloridx]);
g.clear(0, drawRow, width, height - drawRow);
}
// Set cursor color to be the opposite of the text color here (inverse)
int attr = (int) (rsi.emulator.terminalData[rsi.emulator.cursorY][rsi.emulator.cursorX] >> 32);
g.setColor(getColor(attr, fgcoloridx, true));
g.setBackgroundColor(getColor(attr, bgcoloridx, false));
if (state.typingMode == TerminalStateData.TYPING_MODE_SELECT) {
drawCursor(g, rsi.state.selectionCursorX,
rsi.state.selectionCursorY, fontSize);
} else {
drawCursor(g, rsi.emulator.cursorX, rsi.emulator.cursorY, fontSize);
}
rsi.state.refreshRequired = false;
rsi.state.fullRefreshRequired = false;
}
private int getColor(int attributes, int defaultIndex, boolean foreground) {
int requestedIndex;
if (foreground) {
requestedIndex = ((attributes & VT320.COLOR_FG) >> 4) - 1;
} else {
requestedIndex = ((attributes & VT320.COLOR_BG) >> 8) - 1;
}
if (requestedIndex == -1) { // not set - use default value.
requestedIndex = defaultIndex;
}
if (foreground) {
if ((attributes & VT320.BOLD) != 0)
return Tools.boldcolor[requestedIndex];
if ((attributes & VT320.LOW) != 0)
return Tools.lowcolor[requestedIndex];
}
return Tools.color[requestedIndex];
}
// private String debugGetString(long[] row, int offset) {
// int size = row.length - offset;
//
// char[] data = new char[size];
// for (int x = offset; x < row.length; x++) {
// data[x - offset] = (char) (row[x] & 0xFF);
//
// }
//
// return new String(data);
// }
protected void drawCursor(Graphics g, int cursorX, int cursorY,
XYPoint fontSize) {
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null)
return;
cursorY = rsi.emulator.screenBase + cursorY;
// draw cursor if it's visible in the current viewport
// screenbase + pos > windowbase and < [end of displayed - must calc
// displayed line in common place.)
if (rsi.emulator.showcursor
&& (cursorY >= rsi.emulator.windowBase && cursorY < rsi.emulator.windowBase
+ rsi.emulator.getTerminalHeight())) {
int height = fontSize.y / 3; // this is our cursor height
g.fillRect(
(cursorX - rsi.state.left) * fontSize.x,
((cursorY - rsi.state.topTermRow - rsi.emulator.windowBase) * fontSize.y)
+ (fontSize.y - height), fontSize.x, height);
}
}
/**
* We maintain a "backing store" image that we paint onto the screen. When a
* repaint is required but nothing has changed, nothing further is needed
* because the image is saved. However, if the image needs to change (such
* as when text arrives in the terminal) we must redraw it. This method
* handles the redrawing. When our content is invalidated, we need to
* recreate that image with the new contents. We will refresh our image with
* the lines that the model says are dirty -- note that when we do this, the
* model is driving the behavior nad not the view - so we don't care which
* rows the OS says are dirty.
*/
protected void redrawBackingStore(Graphics g) {
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null || !rsi.state.refreshRequired) {
return;
}
// Ensure that the backing store doesn't get modified at the same time
// we're
// requesting a refresh. This indirectly guarantees that the drawing
// operation
// can't occur while modifications to the underlyin gbuffer are still in
// progress.
long start = System.currentTimeMillis();
synchronized (rsi.emulator.getTermBufferMutex()) {
rsi.state.debugRedrawStartWaitTime += (System.currentTimeMillis() - start);
redrawBackingStoreImpl(g);
}
}
public void invalidateStatusIcons() {
if (statusInvalidated)
return;
statusInvalidated = true;
invalidate(statusBarRegionRect.x, statusBarRegionRect.y,
statusBarRegionRect.width, statusBarRegionRect.height);
}
/**
* Update the current font used. Make sure to invoke "sizeChanged"
* afterwards, if the font differs.
*
* @param settings
* @return true if the font has been changed.
* @throws FontNotFoundException
*/
public boolean updateFontSettings(FontSettings settings)
throws FontNotFoundException {
if (SessionManager.getInstance().activeSession == null)
return false;
renderer = BBSSHFontManager.getInstance().getRenderer(settings);
// We only want to do this AFTER the initial font setup is complete
if (oldFontSettings == null) {
oldFontSettings = new FontSettings(settings);
return true;
} else if (oldFontSettings.equals(settings)) {
return false;
}
oldFontSettings = new FontSettings(settings);
redraw(true);
return true;
}
/**
* returns font settings currently in use.
*/
public FontSettings getFontSettings() {
if (renderer == null)
return null;
return renderer.getSettings();
}
public void showExpiringAlertMessage(int messageId) {
showExpiringMessage(Tools.getStringResource(messageId));
if (SettingsManager.getSettings().isVibrateOnAlertEnabled()) {
Alert.startVibrate(100);
}
}
public void showExpiringMessage(final String expiringMessage) {
if (isVisible()) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
setExpiringMessage(expiringMessage);
if (messageTimer != -1) {
UiApplication.getUiApplication().cancelInvokeLater(
messageTimer);
}
messageTimer = UiApplication.getUiApplication()
.invokeLater(expireMessageTask,
500 + (25 * expiringMessage.length()),
false);
invalidateStatusIcons();
}
});
}
}
private synchronized String getExpiringMessage() {
return expiringMessage;
}
private synchronized void setExpiringMessage(String expiringMessage) {
this.expiringMessage = expiringMessage;
}
public boolean isFocusable() {
return true;
}
/**
* Maintain
*/
protected boolean keyStatus(int keycode, int time) {
RemoteSessionInstance rsi = sessionMgr.activeSession;
if (rsi == null)
return true;
// note: if user has changed status while virtual keyboard is visible -
// do NOT toggle
// virtual status in that case, as it creates an inconsistent result --
// because we disregard
// alt coming from virtual keyboard when processing the keystroke.
if (PlatformServicesProvider.getInstance().hasTouchscreen()
&& !((TerminalScreen) getScreen()).isVirtualKeyboardVisible()) {
int key = Keypad.key(keycode);
switch (key) {
case (Keypad.KEY_SHIFT_RIGHT):
rsi.state.toggleArtificialStatus(KeyListener.STATUS_SHIFT
| KeyListener.STATUS_SHIFT_RIGHT, false);
break;
case (Keypad.KEY_SHIFT_LEFT):
rsi.state.toggleArtificialStatus(KeyListener.STATUS_SHIFT
| KeyListener.STATUS_SHIFT_LEFT, false);
break;
case (Keypad.KEY_ALT):
rsi.state.toggleArtificialStatus(KeyListener.STATUS_ALT, false);
break;
}
}
invalidateStatusIcons();
return true;
}
}