2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package jackpal.androidterm.session;
19 import android.graphics.Canvas;
20 import android.util.Log;
22 import jackpal.androidterm.model.Screen;
23 import jackpal.androidterm.model.TextRenderer;
26 * A TranscriptScreen is a screen that remembers data that's been scrolled. The
27 * old data is stored in a ring buffer to minimize the amount of copying that
28 * needs to be done. The transcript does its own drawing, to avoid having to
29 * expose its internal data structures.
31 public class TranscriptScreen implements Screen {
32 private static final String TAG = "TranscriptScreen";
35 * The width of the transcript, in characters. Fixed at initialization.
40 * The total number of rows in the transcript and the screen. Fixed at
43 private int mTotalRows;
46 * The number of rows in the active portion of the transcript. Doesn't
49 private int mActiveTranscriptRows;
52 * Which row is currently the topmost line of the transcript. Used to
53 * implement a circular buffer.
58 * The number of active rows, includes both the transcript and the screen.
60 private int mActiveRows;
63 * The number of rows in the screen.
65 private int mScreenRows;
68 * The data for both the screen and the transcript. The first mScreenRows *
69 * mLineWidth characters are the screen, the rest are the transcript.
70 * The low byte encodes the ASCII character, the high byte encodes the
71 * foreground and background colors, plus underline and bold.
76 * The data's stored as color-encoded chars, but the drawing routines require chars, so we
77 * need a temporary buffer to hold a row's worth of characters.
79 private char[] mRowBuffer;
82 * Flags that keep track of whether the current line logically wraps to the
83 * next line. This is used when resizing the screen and when copying to the
84 * clipboard or an email attachment
87 private boolean[] mLineWrap;
90 * Create a transcript screen.
92 * @param columns the width of the screen in characters.
93 * @param totalRows the height of the entire text area, in rows of text.
94 * @param screenRows the height of just the screen, not including the
95 * transcript that holds lines that have scrolled off the top of the
98 public TranscriptScreen(int columns, int totalRows, int screenRows,
99 int foreColor, int backColor) {
100 init(columns, totalRows, screenRows, foreColor, backColor);
103 private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
105 mTotalRows = totalRows;
106 mActiveTranscriptRows = 0;
108 mActiveRows = screenRows;
109 mScreenRows = screenRows;
110 int totalSize = columns * totalRows;
111 mData = new char[totalSize];
112 blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
113 mRowBuffer = new char[columns];
114 mLineWrap = new boolean[totalRows];
119 * Convert a row value from the public external coordinate system to our
120 * internal private coordinate system. External coordinate system:
121 * -mActiveTranscriptRows to mScreenRows-1, with the screen being
122 * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
123 * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
124 * transcript, stored as a circular buffer.
126 * @param row a row in the external coordinate system.
127 * @return The row corresponding to the input argument in the private
130 private int externalToInternalRow(int row) {
131 if (row < -mActiveTranscriptRows || row >= mScreenRows) {
132 String errorMessage = "externalToInternalRow "+ row +
133 " " + mActiveTranscriptRows + " " + mScreenRows;
134 Log.e(TAG, errorMessage);
135 throw new IllegalArgumentException(errorMessage);
138 return row; // This is a visible row.
141 + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
144 private int getOffset(int externalLine) {
145 return externalToInternalRow(externalLine) * mColumns;
148 private int getOffset(int x, int y) {
149 return getOffset(y) + x;
152 public void setLineWrap(int row) {
153 mLineWrap[externalToInternalRow(row)] = true;
157 * Store byte b into the screen at location (x, y)
159 * @param x X coordinate (also known as column)
160 * @param y Y coordinate (also known as row)
161 * @param b ASCII character to store
162 * @param foreColor the foreground color
163 * @param backColor the background color
165 public void set(int x, int y, byte b, int foreColor, int backColor) {
166 mData[getOffset(x, y)] = encode(b, foreColor, backColor);
169 private char encode(int b, int foreColor, int backColor) {
170 return (char) ((foreColor << 12) | (backColor << 8) | b);
174 * Scroll the screen down one line. To scroll the whole screen of a 24 line
175 * screen, the arguments would be (0, 24).
177 * @param topMargin First line that is scrolled.
178 * @param bottomMargin One line after the last line that is scrolled.
180 public void scroll(int topMargin, int bottomMargin, int foreColor,
182 // Separate out reasons so that stack crawls help us
183 // figure out which condition was violated.
184 if (topMargin > bottomMargin - 1) {
185 throw new IllegalArgumentException();
188 if (topMargin > mScreenRows - 1) {
189 throw new IllegalArgumentException();
192 if (bottomMargin > mScreenRows) {
193 throw new IllegalArgumentException();
196 // Adjust the transcript so that the last line of the transcript
197 // is ready to receive the newly scrolled data
199 int expansionRows = Math.min(1, mTotalRows - mActiveRows);
200 int rollRows = 1 - expansionRows;
201 mActiveRows += expansionRows;
202 mActiveTranscriptRows += expansionRows;
203 if (mActiveTranscriptRows > 0) {
204 mHead = (mHead + rollRows) % mActiveTranscriptRows;
208 // Block move the scroll line to the transcript
209 int topOffset = getOffset(topMargin);
210 int destOffset = getOffset(-1);
211 System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
213 int topLine = externalToInternalRow(topMargin);
214 int destLine = externalToInternalRow(-1);
215 System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
217 // Block move the scrolled data up
218 int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
219 System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
221 int numScrollLines = (bottomMargin - topMargin - 1);
222 System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
225 // Erase the bottom line of the scroll region
226 blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
227 mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
230 private void consistencyCheck() {
231 checkPositive(mColumns);
232 checkPositive(mTotalRows);
233 checkRange(0, mActiveTranscriptRows, mTotalRows);
234 if (mActiveTranscriptRows == 0) {
235 checkEqual(mHead, 0);
237 checkRange(0, mHead, mActiveTranscriptRows-1);
239 checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
240 checkRange(0, mScreenRows, mTotalRows);
242 checkEqual(mTotalRows, mLineWrap.length);
243 checkEqual(mTotalRows*mColumns, mData.length);
244 checkEqual(mColumns, mRowBuffer.length);
247 private void checkPositive(int n) {
249 throw new IllegalArgumentException("checkPositive " + n);
253 private void checkRange(int a, int b, int c) {
254 if (a > b || b > c) {
255 throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
259 private void checkEqual(int a, int b) {
261 throw new IllegalArgumentException("checkEqual " + a + " == " + b);
266 * Block copy characters from one position in the screen to another. The two
267 * positions can overlap. All characters of the source and destination must
268 * be within the bounds of the screen, or else an InvalidParemeterException
271 * @param sx source X coordinate
272 * @param sy source Y coordinate
275 * @param dx destination X coordinate
276 * @param dy destination Y coordinate
278 public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
279 if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
280 || dx < 0 || dx + w > mColumns || dy < 0
281 || dy + h > mScreenRows) {
282 throw new IllegalArgumentException();
285 // Move in increasing order
286 for (int y = 0; y < h; y++) {
287 int srcOffset = getOffset(sx, sy + y);
288 int dstOffset = getOffset(dx, dy + y);
289 System.arraycopy(mData, srcOffset, mData, dstOffset, w);
292 // Move in decreasing order
293 for (int y = 0; y < h; y++) {
294 int y2 = h - (y + 1);
295 int srcOffset = getOffset(sx, sy + y2);
296 int dstOffset = getOffset(dx, dy + y2);
297 System.arraycopy(mData, srcOffset, mData, dstOffset, w);
303 * Block set characters. All characters must be within the bounds of the
304 * screen, or else and InvalidParemeterException will be thrown. Typically
305 * this is called with a "val" argument of 32 to clear a block of
312 * @param val value to set.
314 public void blockSet(int sx, int sy, int w, int h, int val,
315 int foreColor, int backColor) {
316 if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
317 throw new IllegalArgumentException();
320 char encodedVal = encode(val, foreColor, backColor);
321 for (int y = 0; y < h; y++) {
322 int offset = getOffset(sx, sy + y);
323 for (int x = 0; x < w; x++) {
324 data[offset + x] = encodedVal;
330 * Draw a row of text. Out-of-bounds rows are blank, not errors.
332 * @param row The row of text to draw.
333 * @param canvas The canvas to draw to.
334 * @param x The x coordinate origin of the drawing
335 * @param y The y coordinate origin of the drawing
336 * @param renderer The renderer to use to draw the text
337 * @param cx the cursor X coordinate, -1 means don't draw it
338 * @param selx1 the text selection start X coordinate
339 * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
340 * @param imeText current IME text, to be rendered at cursor
342 public final void drawText(int row, Canvas canvas, float x, float y,
343 TextRenderer renderer, int cx, int selx1, int selx2, String imeText) {
345 // Out-of-bounds rows are blank.
346 if (row < -mActiveTranscriptRows || row >= mScreenRows) {
350 // Copy the data from the byte array to a char array so they can
353 int offset = getOffset(row);
354 char[] rowBuffer = mRowBuffer;
356 int columns = mColumns;
358 int lastRunStart = -1;
359 final int CURSOR_MASK = 0x10000;
360 for (int i = 0; i < columns; i++) {
361 char c = data[offset + i];
362 int colors = (char) (c & 0xff00);
363 if (cx == i || (i >= selx1 && i <= selx2)) {
364 // Set cursor background color:
365 colors |= CURSOR_MASK;
367 rowBuffer[i] = (char) (c & 0x00ff);
368 if (colors != lastColors) {
369 if (lastRunStart >= 0) {
370 renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
371 lastRunStart, i - lastRunStart,
372 (lastColors & CURSOR_MASK) != 0,
373 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
379 if (lastRunStart >= 0) {
380 renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
381 lastRunStart, columns - lastRunStart,
382 (lastColors & CURSOR_MASK) != 0,
383 0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
386 if (cx >= 0 && imeText.length() > 0) {
387 int imeLength = Math.min(columns, imeText.length());
388 int imeOffset = imeText.length() - imeLength;
389 int imePosition = Math.min(cx, columns - imeLength);
390 renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(),
391 imeOffset, imeLength, true, 0x0f, 0x00);
396 * Get the count of active rows.
398 * @return the count of active rows.
400 public int getActiveRows() {
405 * Get the count of active transcript rows.
407 * @return the count of active transcript rows.
409 public int getActiveTranscriptRows() {
410 return mActiveTranscriptRows;
413 public String getTranscriptText() {
414 return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
417 public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
418 return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
421 private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
422 StringBuilder builder = new StringBuilder();
423 char[] rowBuffer = mRowBuffer;
425 int columns = mColumns;
426 for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
427 int offset = getOffset(row);
428 int lastPrintingChar = -1;
429 for (int column = 0; column < columns; column++) {
430 char c = data[offset + column];
432 c = (char) (c & 0xff);
434 if ((c & 0xff) != ' ') {
435 lastPrintingChar = column;
437 rowBuffer[column] = c;
439 if ( row >= selY1 && row <= selY2 ) {
442 if ( row == selY1 ) {
445 if ( row == selY2 ) {
450 if (mLineWrap[externalToInternalRow(row)]) {
451 builder.append(rowBuffer, x1, x2 - x1);
453 builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
454 builder.append('\n');
458 return builder.toString();
461 public void resize(int columns, int rows, int foreColor, int backColor) {
462 init(columns, mTotalRows, rows, foreColor, backColor);