diff options
author | Kenny Root <kenny@the-b.org> | 2009-01-21 06:50:10 +0000 |
---|---|---|
committer | Kenny Root <kenny@the-b.org> | 2009-01-21 06:50:10 +0000 |
commit | c8a5ca406a7c6776f6becbb62ab3edfdbf5adcf8 (patch) | |
tree | a5699fa3d3264eb5e27e09d99312cad8c8948898 /src | |
parent | 5be990cb0339abf90f35afe83f73726e801ebf7f (diff) | |
download | connectbot-c8a5ca406a7c6776f6becbb62ab3edfdbf5adcf8.tar.gz connectbot-c8a5ca406a7c6776f6becbb62ab3edfdbf5adcf8.tar.bz2 connectbot-c8a5ca406a7c6776f6becbb62ab3edfdbf5adcf8.zip |
Allow use of directional pad to select area of text to copy to clipboard
Diffstat (limited to 'src')
-rw-r--r-- | src/org/connectbot/ConsoleActivity.java | 87 | ||||
-rw-r--r-- | src/org/connectbot/TerminalView.java | 22 | ||||
-rw-r--r-- | src/org/connectbot/bean/SelectionArea.java | 190 | ||||
-rw-r--r-- | src/org/connectbot/service/TerminalBridge.java | 107 |
4 files changed, 329 insertions, 77 deletions
diff --git a/src/org/connectbot/ConsoleActivity.java b/src/org/connectbot/ConsoleActivity.java index 00b1543..c0b045e 100644 --- a/src/org/connectbot/ConsoleActivity.java +++ b/src/org/connectbot/ConsoleActivity.java @@ -20,6 +20,7 @@ package org.connectbot; import org.connectbot.bean.HostBean; import org.connectbot.bean.PortForwardBean; +import org.connectbot.bean.SelectionArea; import org.connectbot.service.PromptHelper; import org.connectbot.service.TerminalBridge; import org.connectbot.service.TerminalManager; @@ -97,8 +98,7 @@ public class ConsoleActivity extends Activity { private MenuItem disconnect, copy, paste, portForward, resize; - protected boolean copying = false; - protected TerminalView copySource = null; + protected TerminalBridge copySource = null; private ServiceConnection connection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { @@ -413,7 +413,8 @@ public class ConsoleActivity extends Activity { public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // if copying, then ignore - if(copying) return false; + if (copySource != null && copySource.isSelectingForCopy()) + return false; // if releasing then reset total scroll if(e2.getAction() == MotionEvent.ACTION_UP) { @@ -470,63 +471,46 @@ public class ConsoleActivity extends Activity { public boolean onTouch(View v, MotionEvent event) { // when copying, highlight the area - if(copying) { - if(copySource == null) return false; - float row = event.getY() / copySource.bridge.charHeight; - float col = event.getX() / copySource.bridge.charWidth; + if (copySource != null && copySource.isSelectingForCopy()) { + int row = (int)Math.floor(event.getY() / copySource.charHeight); + int col = (int)Math.floor(event.getX() / copySource.charWidth); + + SelectionArea area = copySource.getSelectionArea(); switch(event.getAction()) { case MotionEvent.ACTION_DOWN: // recording starting area - copySource.top = (int) Math.floor(row); - copySource.left = (int) Math.floor(col); + area.setTop(row); + area.setLeft(col); + copySource.redraw(); return false; case MotionEvent.ACTION_MOVE: // update selected area - copySource.bottom = (int) Math.ceil(row); - copySource.right = (int) Math.ceil(col); - copySource.invalidate(); + area.setBottom(row); + area.setRight(col); + copySource.redraw(); return false; case MotionEvent.ACTION_UP: - // copy selected area to clipboard - int adjust = 0; //copySource.bridge.buffer.windowBase - copySource.bridge.buffer.screenBase; - int top = Math.min(copySource.top, copySource.bottom) + adjust, - bottom = Math.max(copySource.top, copySource.bottom) + adjust, - left = Math.min(copySource.left, copySource.right), - right = Math.max(copySource.left, copySource.right); - - // perform actual buffer copy - int size = (right - left) * (bottom - top); - StringBuffer buffer = new StringBuffer(size); - for(int y = top; y < bottom; y++) { - int lastNonSpace = buffer.length(); - - for(int x = left; x < right; x++) { - // only copy printable chars - char c = copySource.bridge.buffer.getChar(x, y); - if(c < 32 || c >= 127) c = ' '; - if (c != ' ') - lastNonSpace = buffer.length(); - buffer.append(c); - } - - // Don't leave a bunch of spaces in our copy buffer. - if (buffer.length() > lastNonSpace) - buffer.delete(lastNonSpace + 1, buffer.length()); - - if (y != bottom) - buffer.append("\n"); + /* 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; } - clipboard.setText(buffer.toString()); - Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, buffer.length()), Toast.LENGTH_LONG).show(); + // 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 - copySource.resetSelected(); - copySource.invalidate(); - copying = false; + area.reset(); + copySource.setSelectingForCopy(false); + copySource.redraw(); return true; } @@ -579,9 +563,16 @@ public class ConsoleActivity extends Activity { copy.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { // mark as copying and reset any previous bounds - copying = true; - copySource = (TerminalView)view; - copySource.resetSelected(); + copySource = ((TerminalView)view).bridge; + + SelectionArea area = copySource.getSelectionArea(); + area.reset(); + area.setBounds(copySource.buffer.getColumns(), copySource.buffer.getRows()); + + copySource.setSelectingForCopy(true); + + // Make sure we show the initial selection + copySource.redraw(); Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show(); return true; diff --git a/src/org/connectbot/TerminalView.java b/src/org/connectbot/TerminalView.java index 5003190..3feebd5 100644 --- a/src/org/connectbot/TerminalView.java +++ b/src/org/connectbot/TerminalView.java @@ -18,6 +18,7 @@ package org.connectbot; +import org.connectbot.bean.SelectionArea; import org.connectbot.service.TerminalBridge; import android.app.Activity; @@ -45,15 +46,6 @@ public class TerminalView extends View { private Toast notification = null; private String lastNotification = null; - - protected int top = -1, bottom = -1, left = -1, right = -1; - - public void resetSelected() { - top = -1; - bottom = -1; - left = -1; - right = -1; - } public TerminalView(Context context, TerminalBridge bridge) { super(context); @@ -109,9 +101,15 @@ public class TerminalView extends View { } // draw any highlighted area - if(top >= 0 && bottom >= 0 && left >= 0 && right >= 0) { - canvas.drawRect(left * bridge.charWidth, top * bridge.charHeight, - right * bridge.charWidth, bottom * bridge.charHeight, cursorPaint); + if (bridge.isSelectingForCopy()) { + SelectionArea area = bridge.getSelectionArea(); + canvas.drawRect( + area.getLeft() * bridge.charWidth, + area.getTop() * bridge.charHeight, + (area.getRight() + 1) * bridge.charWidth, + (area.getBottom() + 1) * bridge.charHeight, + cursorPaint + ); } } } diff --git a/src/org/connectbot/bean/SelectionArea.java b/src/org/connectbot/bean/SelectionArea.java new file mode 100644 index 0000000..6f13193 --- /dev/null +++ b/src/org/connectbot/bean/SelectionArea.java @@ -0,0 +1,190 @@ +/* + ConnectBot: simple, powerful, open-source SSH client for Android + Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +package org.connectbot.bean; + +import de.mud.terminal.VDUBuffer; + +/** + * @author Kenny Root + * Keep track of a selection area for the terminal copying mechanism. + * If the orientation is flipped one way, swap the bottom and top or + * left and right to keep it in the correct orientation. + */ +public class SelectionArea { + private int top; + private int bottom; + private int left; + private int right; + private int maxColumns; + private int maxRows; + private boolean selectingOrigin; + + public SelectionArea() { + reset(); + } + + public void reset() { + top = left = bottom = right = 0; + selectingOrigin = true; + } + + /** + * @param columns + * @param rows + */ + public void setBounds(int columns, int rows) { + maxColumns = columns - 1; + maxRows = rows - 1; + } + + private int checkBounds(int value, int max) { + if (value < 0) + return 0; + else if (value > max) + return max; + else + return value; + } + + public boolean isSelectingOrigin() { + return selectingOrigin; + } + + public void finishSelectingOrigin() { + selectingOrigin = false; + } + + public void setTop(int top) { + this.top = bottom = checkBounds(top, maxRows); + } + + public void decrementTop() { + setTop(--top); + } + + public void incrementTop() { + setTop(++top); + } + + public int getTop() { + return Math.min(top, bottom); + } + + public void setBottom(int bottom) { + this.bottom = checkBounds(bottom, maxRows); + } + + public void decrementBottom() { + setBottom(--bottom); + } + + public void incrementBottom() { + setBottom(++bottom); + } + + public int getBottom() { + return Math.max(top, bottom); + } + + public void setLeft(int left) { + this.left = right = checkBounds(left, maxColumns); + } + + public void decrementLeft() { + setLeft(--left); + } + + public void incrementLeft() { + setLeft(++left); + } + + public int getLeft() { + return Math.min(left, right); + } + + public void setRight(int right) { + this.right = checkBounds(right, maxColumns); + } + + public void decrementRight() { + setRight(--right); + } + + public void incrementRight() { + setRight(++right); + } + + public int getRight() { + return Math.max(left, right); + } + + public String copyFrom(VDUBuffer vb) { + int size = (getRight() - getLeft() + 1) * (getBottom() - getTop() + 1); + + StringBuffer buffer = new StringBuffer(size); + + for(int y = getTop(); y <= getBottom(); y++) { + int lastNonSpace = buffer.length(); + + for (int x = getLeft(); x <= getRight(); x++) { + // only copy printable chars + char c = vb.getChar(x, y); + + if (!Character.isDefined(c) || + (Character.isISOControl(c) && c != '\t')) + c = ' '; + + if (c != ' ') + lastNonSpace = buffer.length(); + + buffer.append(c); + } + + // Don't leave a bunch of spaces in our copy buffer. + if (buffer.length() > lastNonSpace) + buffer.delete(lastNonSpace + 1, buffer.length()); + + if (y != bottom) + buffer.append("\n"); + } + + return buffer.toString(); + } + + public String toString() { + StringBuilder buffer = new StringBuilder(); + + buffer.append("SelectionArea[top="); + buffer.append(top); + buffer.append(", bottom="); + buffer.append(bottom); + buffer.append(", left="); + buffer.append(left); + buffer.append(", right="); + buffer.append(right); + buffer.append(", maxColumns="); + buffer.append(maxColumns); + buffer.append(", maxRows="); + buffer.append(maxRows); + buffer.append(", isSelectingOrigin="); + buffer.append(isSelectingOrigin()); + buffer.append("]"); + + return buffer.toString(); + } +} diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index 8024c88..2522caa 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -39,10 +39,12 @@ import org.connectbot.TerminalView; import org.connectbot.bean.HostBean; import org.connectbot.bean.PortForwardBean; import org.connectbot.bean.PubkeyBean; +import org.connectbot.bean.SelectionArea; import org.connectbot.util.HostDatabase; import org.connectbot.util.PubkeyDatabase; import org.connectbot.util.PubkeyUtils; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -50,13 +52,13 @@ import android.graphics.Paint; import android.graphics.Typeface; import android.graphics.Bitmap.Config; import android.graphics.Paint.FontMetricsInt; +import android.os.Vibrator; +import android.text.ClipboardManager; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; -import android.os.Vibrator; -import android.content.Context; import com.trilead.ssh2.ChannelCondition; import com.trilead.ssh2.Connection; @@ -148,6 +150,10 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal private int termHeight; private String keymode = null; + + private boolean selectingForCopy = false; + private SelectionArea selectionArea; + private ClipboardManager clipboard; protected KeyCharacterMap keymap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); @@ -285,6 +291,8 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal buffer.setBufferSize(scrollback); resetColors(); buffer.setDisplay(this); + + selectionArea = new SelectionArea(); // TODO Change this when hosts are beans as well portForwards = manager.hostdb.getPortForwardsForHost(host); @@ -940,32 +948,84 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal case KeyEvent.KEYCODE_DEL: stdin.write(0x08); return true; case KeyEvent.KEYCODE_ENTER: ((vt320)buffer).keyTyped(vt320.KEY_ENTER, ' ', event.getMetaState()); return true; case KeyEvent.KEYCODE_DPAD_LEFT: - ((vt320)buffer).keyPressed(vt320.KEY_LEFT, ' ', event.getMetaState()); - this.tryKeyVibrate(); + if (selectingForCopy) { + if (selectionArea.isSelectingOrigin()) + selectionArea.decrementLeft(); + else + selectionArea.decrementRight(); + redraw(); + } else { + ((vt320)buffer).keyPressed(vt320.KEY_LEFT, ' ', event.getMetaState()); + tryKeyVibrate(); + } return true; case KeyEvent.KEYCODE_DPAD_UP: - ((vt320)buffer).keyPressed(vt320.KEY_UP, ' ', event.getMetaState()); - this.tryKeyVibrate(); + if (selectingForCopy) { + if (selectionArea.isSelectingOrigin()) + selectionArea.decrementTop(); + else + selectionArea.decrementBottom(); + redraw(); + } else { + ((vt320)buffer).keyPressed(vt320.KEY_UP, ' ', event.getMetaState()); + tryKeyVibrate(); + } return true; case KeyEvent.KEYCODE_DPAD_DOWN: - ((vt320)buffer).keyPressed(vt320.KEY_DOWN, ' ', event.getMetaState()); - this.tryKeyVibrate(); + if (selectingForCopy) { + if (selectionArea.isSelectingOrigin()) + selectionArea.incrementTop(); + else + selectionArea.incrementBottom(); + redraw(); + } else { + ((vt320)buffer).keyPressed(vt320.KEY_DOWN, ' ', event.getMetaState()); + tryKeyVibrate(); + } return true; case KeyEvent.KEYCODE_DPAD_RIGHT: - ((vt320)buffer).keyPressed(vt320.KEY_RIGHT, ' ', event.getMetaState()); - this.tryKeyVibrate(); + if (selectingForCopy) { + if (selectionArea.isSelectingOrigin()) + selectionArea.incrementLeft(); + else + selectionArea.incrementRight(); + redraw(); + } else { + ((vt320)buffer).keyPressed(vt320.KEY_RIGHT, ' ', event.getMetaState()); + tryKeyVibrate(); + } return true; case KeyEvent.KEYCODE_DPAD_CENTER: - // TODO: Add some visual indication of Ctrl state - if (ctrlPressed) { - ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); - ctrlPressed = false; - } else - ctrlPressed = true; + if (selectingForCopy) { + if (selectionArea.isSelectingOrigin()) + selectionArea.finishSelectingOrigin(); + else { + if (parent != null && clipboard != null) { + // copy selected area to clipboard + String copiedText = selectionArea.copyFrom(buffer); + + clipboard.setText(copiedText); + parent.notifyUser(parent.getContext().getString(R.string.console_copy_done, + copiedText.length())); + + selectingForCopy = false; + selectionArea.reset(); + } + } + + redraw(); + } else { + // TODO: Add some visual indication of Ctrl state + if (ctrlPressed) { + ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); + ctrlPressed = false; + } else + ctrlPressed = true; + } return true; } @@ -986,6 +1046,18 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal return false; } + public void setSelectingForCopy(boolean selectingForCopy) { + this.selectingForCopy = selectingForCopy; + } + + public boolean isSelectingForCopy() { + return selectingForCopy; + } + + public SelectionArea getSelectionArea() { + return selectionArea; + } + public synchronized void tryKeyVibrate() { if (bumpyArrows && vibrator != null) vibrator.vibrate(VIBRATE_DURATION); @@ -1028,7 +1100,8 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal bumpyArrows = manager.prefs.getBoolean(manager.res.getString(R.string.pref_bumpyarrows), true); vibrator = (Vibrator) parent.getContext().getSystemService(Context.VIBRATOR_SERVICE); - + clipboard = (ClipboardManager) parent.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + if (!forcedSize) { // recalculate buffer size int newTermWidth, newTermHeight; |