aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Hansberry <rhansby@gmail.com>2015-10-09 14:05:17 -0700
committerRyan Hansberry <rhansby@gmail.com>2015-10-14 15:47:14 -0700
commit381c4b65027c62fa87db6038fe6de5f4e46fe990 (patch)
treefe668961f6d4c6128abc221263ab6b0f92e4ca37
parent34cb0aac6364ef744e1326e054d292ecde1d7096 (diff)
downloadconnectbot-381c4b65027c62fa87db6038fe6de5f4e46fe990.tar.gz
connectbot-381c4b65027c62fa87db6038fe6de5f4e46fe990.tar.bz2
connectbot-381c4b65027c62fa87db6038fe6de5f4e46fe990.zip
TerminalView: Move TextView logic that allows text selection into its own class, which allows scrolling selection.
-rw-r--r--app/src/main/java/org/connectbot/ConsoleActivity.java21
-rw-r--r--app/src/main/java/org/connectbot/TerminalView.java332
-rw-r--r--app/src/main/java/org/connectbot/util/TerminalTextViewOverlay.java368
-rw-r--r--app/src/main/res/layout-v11/item_terminal.xml47
-rw-r--r--app/src/main/res/layout/item_terminal.xml2
5 files changed, 462 insertions, 308 deletions
diff --git a/app/src/main/java/org/connectbot/ConsoleActivity.java b/app/src/main/java/org/connectbot/ConsoleActivity.java
index abfbee4..10ae45a 100644
--- a/app/src/main/java/org/connectbot/ConsoleActivity.java
+++ b/app/src/main/java/org/connectbot/ConsoleActivity.java
@@ -28,6 +28,7 @@ import org.connectbot.service.TerminalBridge;
import org.connectbot.service.TerminalKeyListener;
import org.connectbot.service.TerminalManager;
import org.connectbot.util.PreferenceConstants;
+import org.connectbot.util.TerminalTextViewOverlay;
import org.connectbot.util.TerminalViewPager;
import android.app.AlertDialog;
@@ -1148,9 +1149,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
* Called whenever the displayed terminal is changed.
*/
private void onTerminalChanged() {
- View overlay = findCurrentView(R.id.terminal_overlay);
- if (overlay != null)
- overlay.startAnimation(fade_out_delayed);
+ View terminalNameOverlay = findCurrentView(R.id.terminal_name_overlay);
+ if (terminalNameOverlay != null)
+ terminalNameOverlay.startAnimation(fade_out_delayed);
updateDefault();
updatePromptVisible();
ActivityCompat.invalidateOptionsMenu(ConsoleActivity.this);
@@ -1203,12 +1204,16 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
RelativeLayout view = (RelativeLayout) inflater.inflate(
R.layout.item_terminal, container, false);
- // set the terminal overlay text
- TextView overlay = (TextView) view.findViewById(R.id.terminal_overlay);
- overlay.setText(bridge.host.getNickname());
+ // set the terminal name overlay text
+ TextView terminalNameOverlay = (TextView) view.findViewById(R.id.terminal_name_overlay);
+ terminalNameOverlay.setText(bridge.host.getNickname());
+
+ TerminalTextViewOverlay terminalTextViewOverlay =
+ (TerminalTextViewOverlay) view.findViewById(R.id.terminal_text_overlay);
// and add our terminal view control, using index to place behind overlay
- final TerminalView terminal = new TerminalView(container.getContext(), bridge, pager);
+ final TerminalView terminal = new TerminalView(
+ container.getContext(), bridge, terminalTextViewOverlay, pager);
terminal.setId(R.id.terminal_view);
view.addView(terminal, 0);
@@ -1216,7 +1221,7 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne
view.setTag(bridge);
container.addView(view);
- overlay.startAnimation(fade_out_delayed);
+ terminalNameOverlay.startAnimation(fade_out_delayed);
return view;
}
diff --git a/app/src/main/java/org/connectbot/TerminalView.java b/app/src/main/java/org/connectbot/TerminalView.java
index 44b5810..578dcd7 100644
--- a/app/src/main/java/org/connectbot/TerminalView.java
+++ b/app/src/main/java/org/connectbot/TerminalView.java
@@ -26,6 +26,7 @@ import org.connectbot.service.FontSizeChangedListener;
import org.connectbot.service.TerminalBridge;
import org.connectbot.service.TerminalKeyListener;
import org.connectbot.util.PreferenceConstants;
+import org.connectbot.util.TerminalTextViewOverlay;
import org.connectbot.util.TerminalViewPager;
import android.annotation.TargetApi;
@@ -37,25 +38,18 @@ import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Matrix;
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.preference.PreferenceManager;
-import android.support.v4.view.MotionEventCompat;
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.ViewConfiguration;
@@ -65,37 +59,29 @@ 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;
/**
- * User interface {@link TextView} for showing a TerminalBridge in an
+ * User interface {@link View} for showing a TerminalBridge in an
* {@link android.app.Activity}. Handles drawing bitmap updates and passing keystrokes down
* to terminal.
*
- * On Honeycomb devices and above (>= APIv11), a TextView with transparent text (which is identical
- * to the bitmap) is drawn above the bitmap. This TextView exists to allow the user to
- * select and copy text.
- *
* @author jsharkey
*/
-public class TerminalView extends TextView implements FontSizeChangedListener {
-
+public class TerminalView extends View implements FontSizeChangedListener {
private final Context context;
public final TerminalBridge bridge;
- private final TerminalViewPager viewPager;
+ private TerminalTextViewOverlay terminalTextViewOverlay;
+ public final TerminalViewPager viewPager;
private GestureDetector gestureDetector;
private SharedPreferences prefs;
- private ClipboardManager clipboard;
- private ActionMode selectionActionMode = null;
- private String currentSelection = "";
-
// These are only used for pre-Honeycomb copying.
private int lastTouchedRow, lastTouchedCol;
+ private final ClipboardManager clipboard;
private final Paint paint;
private final Paint cursorPaint;
@@ -127,11 +113,12 @@ public class TerminalView extends TextView 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, TerminalViewPager pager) {
+ public TerminalView(Context context, TerminalBridge bridge, final TerminalTextViewOverlay terminalTextViewOverlay, TerminalViewPager pager) {
super(context);
this.context = context;
this.bridge = bridge;
+ this.terminalTextViewOverlay = terminalTextViewOverlay;
this.viewPager = pager;
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
@@ -188,6 +175,13 @@ public class TerminalView extends TextView implements FontSizeChangedListener {
// connect our view up to the bridge
setOnKeyListener(bridge.getKeyHandler());
+ if (terminalTextViewOverlay != null) {
+ terminalTextViewOverlay.parent = this;
+
+ // Once terminalTextViewOverlay is active, allow it to handle key events instead.
+ terminalTextViewOverlay.setOnKeyListener(bridge.getKeyHandler());
+ }
+
mAccessibilityBuffer = new StringBuffer();
// Enable accessibility features if a screen reader is active.
@@ -196,25 +190,19 @@ public class TerminalView extends TextView implements FontSizeChangedListener {
clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
prefs = PreferenceManager.getDefaultSharedPreferences(context);
- setTextColor(Color.TRANSPARENT);
- setTypeface(Typeface.MONOSPACE);
onFontSizeChanged(bridge.getFontSize());
- // Allow selection of and copying text for Honeycomb and above devices.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- setTextIsSelectable(true);
- setCustomSelectionActionModeCallback(new TextSelectionActionModeCallback());
- }
-
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
+ // Only used for pre-Honeycomb devices.
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;
+ // The terminalTextViewOverlay handles scrolling. Only handle scrolling if it
+ // is not available (i.e. on pre-Honeycomb devices).
+ if (terminalTextViewOverlay != null) {
+ return false;
}
// activate consider if within x tolerance
@@ -264,55 +252,18 @@ public class TerminalView extends TextView implements FontSizeChangedListener {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
- @TargetApi(11)
- private void closeSelectionActionMode() {
- if (selectionActionMode != null) {
- selectionActionMode.finish();
- selectionActionMode = null;
- }
- }
-
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) {
- closeSelectionActionMode();
- }
- }
-
- @Override
- protected void onSelectionChanged(int selStart, int selEnd) {
- if (selStart <= selEnd) {
- currentSelection = getText().toString().substring(selStart, selEnd);
- }
- super.onSelectionChanged(selStart, selEnd);
+ terminalTextViewOverlay.copyCurrentSelectionToClipboard();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- // Selection may be beginning. Sync the TextView with the buffer.
- refreshTextFromBuffer();
- }
-
- // Mouse input is treated differently:
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
- MotionEventCompat.getSource(event) == InputDevice.SOURCE_MOUSE) {
- if (onMouseEvent(event, bridge)) {
- return true;
- }
- viewPager.setPagingEnabled(true);
- } else if (gestureDetector != null) {
- // The gesture detector should not be called if touch event was from mouse.
+ if (gestureDetector != null) {
gestureDetector.onTouchEvent(event);
}
// Old version of copying, only for pre-Honeycomb.
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ if (terminalTextViewOverlay == null) {
// when copying, highlight the area
if (bridge.isSelectingForCopy()) {
SelectionArea area = bridge.getSelectionArea();
@@ -386,224 +337,6 @@ public class TerminalView extends TextView implements FontSizeChangedListener {
return true;
}
- @TargetApi(11)
- private class TextSelectionActionModeCallback implements ActionMode.Callback {
- private static final int COPY = 0;
- private static final int PASTE = 1;
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- TerminalView.this.selectionActionMode = mode;
-
- menu.clear();
-
- menu.add(0, COPY, 0, R.string.console_menu_copy)
- .setIcon(R.drawable.ic_action_copy)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT | MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(0, PASTE, 1, R.string.console_menu_paste)
- .setIcon(R.drawable.ic_action_paste)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT | MenuItem.SHOW_AS_ACTION_IF_ROOM);
-
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch (item.getItemId()) {
- case COPY:
- copyCurrentSelectionToClipboard();
- return true;
- case PASTE:
- String clip = "";
- if (clipboard.hasText()) {
- clip = clipboard.getText().toString();
- }
- TerminalView.this.bridge.injectString(clip);
- mode.finish();
- return true;
- }
-
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- }
- }
-
- /**
- * @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;
- vt320 vtBuffer = (vt320) bridge.buffer;
- boolean mouseReport = vtBuffer.isMouseReportEnabled();
-
- // MouseReport can be "defeated" using the shift key.
- if (!mouseReport || shiftOn) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- if (event.getButtonState() == MotionEvent.BUTTON_TERTIARY) {
- // Middle click pastes.
- String clip = clipboard.getText().toString();
- bridge.injectString(clip);
- return true;
- }
-
- // Begin "selection mode"
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- closeSelectionActionMode();
- }
- } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
- // In the middle of selection.
-
- if (selectionActionMode == null) {
- selectionActionMode = startActionMode(new TextSelectionActionModeCallback());
- }
-
- int selectionStart = getSelectionStart();
- int selectionEnd = getSelectionEnd();
-
- if (selectionStart > selectionEnd) {
- int tempStart = selectionStart;
- selectionStart = selectionEnd;
- selectionEnd = tempStart;
- }
-
- currentSelection = getText().toString().substring(selectionStart, selectionEnd);
- }
- } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
- viewPager.setPagingEnabled(false);
- vtBuffer.mousePressed(
- col, row, mouseEventToJavaModifiers(event));
- return true;
- } else if (event.getAction() == MotionEvent.ACTION_UP) {
- viewPager.setPagingEnabled(true);
- vtBuffer.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;
- vtBuffer.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);
- vt320 vtBuffer = (vt320) bridge.buffer;
- boolean mouseReport = vtBuffer.isMouseReportEnabled();
- if (mouseReport) {
- int row = (int) Math.floor(event.getY() / bridge.charHeight);
- int col = (int) Math.floor(event.getX() / bridge.charWidth);
-
- vtBuffer.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);
- }
-
- // TODO: cleanup and possibly optimize
- private void refreshTextFromBuffer() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
- // Do not 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);
- }
-
/**
* Only intended for pre-Honeycomb devices.
*/
@@ -639,13 +372,15 @@ public class TerminalView extends TextView implements FontSizeChangedListener {
((Activity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
- setTextSize(size);
-
- // For the TextView to line up with the bitmap text, lineHeight must be equal to
- // the bridge's charHeight. See TextView.getLineHeight(), which has been reversed to
- // derive lineSpacingMultiplier.
- float lineSpacingMultiplier = (float) bridge.charHeight / getPaint().getFontMetricsInt(null);
- setLineSpacing(0.0f, lineSpacingMultiplier);
+ if (terminalTextViewOverlay != null) {
+ terminalTextViewOverlay.setTextSize(size);
+
+ // For the TextView to line up with the bitmap text, lineHeight must be equal to
+ // the bridge's charHeight. See TextView.getLineHeight(), which has been reversed to
+ // derive lineSpacingMultiplier.
+ float lineSpacingMultiplier = (float) bridge.charHeight / terminalTextViewOverlay.getPaint().getFontMetricsInt(null);
+ terminalTextViewOverlay.setLineSpacing(0.0f, lineSpacingMultiplier);
+ }
}
});
}
@@ -726,8 +461,7 @@ public class TerminalView extends TextView implements FontSizeChangedListener {
}
// draw any highlighted area
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
- bridge.isSelectingForCopy()) {
+ if (terminalTextViewOverlay == null && bridge.isSelectingForCopy()) {
SelectionArea area = bridge.getSelectionArea();
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(
diff --git a/app/src/main/java/org/connectbot/util/TerminalTextViewOverlay.java b/app/src/main/java/org/connectbot/util/TerminalTextViewOverlay.java
new file mode 100644
index 0000000..b64351b
--- /dev/null
+++ b/app/src/main/java/org/connectbot/util/TerminalTextViewOverlay.java
@@ -0,0 +1,368 @@
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2015 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.connectbot.util;
+
+import org.connectbot.R;
+import org.connectbot.TerminalView;
+import org.connectbot.service.TerminalBridge;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.v4.view.MotionEventCompat;
+import android.text.ClipboardManager;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.TextView;
+import de.mud.terminal.VDUBuffer;
+import de.mud.terminal.vt320;
+
+/**
+ * Custom TextView {@link TextView} which is intended to (invisibly) be on top of the TerminalView
+ * (@link TerminalView) in order to allow the user to select and copy the text of the bitmap below.
+ *
+ * @author rhansby
+ */
+@TargetApi(11)
+public class TerminalTextViewOverlay extends TextView {
+ public TerminalView parent;
+ private String currentSelection = "";
+ private ActionMode selectionActionMode;
+ private ClipboardManager clipboard;
+
+ private int oldScrollY = -1;
+
+ public TerminalTextViewOverlay(Context context) {
+ super(context);
+ init();
+ }
+
+ public TerminalTextViewOverlay(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ @TargetApi(21)
+ public TerminalTextViewOverlay(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ setTextColor(Color.TRANSPARENT);
+ setTypeface(Typeface.MONOSPACE);
+ setTextIsSelectable(true);
+ setCustomSelectionActionModeCallback(new TextSelectionActionModeCallback());
+
+ clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ }
+
+ // TODO: optimize: instead of always copying the entire buffer, instead append() as needed.
+ public void refreshTextFromBuffer() {
+ VDUBuffer vb = parent.bridge.getVDUBuffer();
+
+ String line = "";
+ String buffer = "";
+
+ int numRows = vb.getBufferSize();
+ int numCols = vb.getColumns() - 1;
+
+ for (int r = 0; r < numRows && vb.charArray[r] != null; r++) {
+ for (int c = 0; c < numCols; c++) {
+ line += vb.charArray[r][c];
+ }
+ buffer += line.replaceAll("\\s+$", "") + "\n";
+ line = "";
+ }
+
+ oldScrollY = vb.getWindowBase() * getLineHeight();
+
+ setText(buffer);
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ boolean superResult = super.onPreDraw();
+
+ if (oldScrollY >= 0) {
+ scrollTo(0, oldScrollY);
+ oldScrollY = -1;
+ }
+
+ return superResult;
+ }
+
+ private void closeSelectionActionMode() {
+ if (selectionActionMode != null) {
+ selectionActionMode.finish();
+ selectionActionMode = null;
+ }
+ }
+
+ public void copyCurrentSelectionToClipboard() {
+ if (currentSelection.length() != 0) {
+ clipboard.setText(currentSelection);
+ }
+ closeSelectionActionMode();
+ }
+
+ @Override
+ protected void onSelectionChanged(int selStart, int selEnd) {
+ if (selStart <= selEnd) {
+ currentSelection = getText().toString().substring(selStart, selEnd);
+ }
+ super.onSelectionChanged(selStart, selEnd);
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ int lineMultiple = y / getLineHeight();
+
+ TerminalBridge bridge = parent.bridge;
+ bridge.buffer.setWindowBase(lineMultiple);
+
+ super.scrollTo(0, lineMultiple * getLineHeight());
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ // Selection may be beginning. Sync the TextView with the buffer.
+ refreshTextFromBuffer();
+ }
+
+ // Mouse input is treated differently:
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
+ MotionEventCompat.getSource(event) == InputDevice.SOURCE_MOUSE) {
+ if (onMouseEvent(event, parent.bridge)) {
+ return true;
+ }
+ parent.viewPager.setPagingEnabled(true);
+ } else {
+ parent.onTouchEvent(event);
+ }
+
+ super.onTouchEvent(event);
+
+ return true;
+ }
+
+ @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);
+
+ vt320 vtBuffer = (vt320) parent.bridge.buffer;
+ boolean mouseReport = vtBuffer.isMouseReportEnabled();
+ if (mouseReport) {
+ int row = (int) Math.floor(event.getY() / parent.bridge.charHeight);
+ int col = (int) Math.floor(event.getX() / parent.bridge.charWidth);
+
+ vtBuffer.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;
+ }
+ }
+ }
+
+ return super.onGenericMotionEvent(event);
+ }
+
+ /**
+ * @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;
+ vt320 vtBuffer = (vt320) bridge.buffer;
+ boolean mouseReport = vtBuffer.isMouseReportEnabled();
+
+ // MouseReport can be "defeated" using the shift key.
+ if (!mouseReport || shiftOn) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (event.getButtonState() == MotionEvent.BUTTON_TERTIARY) {
+ // Middle click pastes.
+ String clip = clipboard.getText().toString();
+ bridge.injectString(clip);
+ return true;
+ }
+
+ // Begin "selection mode"
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ closeSelectionActionMode();
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ // In the middle of selection.
+
+ if (selectionActionMode == null) {
+ selectionActionMode = startActionMode(new TextSelectionActionModeCallback());
+ }
+
+ int selectionStart = getSelectionStart();
+ int selectionEnd = getSelectionEnd();
+
+ if (selectionStart > selectionEnd) {
+ int tempStart = selectionStart;
+ selectionStart = selectionEnd;
+ selectionEnd = tempStart;
+ }
+
+ currentSelection = getText().toString().substring(selectionStart, selectionEnd);
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ parent.viewPager.setPagingEnabled(false);
+ vtBuffer.mousePressed(
+ col, row, mouseEventToJavaModifiers(event));
+ return true;
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
+ parent.viewPager.setPagingEnabled(true);
+ vtBuffer.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;
+ vtBuffer.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
+ public boolean onCheckIsTextEditor() {
+ return true;
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return parent.onCreateInputConnection(outAttrs);
+ }
+
+ private class TextSelectionActionModeCallback implements ActionMode.Callback {
+ private static final int COPY = 0;
+ private static final int PASTE = 1;
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ TerminalTextViewOverlay.this.selectionActionMode = mode;
+
+ menu.clear();
+
+ menu.add(0, COPY, 0, R.string.console_menu_copy)
+ .setIcon(R.drawable.ic_action_copy)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT | MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(0, PASTE, 1, R.string.console_menu_paste)
+ .setIcon(R.drawable.ic_action_paste)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT | MenuItem.SHOW_AS_ACTION_IF_ROOM);
+
+ return true;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ switch (item.getItemId()) {
+ case COPY:
+ copyCurrentSelectionToClipboard();
+ return true;
+ case PASTE:
+ String clip = clipboard.getText().toString();
+ TerminalTextViewOverlay.this.parent.bridge.injectString(clip);
+ mode.finish();
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ }
+ }
+}
diff --git a/app/src/main/res/layout-v11/item_terminal.xml b/app/src/main/res/layout-v11/item_terminal.xml
new file mode 100644
index 0000000..6817554
--- /dev/null
+++ b/app/src/main/res/layout-v11/item_terminal.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+
+ <TextView
+ android:id="@+id/terminal_name_overlay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:background="#aa000000"
+ android:padding="10dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
+
+ <org.connectbot.util.TerminalTextViewOverlay
+ android:id="@+id/terminal_text_overlay"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_centerInParent="true"
+ android:background="@android:color/transparent"
+ android:isScrollContainer="false"
+ android:ellipsize="end"
+ />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/item_terminal.xml b/app/src/main/res/layout/item_terminal.xml
index b34c800..e79325d 100644
--- a/app/src/main/res/layout/item_terminal.xml
+++ b/app/src/main/res/layout/item_terminal.xml
@@ -25,7 +25,7 @@
>
<TextView
- android:id="@+id/terminal_overlay"
+ android:id="@+id/terminal_name_overlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"