summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-04-14 16:16:49 -0700
committerJeff Sharkey <jsharkey@android.com>2013-04-25 09:59:52 -0700
commit00b00812cd0c883c2380065d7fda29512d5477f0 (patch)
treee46d54ccffbcae9ca81736263e00c6381d7c8207 /src/com/android
parenta816285f9c02e687268e76f02896ada33398b0dd (diff)
downloadandroid_packages_apps_Terminal-00b00812cd0c883c2380065d7fda29512d5477f0.tar.gz
android_packages_apps_Terminal-00b00812cd0c883c2380065d7fda29512d5477f0.tar.bz2
android_packages_apps_Terminal-00b00812cd0c883c2380065d7fda29512d5477f0.zip
Add scrollback support.
Switch terminal rendering to use ListView, splitting each row into a TerminalLineView item. This leverages existing ListView display list optimizations when scrolling, and gives us fling and overscroll for free. However, the simple case of a single line scrolling requires an entire screen rebind. Added locking between I/O thread and UI thread to provide consistent view of terminal state. Snap to current upstream libvterm, which has updated scrollback API. Examine full cell style when building runs. Address terminals using "keys" instead of indicies, since ordering can shift. Save and restore instance state to remember scrollback position. Avoid crashing after closing last terminal. Remove unused callbacks. Bug: 8332387 Change-Id: I06468d16ae8e1ff8ac79b7115c7cb3f9434b3c0d
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/terminal/Terminal.java56
-rw-r--r--src/com/android/terminal/TerminalActivity.java48
-rw-r--r--src/com/android/terminal/TerminalCallbacks.java8
-rw-r--r--src/com/android/terminal/TerminalKeys.java2
-rw-r--r--src/com/android/terminal/TerminalLineView.java85
-rw-r--r--src/com/android/terminal/TerminalService.java30
-rw-r--r--src/com/android/terminal/TerminalView.java354
7 files changed, 361 insertions, 222 deletions
diff --git a/src/com/android/terminal/Terminal.java b/src/com/android/terminal/Terminal.java
index 35c2cee..cdf3f60 100644
--- a/src/com/android/terminal/Terminal.java
+++ b/src/com/android/terminal/Terminal.java
@@ -22,7 +22,9 @@ import android.graphics.Color;
* Single terminal session backed by a pseudo terminal on the local device.
*/
public class Terminal {
- private static final String TAG = "Terminal";
+ public static final String TAG = "Terminal";
+
+ public final int key;
private static int sNumber = 0;
@@ -46,15 +48,17 @@ public class Terminal {
boolean strike;
int font;
- int fg = Color.RED;
- int bg = Color.BLUE;
+ int fg = Color.CYAN;
+ int bg = Color.DKGRAY;
}
+ // NOTE: clients must not call back into terminal while handling a callback,
+ // since native mutex isn't reentrant.
public interface TerminalClient {
- public void damage(int startRow, int endRow, int startCol, int endCol);
- public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
+ public void onDamage(int startRow, int endRow, int startCol, int endCol);
+ public void onMoveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol);
- public void bell();
+ public void onBell();
}
private final int mNativePtr;
@@ -68,7 +72,7 @@ public class Terminal {
@Override
public int damage(int startRow, int endRow, int startCol, int endCol) {
if (mClient != null) {
- mClient.damage(startRow, endRow, startCol, endCol);
+ mClient.onDamage(startRow, endRow, startCol, endCol);
}
return 1;
}
@@ -77,7 +81,7 @@ public class Terminal {
public int moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
if (mClient != null) {
- mClient.moveRect(destStartRow, destEndRow, destStartCol, destEndCol, srcStartRow,
+ mClient.onMoveRect(destStartRow, destEndRow, destStartCol, destEndCol, srcStartRow,
srcEndRow, srcStartCol, srcEndCol);
}
return 1;
@@ -86,7 +90,7 @@ public class Terminal {
@Override
public int bell() {
if (mClient != null) {
- mClient.bell();
+ mClient.onBell();
}
return 1;
}
@@ -94,8 +98,9 @@ public class Terminal {
public Terminal() {
mNativePtr = nativeInit(mCallbacks, 25, 80);
- mTitle = TAG + " " + sNumber++;
- mThread = new Thread(TAG) {
+ key = sNumber++;
+ mTitle = TAG + " " + key;
+ mThread = new Thread(mTitle) {
@Override
public void run() {
nativeRun(mNativePtr);
@@ -110,9 +115,9 @@ public class Terminal {
mThread.start();
}
- public void stop() {
- if (nativeStop(mNativePtr) != 0) {
- throw new IllegalStateException("stop failed");
+ public void destroy() {
+ if (nativeDestroy(mNativePtr) != 0) {
+ throw new IllegalStateException("destroy failed");
}
}
@@ -120,14 +125,8 @@ public class Terminal {
mClient = client;
}
- public void flushDamage() {
- if (nativeFlushDamage(mNativePtr) != 0) {
- throw new IllegalStateException("flushDamage failed");
- }
- }
-
- public void resize(int rows, int cols) {
- if (nativeResize(mNativePtr, rows, cols) != 0) {
+ public void resize(int rows, int cols, int scrollRows) {
+ if (nativeResize(mNativePtr, rows, cols, scrollRows) != 0) {
throw new IllegalStateException("resize failed");
}
}
@@ -140,6 +139,10 @@ public class Terminal {
return nativeGetCols(mNativePtr);
}
+ public int getScrollRows() {
+ return nativeGetScrollRows(mNativePtr);
+ }
+
public void getCellRun(int row, int col, CellRun run) {
if (nativeGetCellRun(mNativePtr, row, col, run) != 0) {
throw new IllegalStateException("getCell failed");
@@ -159,16 +162,15 @@ public class Terminal {
return nativeDispatchCharacter(mNativePtr, modifiers, character);
}
-
private static native int nativeInit(TerminalCallbacks callbacks, int rows, int cols);
- private static native int nativeRun(int ptr);
- private static native int nativeStop(int ptr);
+ private static native int nativeDestroy(int ptr);
- private static native int nativeFlushDamage(int ptr);
- private static native int nativeResize(int ptr, int rows, int cols);
+ private static native int nativeRun(int ptr);
+ private static native int nativeResize(int ptr, int rows, int cols, int scrollRows);
private static native int nativeGetCellRun(int ptr, int row, int col, CellRun run);
private static native int nativeGetRows(int ptr);
private static native int nativeGetCols(int ptr);
+ private static native int nativeGetScrollRows(int ptr);
private static native boolean nativeDispatchKey(int ptr, int modifiers, int key);
private static native boolean nativeDispatchCharacter(int ptr, int modifiers, int character);
diff --git a/src/com/android/terminal/TerminalActivity.java b/src/com/android/terminal/TerminalActivity.java
index 99d2be6..dd94d74 100644
--- a/src/com/android/terminal/TerminalActivity.java
+++ b/src/com/android/terminal/TerminalActivity.java
@@ -16,6 +16,8 @@
package com.android.terminal;
+import static com.android.terminal.Terminal.TAG;
+
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
@@ -23,10 +25,12 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.PagerTitleStrip;
import android.support.v4.view.ViewPager;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -37,7 +41,6 @@ import android.view.ViewGroup;
* {@link TerminalService}.
*/
public class TerminalActivity extends Activity {
- private static final String TAG = "Terminal";
private TerminalService mService;
@@ -59,6 +62,7 @@ public class TerminalActivity extends Activity {
// Bind UI to known terminals
mTermAdapter.notifyDataSetChanged();
+ invalidateOptionsMenu();
}
@Override
@@ -69,6 +73,9 @@ public class TerminalActivity extends Activity {
};
private final PagerAdapter mTermAdapter = new PagerAdapter() {
+ private SparseArray<SparseArray<Parcelable>>
+ mSavedState = new SparseArray<SparseArray<Parcelable>>();
+
@Override
public int getCount() {
if (mService != null) {
@@ -80,9 +87,17 @@ public class TerminalActivity extends Activity {
@Override
public Object instantiateItem(ViewGroup container, int position) {
- final Terminal term = mService.getTerminals().get(position);
final TerminalView view = new TerminalView(container.getContext());
+ view.setId(android.R.id.list);
+
+ final Terminal term = mService.getTerminals().valueAt(position);
view.setTerminal(term);
+
+ final SparseArray<Parcelable> state = mSavedState.get(term.key);
+ if (state != null) {
+ view.restoreHierarchyState(state);
+ }
+
container.addView(view);
return view;
}
@@ -90,13 +105,24 @@ public class TerminalActivity extends Activity {
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
final TerminalView view = (TerminalView) object;
+
+ final int key = view.getTerminal().key;
+ SparseArray<Parcelable> state = mSavedState.get(key);
+ if (state == null) {
+ state = new SparseArray<Parcelable>();
+ mSavedState.put(key, state);
+ }
+ view.saveHierarchyState(state);
+
view.setTerminal(null);
container.removeView(view);
}
@Override
public int getItemPosition(Object object) {
- final int index = mService.getTerminals().indexOf(object);
+ final TerminalView view = (TerminalView) object;
+ final int key = view.getTerminal().key;
+ final int index = mService.getTerminals().indexOfKey(key);
if (index == -1) {
return POSITION_NONE;
} else {
@@ -111,7 +137,7 @@ public class TerminalActivity extends Activity {
@Override
public CharSequence getPageTitle(int position) {
- return mService.getTerminals().get(position).getTitle();
+ return mService.getTerminals().valueAt(position).getTitle();
}
};
@@ -147,21 +173,29 @@ public class TerminalActivity extends Activity {
}
@Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ menu.findItem(R.id.menu_close_tab).setEnabled(mTermAdapter.getCount() > 0);
+ return true;
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_tab: {
mService.createTerminal();
mTermAdapter.notifyDataSetChanged();
+ invalidateOptionsMenu();
final int index = mService.getTerminals().size() - 1;
mPager.setCurrentItem(index, true);
return true;
}
case R.id.menu_close_tab: {
final int index = mPager.getCurrentItem();
- final Terminal term = mService.getTerminals().get(index);
- mService.destroyTerminal(term);
- // TODO: ask adamp about buggy zero item behavior
+ final int key = mService.getTerminals().keyAt(index);
+ mService.destroyTerminal(key);
mTermAdapter.notifyDataSetChanged();
+ invalidateOptionsMenu();
return true;
}
}
diff --git a/src/com/android/terminal/TerminalCallbacks.java b/src/com/android/terminal/TerminalCallbacks.java
index b449075..fb5a1ca 100644
--- a/src/com/android/terminal/TerminalCallbacks.java
+++ b/src/com/android/terminal/TerminalCallbacks.java
@@ -21,10 +21,6 @@ public abstract class TerminalCallbacks {
return 1;
}
- public int prescroll(int startRow, int endRow, int startCol, int endCol) {
- return 1;
- }
-
public int moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
return 1;
@@ -53,8 +49,4 @@ public abstract class TerminalCallbacks {
public int bell() {
return 1;
}
-
- public int resize(int rows, int cols) {
- return 1;
- }
}
diff --git a/src/com/android/terminal/TerminalKeys.java b/src/com/android/terminal/TerminalKeys.java
index 17cab5e..67dc231 100644
--- a/src/com/android/terminal/TerminalKeys.java
+++ b/src/com/android/terminal/TerminalKeys.java
@@ -21,7 +21,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
-public class TerminalKeys implements View.OnKeyListener {
+public class TerminalKeys {
private static final String TAG = "TerminalKeys";
private static final boolean DEBUG = true;
// Taken from vterm_input.h
diff --git a/src/com/android/terminal/TerminalLineView.java b/src/com/android/terminal/TerminalLineView.java
new file mode 100644
index 0000000..fff1301
--- /dev/null
+++ b/src/com/android/terminal/TerminalLineView.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.terminal;
+
+import static com.android.terminal.Terminal.TAG;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.util.Log;
+import android.view.View;
+
+import com.android.terminal.TerminalView.TerminalMetrics;
+
+/**
+ * Rendered contents of a single line of a {@link Terminal} session.
+ */
+public class TerminalLineView extends View {
+ public int pos;
+ public int row;
+ public int cols;
+
+ private final Terminal mTerm;
+ private final TerminalMetrics mMetrics;
+
+ public TerminalLineView(Context context, Terminal term, TerminalMetrics metrics) {
+ super(context);
+ mTerm = term;
+ mMetrics = metrics;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
+ getDefaultSize(mMetrics.charHeight, heightMeasureSpec));
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mTerm == null) {
+ Log.w(TAG, "onDraw() without a terminal");
+ canvas.drawColor(Color.MAGENTA);
+ return;
+ }
+
+ final TerminalMetrics m = mMetrics;
+
+ for (int col = 0; col < cols;) {
+ mTerm.getCellRun(row, col, m.run);
+
+ m.bgPaint.setColor(m.run.bg);
+ m.textPaint.setColor(m.run.fg);
+
+ final int x = col * m.charWidth;
+ final int xEnd = x + (m.run.colSize * m.charWidth);
+
+ canvas.save();
+ canvas.translate(x, 0);
+ canvas.clipRect(0, 0, m.run.colSize * m.charWidth, m.charHeight);
+
+ canvas.drawPaint(m.bgPaint);
+ canvas.drawPosText(m.run.data, 0, m.run.dataSize, m.pos, m.textPaint);
+
+ canvas.restore();
+
+ col += m.run.colSize;
+ }
+ }
+}
diff --git a/src/com/android/terminal/TerminalService.java b/src/com/android/terminal/TerminalService.java
index ebbdcce..4399390 100644
--- a/src/com/android/terminal/TerminalService.java
+++ b/src/com/android/terminal/TerminalService.java
@@ -20,19 +20,14 @@ import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import android.util.SparseArray;
/**
* Background service that keeps {@link Terminal} instances running and warm
* when UI isn't present.
*/
public class TerminalService extends Service {
- private static final String TAG = "Terminal";
-
- private final ArrayList<Terminal> mTerminals = new ArrayList<Terminal>();
+ private final SparseArray<Terminal> mTerminals = new SparseArray<Terminal>();
public class ServiceBinder extends Binder {
public TerminalService getService() {
@@ -45,28 +40,29 @@ public class TerminalService extends Service {
return new ServiceBinder();
}
- public List<Terminal> getTerminals() {
- return Collections.unmodifiableList(mTerminals);
+ public SparseArray<Terminal> getTerminals() {
+ return mTerminals;
}
- public Terminal createTerminal() {
+ public int createTerminal() {
// If our first terminal, start ourselves as long-lived service
- if (mTerminals.isEmpty()) {
+ if (mTerminals.size() == 0) {
startService(new Intent(this, TerminalService.class));
}
final Terminal term = new Terminal();
term.start();
- mTerminals.add(term);
- return term;
+ mTerminals.put(term.key, term);
+ return term.key;
}
- public void destroyTerminal(Terminal term) {
- term.stop();
- mTerminals.remove(term);
+ public void destroyTerminal(int key) {
+ final Terminal term = mTerminals.get(key);
+ term.destroy();
+ mTerminals.delete(key);
// If our last terminal, tear down long-lived service
- if (mTerminals.isEmpty()) {
+ if (mTerminals.size() == 0) {
stopService(new Intent(this, TerminalService.class));
}
}
diff --git a/src/com/android/terminal/TerminalView.java b/src/com/android/terminal/TerminalView.java
index 4764183..72d1191 100644
--- a/src/com/android/terminal/TerminalView.java
+++ b/src/com/android/terminal/TerminalView.java
@@ -16,20 +16,23 @@
package com.android.terminal;
+import static com.android.terminal.Terminal.TAG;
+
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
-import android.graphics.Rect;
import android.graphics.Typeface;
-import android.os.SystemClock;
+import android.os.Parcelable;
+import android.util.AttributeSet;
import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
-import android.view.KeyEvent;
-import android.view.View;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
import com.android.terminal.Terminal.CellRun;
import com.android.terminal.Terminal.TerminalClient;
@@ -37,219 +40,246 @@ import com.android.terminal.Terminal.TerminalClient;
/**
* Rendered contents of a {@link Terminal} session.
*/
-public class TerminalView extends View {
- private static final String TAG = "Terminal";
+public class TerminalView extends ListView {
private static final boolean LOGD = true;
- private static final int MAX_RUN_LENGTH = 128;
+ private static final boolean SCROLL_ON_DAMAGE = false;
+ private static final boolean SCROLL_ON_INPUT = true;
- private final Context mContext;
+ private Terminal mTerm;
- private final Paint mBgPaint = new Paint();
- private final Paint mTextPaint = new Paint();
+ private boolean mScrolled;
- /** Run of cells used when drawing */
- private final CellRun mRun;
- /** Screen coordinates to draw chars into */
- private final float[] mPos;
+ private int mRows;
+ private int mCols;
+ private int mScrollRows;
- private Terminal mTerm;
+ private final TerminalMetrics mMetrics = new TerminalMetrics();
+ private final TerminalKeys mTermKeys = new TerminalKeys();
- private TerminalKeys mTermKeys;
+ /**
+ * Metrics shared between all {@link TerminalLineView} children. Locking
+ * provided by main thread.
+ */
+ static class TerminalMetrics {
+ private static final int MAX_RUN_LENGTH = 128;
- private int mCharTop;
- private int mCharWidth;
- private int mCharHeight;
+ final Paint bgPaint = new Paint();
+ final Paint textPaint = new Paint();
- // TODO: for atomicity we might need to snapshot runs when processing
- // callbacks driven by vterm thread
+ /** Run of cells used when drawing */
+ final CellRun run;
+ /** Screen coordinates to draw chars into */
+ final float[] pos;
- private TerminalClient mClient = new TerminalClient() {
- @Override
- public void damage(int startRow, int endRow, int startCol, int endCol) {
- if (LOGD) Log.d(TAG, "damage(" + startRow + ", " + endRow + ", " + startCol + ", " + endCol + ")");
-
- // Invalidate region on screen
- final int top = startRow * mCharHeight;
- final int bottom = (endRow + 1) * mCharHeight;
- final int left = startCol * mCharWidth;
- final int right = (endCol + 1) * mCharWidth;
- postInvalidate(left, top, right, bottom);
+ int charTop;
+ int charWidth;
+ int charHeight;
+
+ public TerminalMetrics() {
+ run = new Terminal.CellRun();
+ run.data = new char[MAX_RUN_LENGTH];
+
+ // Positions of each possible cell
+ // TODO: make sure this works with surrogate pairs
+ pos = new float[MAX_RUN_LENGTH * 2];
+ setTextSize(20);
}
- @Override
- public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
- int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
- // Treat as normal damage and perform full redraw
- final int startRow = Math.min(destStartRow, srcStartRow);
- final int endRow = Math.max(destEndRow, srcEndRow);
- final int startCol = Math.min(destStartCol, srcStartCol);
- final int endCol = Math.max(destEndCol, srcEndCol);
- damage(startRow, endRow, startCol, endCol);
+ public void setTextSize(float textSize) {
+ textPaint.setTypeface(Typeface.MONOSPACE);
+ textPaint.setAntiAlias(true);
+ textPaint.setTextSize(textSize);
+
+ // Read metrics to get exact pixel dimensions
+ final FontMetrics fm = textPaint.getFontMetrics();
+ charTop = (int) Math.ceil(fm.top);
+
+ final float[] widths = new float[1];
+ textPaint.getTextWidths("X", widths);
+ charWidth = (int) Math.ceil(widths[0]);
+ charHeight = (int) Math.ceil(fm.descent - fm.top);
+
+ // Update drawing positions
+ for (int i = 0; i < MAX_RUN_LENGTH; i++) {
+ pos[i * 2] = i * charWidth;
+ pos[(i * 2) + 1] = -charTop;
+ }
}
+ }
+ private final Runnable mDamageRunnable = new Runnable() {
@Override
- public void bell() {
- Log.i(TAG, "DING!");
+ public void run() {
+ invalidateViews();
+ if (SCROLL_ON_DAMAGE) {
+ scrollToBottom(true);
+ }
}
};
public TerminalView(Context context) {
- super(context);
- mContext = context;
+ this(context, null);
+ }
- mRun = new Terminal.CellRun();
- mRun.data = new char[MAX_RUN_LENGTH];
+ public TerminalView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.listViewStyle);
+ }
- // Positions of each possible cell
- // TODO: make sure this works with surrogate pairs
- mPos = new float[MAX_RUN_LENGTH * 2];
+ public TerminalView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
- setBackgroundColor(Color.BLACK);
- setTextSize(20);
+ setBackground(null);
+ setDivider(null);
- // TODO: remove this test code that triggers invalidates
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- v.invalidate();
- v.requestFocus();
- }
- });
-
- // Set view properties
setFocusable(true);
setFocusableInTouchMode(true);
- setScrollContainer(true);
- mTermKeys = new TerminalKeys();
- setOnKeyListener(mTermKeys);
+ setAdapter(mAdapter);
+ setOnKeyListener(mKeyListener);
}
- public void setTerminal(Terminal term) {
- final Terminal orig = mTerm;
- if (orig != null) {
- orig.setClient(null);
- }
- mTerm = term;
- if (term != null) {
- term.setClient(mClient);
- mTermKeys.setTerminal(term);
+ private final BaseAdapter mAdapter = new BaseAdapter() {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final TerminalLineView view;
+ if (convertView != null) {
+ view = (TerminalLineView) convertView;
+ } else {
+ view = new TerminalLineView(parent.getContext(), mTerm, mMetrics);
+ }
+
+ view.pos = position;
+ view.row = posToRow(position);
+ view.cols = mCols;
+ return view;
}
- updateTerminalSize();
- }
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- if (mTerm != null) {
- mTerm.setClient(mClient);
+ @Override
+ public long getItemId(int position) {
+ return position;
}
- }
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mTerm != null) {
- mTerm.setClient(null);
+ @Override
+ public Object getItem(int position) {
+ return null;
}
- }
- public void setTextSize(float textSize) {
- mTextPaint.setTypeface(Typeface.MONOSPACE);
- mTextPaint.setAntiAlias(true);
- mTextPaint.setTextSize(textSize);
-
- // Read metrics to get exact pixel dimensions
- final FontMetrics fm = mTextPaint.getFontMetrics();
- mCharTop = (int) Math.ceil(fm.top);
-
- final float[] widths = new float[1];
- mTextPaint.getTextWidths("X", widths);
- mCharWidth = (int) Math.ceil(widths[0]);
- mCharHeight = (int) Math.ceil(fm.descent - fm.top);
-
- // Update drawing positions
- for (int i = 0; i < MAX_RUN_LENGTH; i++) {
- mPos[i * 2] = i * mCharWidth;
- mPos[(i * 2) + 1] = -mCharTop;
+ @Override
+ public int getCount() {
+ if (mTerm != null) {
+ return mRows + mScrollRows;
+ } else {
+ return 0;
+ }
}
+ };
- updateTerminalSize();
- }
+ private TerminalClient mClient = new TerminalClient() {
+ @Override
+ public void onDamage(final int startRow, final int endRow, int startCol, int endCol) {
+ post(mDamageRunnable);
+ }
- /**
- * Determine terminal dimensions based on current dimensions and font size,
- * and request that {@link Terminal} change to that size.
- */
- public void updateTerminalSize() {
- if (getWidth() > 0 && getHeight() > 0 && mTerm != null) {
- final int rows = getHeight() / mCharHeight;
- final int cols = getWidth() / mCharWidth;
- mTerm.resize(rows, cols);
+ @Override
+ public void onMoveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
+ int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
+ post(mDamageRunnable);
}
- }
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (changed) {
- updateTerminalSize();
+ @Override
+ public void onBell() {
+ Log.i(TAG, "DING!");
}
+ };
+
+ private int rowToPos(int row) {
+ return row + mScrollRows;
}
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
+ private int posToRow(int pos) {
+ return pos - mScrollRows;
+ }
- if (mTerm == null) {
- Log.w(TAG, "onDraw() without a terminal");
- canvas.drawColor(Color.MAGENTA);
- return;
+ private View.OnKeyListener mKeyListener = new OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ final boolean res = mTermKeys.onKey(v, keyCode, event);
+ if (res && SCROLL_ON_INPUT) {
+ scrollToBottom(true);
+ }
+ return res;
}
+ };
- final long start = SystemClock.elapsedRealtime();
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ super.onRestoreInstanceState(state);
+ mScrolled = true;
+ }
- // Only draw dirty region of console
- final Rect dirty = canvas.getClipBounds();
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (!mScrolled) {
+ scrollToBottom(false);
+ }
+ }
- final int rows = mTerm.getRows();
- final int cols = mTerm.getCols();
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
- final int startRow = dirty.top / mCharHeight;
- final int endRow = Math.min(dirty.bottom / mCharHeight, rows - 1);
- final int startCol = dirty.left / mCharWidth;
- final int endCol = Math.min(dirty.right / mCharWidth, cols - 1);
+ final int rows = h / mMetrics.charHeight;
+ final int cols = w / mMetrics.charWidth;
+ final int scrollRows = mScrollRows;
- final CellRun run = mRun;
- final float[] pos = mPos;
+ final boolean sizeChanged = (rows != mRows || cols != mCols || scrollRows != mScrollRows);
+ if (mTerm != null && sizeChanged) {
+ mTerm.resize(rows, cols, scrollRows);
- for (int row = startRow; row <= endRow; row++) {
- for (int col = startCol; col <= endCol;) {
- mTerm.getCellRun(row, col, run);
+ mRows = rows;
+ mCols = cols;
+ mScrollRows = scrollRows;
- mBgPaint.setColor(run.bg);
- mTextPaint.setColor(run.fg);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
- final int y = row * mCharHeight;
- final int x = col * mCharWidth;
- final int xEnd = x + (run.colSize * mCharWidth);
+ public void scrollToBottom(boolean animate) {
+ final int dur = animate ? 250 : 0;
+ smoothScrollToPositionFromTop(getCount(), 0, dur);
+ mScrolled = true;
+ }
- canvas.save(Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG);
- canvas.translate(x, y);
- canvas.clipRect(0, 0, run.colSize * mCharWidth, mCharHeight);
+ public void setTerminal(Terminal term) {
+ final Terminal orig = mTerm;
+ if (orig != null) {
+ orig.setClient(null);
+ }
+ mTerm = term;
+ mScrolled = false;
+ if (term != null) {
+ term.setClient(mClient);
+ mTermKeys.setTerminal(term);
- canvas.drawPaint(mBgPaint);
- canvas.drawPosText(run.data, 0, run.dataSize, pos, mTextPaint);
+ // Populate any current settings
+ mRows = mTerm.getRows();
+ mCols = mTerm.getCols();
+ mScrollRows = mTerm.getScrollRows();
+ mAdapter.notifyDataSetChanged();
+ }
+ }
- canvas.restore();
+ public Terminal getTerminal() {
+ return mTerm;
+ }
- col += run.colSize;
- }
- }
+ public void setTextSize(float textSize) {
+ mMetrics.setTextSize(textSize);
- final long delta = SystemClock.elapsedRealtime() - start;
- if (LOGD) Log.d(TAG, "onDraw() took " + delta + "ms");
+ // Layout will kick off terminal resize when needed
+ requestLayout();
}
@Override