aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Hansberry <rhansby@gmail.com>2015-10-02 17:50:01 -0700
committerRyan Hansberry <rhansby@gmail.com>2015-10-07 10:28:29 -0700
commitf93f415b77fd777bccbac6c73f2aecfe01464fb8 (patch)
tree15ccab6857479e6f8e654ba5e57d69a2e2f0cae2
parentfde40fdcb62c764df90e889ae95e28b86ac5f746 (diff)
downloadconnectbot-f93f415b77fd777bccbac6c73f2aecfe01464fb8.tar.gz
connectbot-f93f415b77fd777bccbac6c73f2aecfe01464fb8.tar.bz2
connectbot-f93f415b77fd777bccbac6c73f2aecfe01464fb8.zip
Text selection now uses TextView. Pre-Honeycomb uses the same method as before.
-rw-r--r--app/src/main/java/org/connectbot/ConsoleActivity.java363
-rw-r--r--app/src/main/java/org/connectbot/TerminalView.java329
-rw-r--r--app/src/main/java/org/connectbot/service/TerminalBridge.java44
-rw-r--r--app/src/main/java/org/connectbot/service/TerminalKeyListener.java8
4 files changed, 413 insertions, 331 deletions
diff --git a/app/src/main/java/org/connectbot/ConsoleActivity.java b/app/src/main/java/org/connectbot/ConsoleActivity.java
index d628a07..e979b34 100644
--- a/app/src/main/java/org/connectbot/ConsoleActivity.java
+++ b/app/src/main/java/org/connectbot/ConsoleActivity.java
@@ -50,10 +50,9 @@ import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
-import android.support.v4.app.ActivityCompat;
import android.support.design.widget.TabLayout;
+import android.support.v4.app.ActivityCompat;
import android.support.v4.view.MenuItemCompat;
-import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
@@ -61,9 +60,6 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.ClipboardManager;
import android.util.Log;
-import android.view.ContextMenu;
-import android.view.GestureDetector;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -74,7 +70,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
@@ -100,8 +95,6 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
protected static final int REQUEST_EDIT = 1;
- private static final int CLICK_TIME = 400;
- private static final float MAX_CLICK_DISTANCE = 25f;
private static final int KEYBOARD_DISPLAY_TIME = 3000;
private static final int KEYBOARD_REPEAT_INITIAL = 500;
private static final int KEYBOARD_REPEAT = 100;
@@ -140,7 +133,6 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
private Animation fade_out_delayed;
private Animation keyboard_fade_in, keyboard_fade_out;
- private float lastX, lastY;
private InputMethodManager inputManager;
@@ -498,8 +490,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
inflater = LayoutInflater.from(this);
toolbar = (Toolbar) findViewById(R.id.toolbar);
+
pager = (ViewPager) findViewById(R.id.console_flip);
- registerForContextMenu(pager);
+
pager.addOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
@Override
@@ -669,255 +662,81 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
if (tabs != null)
setupTabLayoutWithViewPager();
- // detect fling gestures to switch between terminals
- final GestureDetector detect = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
- private float totalY = 0;
-
- @Override
- public void onLongPress(MotionEvent e) {
- super.onLongPress(e);
- openContextMenu(pager);
- }
-
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ pager.setOnTouchListener(new OnTouchListener() {
+ public boolean onTouch(View v, MotionEvent event) {
+ TerminalBridge bridge = adapter.getCurrentTerminalView().bridge;
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-
- // if copying, then ignore
- if (copySource != null && copySource.isSelectingForCopy())
- return false;
-
- if (e1 == null || e2 == null)
- return false;
-
- // if releasing then reset total scroll
- if (e2.getAction() == MotionEvent.ACTION_UP) {
- totalY = 0;
- }
-
- // activate consider if within x tolerance
- int touchSlop = ViewConfiguration.get(ConsoleActivity.this).getScaledTouchSlop();
- if (Math.abs(e1.getX() - e2.getX()) < touchSlop * 4) {
-
- View view = adapter.getCurrentTerminalView();
- if (view == null) return false;
- TerminalView terminal = (TerminalView) view;
-
- // estimate how many rows we have scrolled through
- // accumulate distance that doesn't trigger immediate scroll
- totalY += distanceY;
- final int moved = (int) (totalY / terminal.bridge.charHeight);
-
- // consume as scrollback only if towards right half of screen
- if (e2.getX() > view.getWidth() / 2) {
- if (moved != 0) {
- int base = terminal.bridge.buffer.getWindowBase();
- terminal.bridge.buffer.setWindowBase(base + moved);
- totalY = 0;
- return true;
- }
- } else {
- // otherwise consume as pgup/pgdown for every 5 lines
- if (moved > 5) {
- ((vt320) terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0);
- terminal.bridge.tryKeyVibrate();
- totalY = 0;
- return true;
- } else if (moved < -5) {
- ((vt320) terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0);
- terminal.bridge.tryKeyVibrate();
- totalY = 0;
- return true;
- }
+ boolean isCopyingInProgress =
+ (copySource != null && copySource.isSelectingForCopy());
+ if (!isCopyingInProgress && keyboardGroup.getVisibility() == View.GONE) {
+ showEmulatedKeys(true);
}
- }
-
- return false;
- }
-
-
- });
-
- pager.setLongClickable(true);
- pager.setOnTouchListener(new OnTouchListener() {
-
- public boolean onTouch(View v, MotionEvent event) {
- TerminalBridge bridge = adapter.getCurrentTerminalView().bridge;
+ // when copying, highlight the area
+ if (isCopyingInProgress) {
+ SelectionArea area = copySource.getSelectionArea();
+ int row = (int) Math.floor(event.getY() / bridge.charHeight);
+ int col = (int) Math.floor(event.getX() / bridge.charWidth);
- // Handle mouse-specific actions.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
- MotionEventCompat.getSource(event) == InputDevice.SOURCE_MOUSE) {
- if (onMouseEvent(event, bridge)) {
- return true;
- }
- }
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ // recording starting area
+ if (area.isSelectingOrigin()) {
+ area.setRow(row);
+ area.setColumn(col);
+ lastTouchRow = row;
+ lastTouchCol = col;
+ copySource.redraw();
+ }
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ /* ignore when user hasn't moved since last time so
+ * we can fine-tune with directional pad
+ */
+ if (row == lastTouchRow && col == lastTouchCol)
+ return true;
- // when copying, highlight the area
- if (copySource != null && copySource.isSelectingForCopy()) {
- SelectionArea area = copySource.getSelectionArea();
- int row = (int) Math.floor(event.getY() / bridge.charHeight);
- int col = (int) Math.floor(event.getX() / bridge.charWidth);
+ // if the user moves, start the selection for other corner
+ area.finishSelectingOrigin();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- // recording starting area
- if (area.isSelectingOrigin()) {
+ // update selected area
area.setRow(row);
area.setColumn(col);
lastTouchRow = row;
lastTouchCol = col;
copySource.redraw();
- }
- return true;
- case MotionEvent.ACTION_MOVE:
- /* ignore when user hasn't moved since last time so
- * we can fine-tune with directional pad
- */
- if (row == lastTouchRow && col == lastTouchCol)
- return true;
-
- // if the user moves, start the selection for other corner
- area.finishSelectingOrigin();
-
- // update selected area
- area.setRow(row);
- area.setColumn(col);
- lastTouchRow = row;
- lastTouchCol = col;
- copySource.redraw();
- return true;
- case MotionEvent.ACTION_UP:
- /* If they didn't move their finger, maybe they meant to
- * select the rest of the text with the directional pad.
- */
- if (area.getLeft() == area.getRight() &&
- area.getTop() == area.getBottom()) {
return true;
- }
-
- // copy selected area to clipboard
- String copiedText = area.copyFrom(copySource.buffer);
-
- clipboard.setText(copiedText);
- Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show();
- // fall through to clear state
-
- case MotionEvent.ACTION_CANCEL:
- // make sure we clear any highlighted area
- area.reset();
- copySource.setSelectingForCopy(false);
- copySource.redraw();
- return true;
- }
- }
+ case MotionEvent.ACTION_UP:
+ /* If they didn't move their finger, maybe they meant to
+ * select the rest of the text with the directional pad.
+ */
+ if (area.getLeft() == area.getRight() &&
+ area.getTop() == area.getBottom()) {
+ return true;
+ }
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- lastX = event.getX();
- lastY = event.getY();
- } else if (event.getAction() == MotionEvent.ACTION_UP
- && keyboardGroup.getVisibility() == View.GONE
- && event.getEventTime() - event.getDownTime() < CLICK_TIME
- && Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE
- && Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) {
- showEmulatedKeys(true);
- }
+ // copy selected area to clipboard
+ String copiedText = area.copyFrom(copySource.buffer);
- // pass any touch events back to detector
- return detect.onTouchEvent(event);
- }
+ clipboard.setText(copiedText);
+ Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show();
+ // fall through to clear state
- /**
- * @param event
- * @param bridge
- * @return True if the event is handled.
- */
- @TargetApi(14)
- private boolean onMouseEvent(MotionEvent event, TerminalBridge bridge) {
- int row = (int) Math.floor(event.getY() / bridge.charHeight);
- int col = (int) Math.floor(event.getX() / bridge.charWidth);
- int meta = event.getMetaState();
- boolean shiftOn = (meta & KeyEvent.META_SHIFT_ON) != 0;
- boolean mouseReport = ((vt320) bridge.buffer).isMouseReportEnabled();
-
- // MouseReport can be "defeated" using the shift key.
- if ((!mouseReport || shiftOn)) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- switch (event.getButtonState()) {
- case MotionEvent.BUTTON_PRIMARY:
- // Automatically start copy mode if using a mouse.
- startCopyMode();
- break;
- case MotionEvent.BUTTON_SECONDARY:
- openContextMenu(pager);
- return true;
- case MotionEvent.BUTTON_TERTIARY:
- // Middle click pastes.
- pasteIntoTerminal();
+ case MotionEvent.ACTION_CANCEL:
+ // make sure we clear any highlighted area
+ area.reset();
+ copySource.setSelectingForCopy(false);
+ copySource.redraw();
return true;
}
}
- } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
- ((vt320) bridge.buffer).mousePressed(
- col, row, mouseEventToJavaModifiers(event));
- return true;
- } else if (event.getAction() == MotionEvent.ACTION_UP) {
- ((vt320) bridge.buffer).mouseReleased(col, row);
- return true;
- } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
- int buttonState = event.getButtonState();
- int button = (buttonState & MotionEvent.BUTTON_PRIMARY) != 0 ? 0 :
- (buttonState & MotionEvent.BUTTON_SECONDARY) != 0 ? 1 :
- (buttonState & MotionEvent.BUTTON_TERTIARY) != 0 ? 2 : 3;
- ((vt320) bridge.buffer).mouseMoved(
- button,
- col,
- row,
- (meta & KeyEvent.META_CTRL_ON) != 0,
- (meta & KeyEvent.META_SHIFT_ON) != 0,
- (meta & KeyEvent.META_META_ON) != 0);
+
return true;
}
-
- return false;
- }
-
- });
- }
-
- /**
- * Takes an android mouse event and produces a Java InputEvent modifiers int which can be
- * passed to vt320.
- * @param mouseEvent The {@link MotionEvent} which should be a mouse click or release.
- * @return A Java InputEvent modifier int. See
- * http://docs.oracle.com/javase/7/docs/api/java/awt/event/InputEvent.html
- */
- @TargetApi(14)
- private static int mouseEventToJavaModifiers(MotionEvent mouseEvent) {
- if (MotionEventCompat.getSource(mouseEvent) != InputDevice.SOURCE_MOUSE) return 0;
-
- int mods = 0;
-
- // See http://docs.oracle.com/javase/7/docs/api/constant-values.html
- int buttonState = mouseEvent.getButtonState();
- if ((buttonState & MotionEvent.BUTTON_PRIMARY) != 0)
- mods |= 16;
- if ((buttonState & MotionEvent.BUTTON_SECONDARY) != 0)
- mods |= 8;
- if ((buttonState & MotionEvent.BUTTON_TERTIARY) != 0)
- mods |= 4;
-
- // Note: Meta and Ctrl are intentionally swapped here to keep logic in vt320 simple.
- int meta = mouseEvent.getMetaState();
- if ((meta & KeyEvent.META_META_ON) != 0)
- mods |= 2;
- if ((meta & KeyEvent.META_SHIFT_ON) != 0)
- mods |= 1;
- if ((meta & KeyEvent.META_CTRL_ON) != 0)
- mods |= 4;
-
- return mods;
+ });
+ }
}
/**
@@ -1011,19 +830,21 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
}
});
- copy = menu.add(R.string.console_menu_copy);
- if (hardKeyboard)
- copy.setAlphabeticShortcut('c');
- MenuItemCompat.setShowAsAction(copy, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
- copy.setIcon(R.drawable.ic_action_copy);
- copy.setEnabled(activeTerminal);
- copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- public boolean onMenuItemClick(MenuItem item) {
- startCopyMode();
- Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show();
- return true;
- }
- });
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ copy = menu.add(R.string.console_menu_copy);
+ if (hardKeyboard)
+ copy.setAlphabeticShortcut('c');
+ MenuItemCompat.setShowAsAction(copy, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
+ copy.setIcon(R.drawable.ic_action_copy);
+ copy.setEnabled(activeTerminal);
+ copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ startCopyMode();
+ Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show();
+ return true;
+ }
+ });
+ }
paste = menu.add(R.string.console_menu_paste);
if (hardKeyboard)
@@ -1144,7 +965,10 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
disconnect.setTitle(R.string.list_host_disconnect);
else
disconnect.setTitle(R.string.console_menu_close);
- copy.setEnabled(activeTerminal);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ copy.setEnabled(activeTerminal);
+ }
paste.setEnabled(clipboard.hasText() && sessionOpen);
portForward.setEnabled(sessionOpen && canForwardPorts);
urlscan.setEnabled(activeTerminal);
@@ -1174,32 +998,6 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
}
@Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
- final TerminalView view = adapter.getCurrentTerminalView();
- boolean activeTerminal = view != null;
- boolean sessionOpen = false;
-
- if (activeTerminal) {
- TerminalBridge bridge = view.bridge;
- sessionOpen = bridge.isSessionOpen();
- }
-
- MenuItem paste = menu.add(R.string.console_menu_paste);
- if (hardKeyboard)
- paste.setAlphabeticShortcut('v');
- paste.setIcon(android.R.drawable.ic_menu_edit);
- paste.setEnabled(clipboard.hasText() && sessionOpen);
- paste.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- public boolean onMenuItemClick(MenuItem item) {
- pasteIntoTerminal();
- return true;
- }
- });
-
-
- }
-
- @Override
public void onStart() {
super.onStart();
@@ -1308,6 +1106,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
super.onSaveInstanceState(savedInstanceState);
}
+ /**
+ * Only intended for pre-Honeycomb devices.
+ */
private void startCopyMode() {
// mark as copying and reset any previous bounds
TerminalView terminalView = (TerminalView) adapter.getCurrentTerminalView();
@@ -1494,7 +1295,7 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
overlay.setText(bridge.host.getNickname());
// and add our terminal view control, using index to place behind overlay
- final TerminalView terminal = new TerminalView(container.getContext(), bridge);
+ final TerminalView terminal = new TerminalView(container.getContext(), bridge, pager);
terminal.setId(R.id.terminal_view);
view.addView(terminal, 0);
@@ -1572,7 +1373,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
public TerminalView getCurrentTerminalView() {
View currentView = pager.findViewWithTag(getBridgeAtPosition(pager.getCurrentItem()));
- if (currentView == null) return null;
+ if (currentView == null) {
+ return null;
+ }
return (TerminalView) currentView.findViewById(R.id.terminal_view);
}
}
diff --git a/app/src/main/java/org/connectbot/TerminalView.java b/app/src/main/java/org/connectbot/TerminalView.java
index 7c4f51f..6d051f6 100644
--- a/app/src/main/java/org/connectbot/TerminalView.java
+++ b/app/src/main/java/org/connectbot/TerminalView.java
@@ -27,6 +27,7 @@ import org.connectbot.service.TerminalBridge;
import org.connectbot.service.TerminalKeyListener;
import android.annotation.TargetApi;
+import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -38,11 +39,19 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelXorXfermode;
import android.graphics.RectF;
+import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewPager;
+import android.text.ClipboardManager;
+import android.view.ActionMode;
+import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
@@ -51,6 +60,7 @@ import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
+import android.widget.TextView;
import android.widget.Toast;
import de.mud.terminal.VDUBuffer;
import de.mud.terminal.vt320;
@@ -62,10 +72,18 @@ import de.mud.terminal.vt320;
*
* @author jsharkey
*/
-public class TerminalView extends View implements FontSizeChangedListener {
+public class TerminalView extends TextView implements FontSizeChangedListener {
private final Context context;
public final TerminalBridge bridge;
+
+ private final ViewPager viewPager;
+ private GestureDetector gestureDetector;
+
+ private ClipboardManager clipboard;
+ private ActionMode selectionActionMode = null;
+ private String currentSelection = "";
+
private final Paint paint;
private final Paint cursorPaint;
private final Paint cursorStrokePaint;
@@ -96,17 +114,19 @@ public class TerminalView extends View implements FontSizeChangedListener {
private static final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService";
private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN";
- public TerminalView(Context context, TerminalBridge bridge) {
+ public TerminalView(Context context, TerminalBridge bridge, ViewPager pager) {
super(context);
this.context = context;
this.bridge = bridge;
- paint = new Paint();
+ this.viewPager = pager;
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
setFocusable(true);
setFocusableInTouchMode(true);
+ paint = new Paint();
+
cursorPaint = new Paint();
cursorPaint.setColor(bridge.color[bridge.defaultFg]);
cursorPaint.setXfermode(new PixelXorXfermode(bridge.color[bridge.defaultBg]));
@@ -142,6 +162,7 @@ public class TerminalView extends View implements FontSizeChangedListener {
scaleMatrix = new Matrix();
bridge.addFontSizeChangedListener(this);
+ bridge.terminalView = this;
// connect our view up to the bridge
setOnKeyListener(bridge.getKeyHandler());
@@ -150,6 +171,261 @@ public class TerminalView extends View implements FontSizeChangedListener {
// Enable accessibility features if a screen reader is active.
new AccessibilityStateTester().execute((Void) null);
+
+ clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+
+ setTextColor(0x00000000);
+ setTypeface(Typeface.MONOSPACE);
+ onFontSizeChanged(bridge.getFontSize());
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ setTextIsSelectable(true);
+
+ this.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
+ private static final int PASTE = 0;
+
+ @Override
+ @SuppressLint("NewApi")
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ TerminalView.this.selectionActionMode = mode;
+
+ menu.add(0, PASTE, 2, "Paste")
+ .setIcon(R.drawable.ic_action_paste)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT | MenuItem.SHOW_AS_ACTION_ALWAYS);
+
+ return true;
+ }
+
+ @Override
+ @SuppressLint("NewApi")
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ if (item.getItemId() == PASTE) {
+ String clip = clipboard.getText().toString();
+ TerminalView.this.bridge.injectString(clip);
+ mode.finish();
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ }
+ });
+
+ gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
+ private TerminalBridge bridge = TerminalView.this.bridge;
+ private float totalY = 0;
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ // if releasing then reset total scroll
+ if (e2.getAction() == MotionEvent.ACTION_UP) {
+ totalY = 0;
+ }
+
+ totalY += distanceY;
+ final int moved = (int) (totalY / bridge.charHeight);
+
+ if (moved != 0) {
+ int base = bridge.buffer.getWindowBase();
+ bridge.buffer.setWindowBase(base + moved);
+ totalY = 0;
+
+ copyBufferToText();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ viewPager.performClick();
+ return super.onSingleTapConfirmed(e);
+ }
+ });
+ }
+ }
+
+ public void copyCurrentSelectionToClipboard() {
+ ClipboardManager clipboard =
+ (ClipboardManager) TerminalView.this.context.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (currentSelection.length() != 0) {
+ clipboard.setText(currentSelection);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB &&
+ selectionActionMode != null) {
+ selectionActionMode.finish();
+ selectionActionMode = null;
+ }
+ }
+
+ @Override
+ protected void onSelectionChanged(int selStart, int selEnd) {
+ currentSelection = getText().toString().substring(selStart, selEnd);
+ super.onSelectionChanged(selStart, selEnd);
+ }
+
+ @Override
+ public boolean performLongClick() {
+ copyBufferToText();
+ return super.performLongClick();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ return false;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
+ MotionEventCompat.getSource(event) == InputDevice.SOURCE_MOUSE) {
+ if (onMouseEvent(event, bridge)) {
+ return true;
+ }
+ }
+
+ super.onTouchEvent(event);
+ if (gestureDetector != null) {
+ gestureDetector.onTouchEvent(event);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param event
+ * @param bridge
+ * @return True if the event is handled.
+ */
+ @TargetApi(14)
+ private boolean onMouseEvent(MotionEvent event, TerminalBridge bridge) {
+ int row = (int) Math.floor(event.getY() / bridge.charHeight);
+ int col = (int) Math.floor(event.getX() / bridge.charWidth);
+ int meta = event.getMetaState();
+ boolean shiftOn = (meta & KeyEvent.META_SHIFT_ON) != 0;
+ boolean mouseReport = ((vt320) bridge.buffer).isMouseReportEnabled();
+
+ // MouseReport can be "defeated" using the shift key.
+ if ((!mouseReport || shiftOn)) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ switch (event.getButtonState()) {
+ case MotionEvent.BUTTON_TERTIARY:
+ // Middle click pastes.
+ String clip = clipboard.getText().toString();
+ bridge.injectString(clip);
+ return true;
+ }
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ ((vt320) bridge.buffer).mousePressed(
+ col, row, mouseEventToJavaModifiers(event));
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ ((vt320) bridge.buffer).mouseReleased(col, row);
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ int buttonState = event.getButtonState();
+ int button = (buttonState & MotionEvent.BUTTON_PRIMARY) != 0 ? 0 :
+ (buttonState & MotionEvent.BUTTON_SECONDARY) != 0 ? 1 :
+ (buttonState & MotionEvent.BUTTON_TERTIARY) != 0 ? 2 : 3;
+ ((vt320) bridge.buffer).mouseMoved(
+ button,
+ col,
+ row,
+ (meta & KeyEvent.META_CTRL_ON) != 0,
+ (meta & KeyEvent.META_SHIFT_ON) != 0,
+ (meta & KeyEvent.META_META_ON) != 0);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Takes an android mouse event and produces a Java InputEvent modifiers int which can be
+ * passed to vt320.
+ * @param mouseEvent The {@link MotionEvent} which should be a mouse click or release.
+ * @return A Java InputEvent modifier int. See
+ * http://docs.oracle.com/javase/7/docs/api/java/awt/event/InputEvent.html
+ */
+ @TargetApi(14)
+ private static int mouseEventToJavaModifiers(MotionEvent mouseEvent) {
+ if (MotionEventCompat.getSource(mouseEvent) != InputDevice.SOURCE_MOUSE) return 0;
+
+ int mods = 0;
+
+ // See http://docs.oracle.com/javase/7/docs/api/constant-values.html
+ int buttonState = mouseEvent.getButtonState();
+ if ((buttonState & MotionEvent.BUTTON_PRIMARY) != 0)
+ mods |= 16;
+ if ((buttonState & MotionEvent.BUTTON_SECONDARY) != 0)
+ mods |= 8;
+ if ((buttonState & MotionEvent.BUTTON_TERTIARY) != 0)
+ mods |= 4;
+
+ // Note: Meta and Ctrl are intentionally swapped here to keep logic in vt320 simple.
+ int meta = mouseEvent.getMetaState();
+ if ((meta & KeyEvent.META_META_ON) != 0)
+ mods |= 2;
+ if ((meta & KeyEvent.META_SHIFT_ON) != 0)
+ mods |= 1;
+ if ((meta & KeyEvent.META_CTRL_ON) != 0)
+ mods |= 4;
+
+ return mods;
+ }
+
+ @Override
+ @TargetApi(12)
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if ((MotionEventCompat.getSource(event) & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_SCROLL:
+ // Process scroll wheel movement:
+ float yDistance = MotionEventCompat.getAxisValue(event, MotionEvent.AXIS_VSCROLL);
+ if (yDistance != 0) {
+ int base = bridge.buffer.getWindowBase();
+ bridge.buffer.setWindowBase(base - Math.round(yDistance));
+ return true;
+ }
+ }
+ }
+ return super.onGenericMotionEvent(event);
+ }
+
+ private void copyBufferToText() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ // It is pointless to run this function because the textView is not selectable pre-Honeycomb.
+ return;
+ }
+
+ VDUBuffer vb = bridge.getVDUBuffer();
+
+ String line = "";
+ String buffer = "";
+
+ int windowBase = vb.getWindowBase();
+ int rowBegin = vb.getTopMargin();
+ int rowEnd = vb.getBottomMargin();
+ int numCols = vb.getColumns() - 1;
+
+ for (int r = rowBegin; r <= rowEnd; r++) {
+ for (int c = 0; c < numCols; c++) {
+ line += vb.charArray[windowBase + r][c];
+ }
+ buffer += line.replaceAll("\\s+$", "") + "\n";
+ line = "";
+ }
+
+ setText(buffer);
}
public void destroy() {
@@ -168,6 +444,9 @@ public class TerminalView extends View implements FontSizeChangedListener {
public void onFontSizeChanged(float size) {
scaleCursors();
+ setTextSize(size);
+ setLineSpacing(0.0f, 1.1f); // KLUDGE: doesnt work on certain font sizes
+ copyBufferToText();
}
private void scaleCursors() {
@@ -246,19 +525,22 @@ public class TerminalView extends View implements FontSizeChangedListener {
}
// draw any highlighted area
- if (bridge.isSelectingForCopy()) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
+ bridge.isSelectingForCopy()) {
SelectionArea area = bridge.getSelectionArea();
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(
- area.getLeft() * bridge.charWidth,
- area.getTop() * bridge.charHeight,
- (area.getRight() + 1) * bridge.charWidth,
- (area.getBottom() + 1) * bridge.charHeight
+ area.getLeft() * bridge.charWidth,
+ area.getTop() * bridge.charHeight,
+ (area.getRight() + 1) * bridge.charWidth,
+ (area.getBottom() + 1) * bridge.charHeight
);
canvas.drawPaint(cursorPaint);
canvas.restore();
}
}
+
+ super.onDraw(canvas);
}
public void notifyUser(String message) {
@@ -324,37 +606,6 @@ public class TerminalView extends View implements FontSizeChangedListener {
};
}
- @Override
- @TargetApi(12)
- public boolean onGenericMotionEvent(MotionEvent event) {
- if ((MotionEventCompat.getSource(event) & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_SCROLL:
- // Process scroll wheel movement:
- float yDistance = MotionEventCompat.getAxisValue(event, MotionEvent.AXIS_VSCROLL);
- boolean mouseReport = ((vt320) bridge.buffer).isMouseReportEnabled();
- if (mouseReport) {
- int row = (int) Math.floor(event.getY() / bridge.charHeight);
- int col = (int) Math.floor(event.getX() / bridge.charWidth);
-
- ((vt320) bridge.buffer).mouseWheel(
- yDistance > 0,
- col,
- row,
- (event.getMetaState() & KeyEvent.META_CTRL_ON) != 0,
- (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0,
- (event.getMetaState() & KeyEvent.META_META_ON) != 0);
- return true;
- } else if (yDistance != 0) {
- int base = bridge.buffer.getWindowBase();
- bridge.buffer.setWindowBase(base - Math.round(yDistance));
- return true;
- }
- }
- }
- return super.onGenericMotionEvent(event);
- }
-
public void propagateConsoleText(char[] rawText, int length) {
if (mAccessibilityActive) {
synchronized (mAccessibilityLock) {
diff --git a/app/src/main/java/org/connectbot/service/TerminalBridge.java b/app/src/main/java/org/connectbot/service/TerminalBridge.java
index b9e29e8..b532a20 100644
--- a/app/src/main/java/org/connectbot/service/TerminalBridge.java
+++ b/app/src/main/java/org/connectbot/service/TerminalBridge.java
@@ -71,6 +71,7 @@ public class TerminalBridge implements VDUDisplay {
public int defaultBg = HostDatabase.DEFAULT_BG_COLOR;
protected final TerminalManager manager;
+ public TerminalView terminalView;
public HostBean host;
@@ -341,6 +342,33 @@ public class TerminalBridge implements VDUDisplay {
}
/**
+ * Only intended for pre-Honeycomb devices.
+ */
+ public void setSelectingForCopy(boolean selectingForCopy) {
+ this.selectingForCopy = selectingForCopy;
+ }
+
+ /**
+ * Only intended for pre-Honeycomb devices.
+ */
+ public boolean isSelectingForCopy() {
+ return selectingForCopy;
+ }
+
+ /**
+ * Only intended for pre-Honeycomb devices.
+ */
+ public SelectionArea getSelectionArea() {
+ return selectionArea;
+ }
+
+ public void copyCurrentSelection() {
+ if (terminalView != null) {
+ terminalView.copyCurrentSelectionToClipboard();
+ }
+ }
+
+ /**
* Inject a specific string into this terminal. Used for post-login strings
* and pasting clipboard.
*/
@@ -482,18 +510,6 @@ public class TerminalBridge implements VDUDisplay {
}
}
- public void setSelectingForCopy(boolean selectingForCopy) {
- this.selectingForCopy = selectingForCopy;
- }
-
- public boolean isSelectingForCopy() {
- return selectingForCopy;
- }
-
- public SelectionArea getSelectionArea() {
- return selectionArea;
- }
-
public synchronized void tryKeyVibrate() {
manager.tryKeyVibrate();
}
@@ -538,6 +554,10 @@ public class TerminalBridge implements VDUDisplay {
forcedSize = false;
}
+ public float getFontSize() {
+ return fontSizeDp;
+ }
+
/**
* Add an {@link FontSizeChangedListener} to the list of listeners for this
* bridge.
diff --git a/app/src/main/java/org/connectbot/service/TerminalKeyListener.java b/app/src/main/java/org/connectbot/service/TerminalKeyListener.java
index 1b2ffe4..753fa86 100644
--- a/app/src/main/java/org/connectbot/service/TerminalKeyListener.java
+++ b/app/src/main/java/org/connectbot/service/TerminalKeyListener.java
@@ -299,6 +299,14 @@ public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceCha
return true;
}
+ // CTRL-SHIFT-C to copy.
+ if (keyCode == KeyEvent.KEYCODE_C
+ && (derivedMetaState & HC_META_CTRL_ON) != 0
+ && (derivedMetaState & KeyEvent.META_SHIFT_ON) != 0) {
+ bridge.copyCurrentSelection();
+ return true;
+ }
+
// CTRL-SHIFT-V to paste.
if (keyCode == KeyEvent.KEYCODE_V
&& (derivedMetaState & HC_META_CTRL_ON) != 0