blob: 076da80d6d5059c53346168caf406b0a54253c0a [file] [log] [blame]
Jeff Sharkey410e0da2013-02-19 23:10:46 -08001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.terminal;
18
Jeff Sharkey00b00812013-04-14 16:16:49 -070019import static com.android.terminal.Terminal.TAG;
20
Jeff Sharkey410e0da2013-02-19 23:10:46 -080021import android.content.Context;
Jeff Sharkey410e0da2013-02-19 23:10:46 -080022import android.graphics.Paint;
23import android.graphics.Paint.FontMetrics;
Jeff Sharkey479bd642013-02-21 17:45:16 -080024import android.graphics.Typeface;
Jeff Sharkey00b00812013-04-14 16:16:49 -070025import android.os.Parcelable;
26import android.util.AttributeSet;
Jeff Sharkey410e0da2013-02-19 23:10:46 -080027import android.util.Log;
Jeff Sharkey00b00812013-04-14 16:16:49 -070028import android.view.KeyEvent;
29import android.view.View;
30import android.view.ViewGroup;
Michael Wright26c9bb32013-02-25 20:56:53 -080031import android.view.inputmethod.BaseInputConnection;
32import android.view.inputmethod.EditorInfo;
33import android.view.inputmethod.InputConnection;
Tom Marshall1c4704e2014-12-31 09:17:26 -080034import android.view.inputmethod.InputMethodManager;
35import android.widget.AdapterView;
Jeff Sharkey00b00812013-04-14 16:16:49 -070036import android.widget.BaseAdapter;
37import android.widget.ListView;
Jeff Sharkey410e0da2013-02-19 23:10:46 -080038
Jeff Sharkeya76e3382013-02-21 19:09:55 -080039import com.android.terminal.Terminal.CellRun;
Jeff Sharkey410e0da2013-02-19 23:10:46 -080040import com.android.terminal.Terminal.TerminalClient;
41
42/**
43 * Rendered contents of a {@link Terminal} session.
44 */
Jeff Sharkey00b00812013-04-14 16:16:49 -070045public class TerminalView extends ListView {
Jeff Sharkeyde15e792013-02-23 15:42:10 -080046 private static final boolean LOGD = true;
Jeff Sharkey410e0da2013-02-19 23:10:46 -080047
Jeff Sharkey00b00812013-04-14 16:16:49 -070048 private static final boolean SCROLL_ON_DAMAGE = false;
49 private static final boolean SCROLL_ON_INPUT = true;
Jeff Sharkeya76e3382013-02-21 19:09:55 -080050
Jeff Sharkeyde15e792013-02-23 15:42:10 -080051 private Terminal mTerm;
52
Jeff Sharkey00b00812013-04-14 16:16:49 -070053 private boolean mScrolled;
Michael Wright26c9bb32013-02-25 20:56:53 -080054
Jeff Sharkey00b00812013-04-14 16:16:49 -070055 private int mRows;
56 private int mCols;
57 private int mScrollRows;
Jeff Sharkey410e0da2013-02-19 23:10:46 -080058
Jeff Sharkey00b00812013-04-14 16:16:49 -070059 private final TerminalMetrics mMetrics = new TerminalMetrics();
60 private final TerminalKeys mTermKeys = new TerminalKeys();
Jeff Sharkeya76e3382013-02-21 19:09:55 -080061
Jeff Sharkey00b00812013-04-14 16:16:49 -070062 /**
63 * Metrics shared between all {@link TerminalLineView} children. Locking
64 * provided by main thread.
65 */
66 static class TerminalMetrics {
67 private static final int MAX_RUN_LENGTH = 128;
Jeff Sharkey6a142b62013-02-23 12:27:57 -080068
Jeff Sharkey00b00812013-04-14 16:16:49 -070069 final Paint bgPaint = new Paint();
70 final Paint textPaint = new Paint();
Tom Marshall5b68e8a2014-12-31 10:51:01 -080071 final Paint cursorPaint = new Paint();
Jeff Sharkey00b00812013-04-14 16:16:49 -070072
73 /** Run of cells used when drawing */
74 final CellRun run;
75 /** Screen coordinates to draw chars into */
76 final float[] pos;
77
78 int charTop;
79 int charWidth;
80 int charHeight;
81
82 public TerminalMetrics() {
83 run = new Terminal.CellRun();
84 run.data = new char[MAX_RUN_LENGTH];
85
86 // Positions of each possible cell
87 // TODO: make sure this works with surrogate pairs
88 pos = new float[MAX_RUN_LENGTH * 2];
89 setTextSize(20);
Jeff Sharkey410e0da2013-02-19 23:10:46 -080090 }
91
Jeff Sharkey00b00812013-04-14 16:16:49 -070092 public void setTextSize(float textSize) {
93 textPaint.setTypeface(Typeface.MONOSPACE);
94 textPaint.setAntiAlias(true);
95 textPaint.setTextSize(textSize);
Jeff Sharkey6a142b62013-02-23 12:27:57 -080096
Jeff Sharkey00b00812013-04-14 16:16:49 -070097 // Read metrics to get exact pixel dimensions
98 final FontMetrics fm = textPaint.getFontMetrics();
99 charTop = (int) Math.ceil(fm.top);
100
101 final float[] widths = new float[1];
102 textPaint.getTextWidths("X", widths);
103 charWidth = (int) Math.ceil(widths[0]);
104 charHeight = (int) Math.ceil(fm.descent - fm.top);
105
106 // Update drawing positions
107 for (int i = 0; i < MAX_RUN_LENGTH; i++) {
108 pos[i * 2] = i * charWidth;
109 pos[(i * 2) + 1] = -charTop;
110 }
111 }
112 }
113
Tom Marshall1c4704e2014-12-31 09:17:26 -0800114 private final AdapterView.OnItemClickListener mClickListener = new AdapterView.OnItemClickListener() {
115 @Override
116 public void onItemClick(AdapterView<?> parent, View v, int pos, long id) {
117 if (parent.requestFocus()) {
118 InputMethodManager imm = (InputMethodManager) parent.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
119 imm.showSoftInput(parent, InputMethodManager.SHOW_IMPLICIT);
120 }
121 }
122 };
123
Jeff Sharkey00b00812013-04-14 16:16:49 -0700124 private final Runnable mDamageRunnable = new Runnable() {
Jeff Sharkey6a142b62013-02-23 12:27:57 -0800125 @Override
Jeff Sharkey00b00812013-04-14 16:16:49 -0700126 public void run() {
127 invalidateViews();
128 if (SCROLL_ON_DAMAGE) {
129 scrollToBottom(true);
130 }
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800131 }
132 };
133
Jeff Sharkeyde15e792013-02-23 15:42:10 -0800134 public TerminalView(Context context) {
Jeff Sharkey00b00812013-04-14 16:16:49 -0700135 this(context, null);
136 }
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800137
Jeff Sharkey00b00812013-04-14 16:16:49 -0700138 public TerminalView(Context context, AttributeSet attrs) {
139 this(context, attrs, com.android.internal.R.attr.listViewStyle);
140 }
Jeff Sharkeya76e3382013-02-21 19:09:55 -0800141
Jeff Sharkey00b00812013-04-14 16:16:49 -0700142 public TerminalView(Context context, AttributeSet attrs, int defStyle) {
143 super(context, attrs, defStyle);
Jeff Sharkeya76e3382013-02-21 19:09:55 -0800144
Jeff Sharkey00b00812013-04-14 16:16:49 -0700145 setBackground(null);
146 setDivider(null);
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800147
Michael Wright26c9bb32013-02-25 20:56:53 -0800148 setFocusable(true);
149 setFocusableInTouchMode(true);
Michael Wright26c9bb32013-02-25 20:56:53 -0800150
Jeff Sharkey00b00812013-04-14 16:16:49 -0700151 setAdapter(mAdapter);
152 setOnKeyListener(mKeyListener);
Tom Marshall1c4704e2014-12-31 09:17:26 -0800153
154 setOnItemClickListener(mClickListener);
Jeff Sharkey00b00812013-04-14 16:16:49 -0700155 }
156
157 private final BaseAdapter mAdapter = new BaseAdapter() {
158 @Override
159 public View getView(int position, View convertView, ViewGroup parent) {
160 final TerminalLineView view;
161 if (convertView != null) {
162 view = (TerminalLineView) convertView;
163 } else {
164 view = new TerminalLineView(parent.getContext(), mTerm, mMetrics);
165 }
166
167 view.pos = position;
168 view.row = posToRow(position);
169 view.cols = mCols;
170 return view;
171 }
172
173 @Override
174 public long getItemId(int position) {
175 return position;
176 }
177
178 @Override
179 public Object getItem(int position) {
180 return null;
181 }
182
183 @Override
184 public int getCount() {
185 if (mTerm != null) {
186 return mRows + mScrollRows;
187 } else {
188 return 0;
189 }
190 }
191 };
192
193 private TerminalClient mClient = new TerminalClient() {
194 @Override
195 public void onDamage(final int startRow, final int endRow, int startCol, int endCol) {
196 post(mDamageRunnable);
197 }
198
199 @Override
200 public void onMoveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
201 int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
202 post(mDamageRunnable);
203 }
204
205 @Override
Tom Marshall5b68e8a2014-12-31 10:51:01 -0800206 public void onMoveCursor(int posRow, int posCol, int oldPosRow, int oldPosCol, int visible) {
207 post(mDamageRunnable);
208 }
209
210 @Override
Jeff Sharkey00b00812013-04-14 16:16:49 -0700211 public void onBell() {
212 Log.i(TAG, "DING!");
213 }
214 };
215
216 private int rowToPos(int row) {
217 return row + mScrollRows;
218 }
219
220 private int posToRow(int pos) {
221 return pos - mScrollRows;
222 }
223
224 private View.OnKeyListener mKeyListener = new OnKeyListener() {
225 @Override
226 public boolean onKey(View v, int keyCode, KeyEvent event) {
227 final boolean res = mTermKeys.onKey(v, keyCode, event);
228 if (res && SCROLL_ON_INPUT) {
229 scrollToBottom(true);
230 }
231 return res;
232 }
233 };
234
235 @Override
236 public void onRestoreInstanceState(Parcelable state) {
237 super.onRestoreInstanceState(state);
238 mScrolled = true;
239 }
240
241 @Override
242 protected void onAttachedToWindow() {
243 super.onAttachedToWindow();
244 if (!mScrolled) {
245 scrollToBottom(false);
246 }
247 }
248
249 @Override
250 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
251 super.onSizeChanged(w, h, oldw, oldh);
252
253 final int rows = h / mMetrics.charHeight;
254 final int cols = w / mMetrics.charWidth;
255 final int scrollRows = mScrollRows;
256
257 final boolean sizeChanged = (rows != mRows || cols != mCols || scrollRows != mScrollRows);
258 if (mTerm != null && sizeChanged) {
259 mTerm.resize(rows, cols, scrollRows);
260
261 mRows = rows;
262 mCols = cols;
263 mScrollRows = scrollRows;
264
265 mAdapter.notifyDataSetChanged();
266 }
267 }
268
269 public void scrollToBottom(boolean animate) {
270 final int dur = animate ? 250 : 0;
271 smoothScrollToPositionFromTop(getCount(), 0, dur);
272 mScrolled = true;
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800273 }
274
Jeff Sharkeyde15e792013-02-23 15:42:10 -0800275 public void setTerminal(Terminal term) {
276 final Terminal orig = mTerm;
277 if (orig != null) {
278 orig.setClient(null);
279 }
280 mTerm = term;
Jeff Sharkey00b00812013-04-14 16:16:49 -0700281 mScrolled = false;
Jeff Sharkeyde15e792013-02-23 15:42:10 -0800282 if (term != null) {
283 term.setClient(mClient);
Michael Wright26c9bb32013-02-25 20:56:53 -0800284 mTermKeys.setTerminal(term);
Jeff Sharkeyde15e792013-02-23 15:42:10 -0800285
Tom Marshall5b68e8a2014-12-31 10:51:01 -0800286 mMetrics.cursorPaint.setColor(0xfff0f0f0);
287
Jeff Sharkey00b00812013-04-14 16:16:49 -0700288 // Populate any current settings
289 mRows = mTerm.getRows();
290 mCols = mTerm.getCols();
291 mScrollRows = mTerm.getScrollRows();
292 mAdapter.notifyDataSetChanged();
Jeff Sharkeyde15e792013-02-23 15:42:10 -0800293 }
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800294 }
295
Jeff Sharkey00b00812013-04-14 16:16:49 -0700296 public Terminal getTerminal() {
297 return mTerm;
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800298 }
299
300 public void setTextSize(float textSize) {
Jeff Sharkey00b00812013-04-14 16:16:49 -0700301 mMetrics.setTextSize(textSize);
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800302
Jeff Sharkey00b00812013-04-14 16:16:49 -0700303 // Layout will kick off terminal resize when needed
304 requestLayout();
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800305 }
Michael Wright26c9bb32013-02-25 20:56:53 -0800306
307 @Override
308 public boolean onCheckIsTextEditor() {
309 return true;
310 }
311
312 @Override
313 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
314 outAttrs.imeOptions |=
315 EditorInfo.IME_FLAG_NO_EXTRACT_UI |
316 EditorInfo.IME_FLAG_NO_ENTER_ACTION |
317 EditorInfo.IME_ACTION_NONE;
318 outAttrs.inputType = EditorInfo.TYPE_NULL;
319 return new BaseInputConnection(this, false) {
320 @Override
321 public boolean deleteSurroundingText (int leftLength, int rightLength) {
322 KeyEvent k;
323 if (rightLength == 0 && leftLength == 0) {
324 k = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
325 return this.sendKeyEvent(k);
326 }
327 for (int i = 0; i < leftLength; i++) {
328 k = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
329 this.sendKeyEvent(k);
330 }
331 for (int i = 0; i < rightLength; i++) {
332 k = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FORWARD_DEL);
333 this.sendKeyEvent(k);
334 }
335 return true;
336 }
337 };
338 }
Jeff Sharkey410e0da2013-02-19 23:10:46 -0800339}