From f93f415b77fd777bccbac6c73f2aecfe01464fb8 Mon Sep 17 00:00:00 2001 From: Ryan Hansberry Date: Fri, 2 Oct 2015 17:50:01 -0700 Subject: Text selection now uses TextView. Pre-Honeycomb uses the same method as before. --- .../main/java/org/connectbot/ConsoleActivity.java | 363 +++++---------------- 1 file changed, 83 insertions(+), 280 deletions(-) (limited to 'app/src/main/java/org/connectbot/ConsoleActivity.java') 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); @@ -1173,32 +997,6 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne setVolumeControlStream(AudioManager.STREAM_MUSIC); } - @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); } } -- cgit v1.2.3