OSDN Git Service

The fact that Term.java contains substantially all of the classes of the
[android-x86/packages-apps-AndroidTerm.git] / src / jackpal / androidterm / session / TranscriptScreen.java
1 /*
2  * Copyright (C) 2007 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
17 package jackpal.androidterm.session;
18
19 import android.graphics.Canvas;
20 import android.util.Log;
21
22 import jackpal.androidterm.model.Screen;
23 import jackpal.androidterm.model.TextRenderer;
24
25 /**
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.
30  */
31 public class TranscriptScreen implements Screen {
32     private static final String TAG = "TranscriptScreen";
33
34     /**
35      * The width of the transcript, in characters. Fixed at initialization.
36      */
37     private int mColumns;
38
39     /**
40      * The total number of rows in the transcript and the screen. Fixed at
41      * initialization.
42      */
43     private int mTotalRows;
44
45     /**
46      * The number of rows in the active portion of the transcript. Doesn't
47      * include the screen.
48      */
49     private int mActiveTranscriptRows;
50
51     /**
52      * Which row is currently the topmost line of the transcript. Used to
53      * implement a circular buffer.
54      */
55     private int mHead;
56
57     /**
58      * The number of active rows, includes both the transcript and the screen.
59      */
60     private int mActiveRows;
61
62     /**
63      * The number of rows in the screen.
64      */
65     private int mScreenRows;
66
67     /**
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.
72      */
73     private char[] mData;
74
75     /**
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.
78      */
79     private char[] mRowBuffer;
80
81     /**
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
85      */
86
87     private boolean[] mLineWrap;
88
89     /**
90      * Create a transcript screen.
91      *
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
96      *        screen.
97      */
98     public TranscriptScreen(int columns, int totalRows, int screenRows,
99             int foreColor, int backColor) {
100         init(columns, totalRows, screenRows, foreColor, backColor);
101     }
102
103     private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
104         mColumns = columns;
105         mTotalRows = totalRows;
106         mActiveTranscriptRows = 0;
107         mHead = 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];
115         consistencyCheck();
116    }
117
118     /**
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.
125      *
126      * @param row a row in the external coordinate system.
127      * @return The row corresponding to the input argument in the private
128      *         coordinate system.
129      */
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);
136         }
137         if (row >= 0) {
138             return row; // This is a visible row.
139         }
140         return mScreenRows
141                 + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
142     }
143
144     private int getOffset(int externalLine) {
145         return externalToInternalRow(externalLine) * mColumns;
146     }
147
148     private int getOffset(int x, int y) {
149         return getOffset(y) + x;
150     }
151
152     public void setLineWrap(int row) {
153         mLineWrap[externalToInternalRow(row)] = true;
154     }
155
156     /**
157      * Store byte b into the screen at location (x, y)
158      *
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
164      */
165     public void set(int x, int y, byte b, int foreColor, int backColor) {
166         mData[getOffset(x, y)] = encode(b, foreColor, backColor);
167     }
168
169     private char encode(int b, int foreColor, int backColor) {
170         return (char) ((foreColor << 12) | (backColor << 8) | b);
171     }
172
173     /**
174      * Scroll the screen down one line. To scroll the whole screen of a 24 line
175      * screen, the arguments would be (0, 24).
176      *
177      * @param topMargin First line that is scrolled.
178      * @param bottomMargin One line after the last line that is scrolled.
179      */
180     public void scroll(int topMargin, int bottomMargin, int foreColor,
181             int backColor) {
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();
186         }
187
188         if (topMargin > mScreenRows - 1) {
189             throw new IllegalArgumentException();
190         }
191
192         if (bottomMargin > mScreenRows) {
193             throw new IllegalArgumentException();
194         }
195
196         // Adjust the transcript so that the last line of the transcript
197         // is ready to receive the newly scrolled data
198         consistencyCheck();
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;
205         }
206         consistencyCheck();
207
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);
212
213         int topLine = externalToInternalRow(topMargin);
214         int destLine = externalToInternalRow(-1);
215         System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
216
217         // Block move the scrolled data up
218         int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
219         System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
220                 numScrollChars);
221         int numScrollLines = (bottomMargin - topMargin - 1);
222         System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
223                 numScrollLines);
224
225         // Erase the bottom line of the scroll region
226         blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
227         mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
228     }
229
230     private void consistencyCheck() {
231         checkPositive(mColumns);
232         checkPositive(mTotalRows);
233         checkRange(0, mActiveTranscriptRows, mTotalRows);
234         if (mActiveTranscriptRows == 0) {
235             checkEqual(mHead, 0);
236         } else {
237             checkRange(0, mHead, mActiveTranscriptRows-1);
238         }
239         checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
240         checkRange(0, mScreenRows, mTotalRows);
241
242         checkEqual(mTotalRows, mLineWrap.length);
243         checkEqual(mTotalRows*mColumns, mData.length);
244         checkEqual(mColumns, mRowBuffer.length);
245     }
246
247     private void checkPositive(int n) {
248         if (n < 0) {
249             throw new IllegalArgumentException("checkPositive " + n);
250         }
251     }
252
253     private void checkRange(int a, int b, int c) {
254         if (a > b || b > c) {
255             throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
256         }
257     }
258
259     private void checkEqual(int a, int b) {
260         if (a != b) {
261             throw new IllegalArgumentException("checkEqual " + a + " == " + b);
262         }
263     }
264
265     /**
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
269      * will be thrown.
270      *
271      * @param sx source X coordinate
272      * @param sy source Y coordinate
273      * @param w width
274      * @param h height
275      * @param dx destination X coordinate
276      * @param dy destination Y coordinate
277      */
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();
283         }
284         if (sy > dy) {
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);
290             }
291         } else {
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);
298             }
299         }
300     }
301
302     /**
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
306      * characters.
307      *
308      * @param sx source X
309      * @param sy source Y
310      * @param w width
311      * @param h height
312      * @param val value to set.
313      */
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();
318         }
319         char[] data = mData;
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;
325             }
326         }
327     }
328
329     /**
330      * Draw a row of text. Out-of-bounds rows are blank, not errors.
331      *
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
341      */
342     public final void drawText(int row, Canvas canvas, float x, float y,
343             TextRenderer renderer, int cx, int selx1, int selx2, String imeText) {
344
345         // Out-of-bounds rows are blank.
346         if (row < -mActiveTranscriptRows || row >= mScreenRows) {
347             return;
348         }
349
350         // Copy the data from the byte array to a char array so they can
351         // be drawn.
352
353         int offset = getOffset(row);
354         char[] rowBuffer = mRowBuffer;
355         char[] data = mData;
356         int columns = mColumns;
357         int lastColors = 0;
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;
366             }
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));
374                 }
375                 lastColors = colors;
376                 lastRunStart = i;
377             }
378         }
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));
384         }
385
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);
392         }
393      }
394
395     /**
396      * Get the count of active rows.
397      *
398      * @return the count of active rows.
399      */
400     public int getActiveRows() {
401         return mActiveRows;
402     }
403
404     /**
405      * Get the count of active transcript rows.
406      *
407      * @return the count of active transcript rows.
408      */
409     public int getActiveTranscriptRows() {
410         return mActiveTranscriptRows;
411     }
412
413     public String getTranscriptText() {
414         return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
415     }
416
417     public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
418         return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
419     }
420
421     private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
422         StringBuilder builder = new StringBuilder();
423         char[] rowBuffer = mRowBuffer;
424         char[] data = mData;
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];
431                 if (stripColors) {
432                     c = (char) (c & 0xff);
433                 }
434                 if ((c & 0xff) != ' ') {
435                     lastPrintingChar = column;
436                 }
437                 rowBuffer[column] = c;
438             }
439             if ( row >= selY1 && row <= selY2 ) {
440                 int x1 = 0;
441                 int x2 = 0;
442                 if ( row == selY1 ) {
443                     x1 = selX1;
444                 }
445                 if ( row == selY2 ) {
446                     x2 = selX2;
447                 } else {
448                     x2 = columns;
449                 }
450                 if (mLineWrap[externalToInternalRow(row)]) {
451                     builder.append(rowBuffer, x1, x2 - x1);
452                 } else {
453                     builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
454                     builder.append('\n');
455                 }
456             }
457         }
458         return builder.toString();
459     }
460
461     public void resize(int columns, int rows, int foreColor, int backColor) {
462         init(columns, mTotalRows, rows, foreColor, backColor);
463     }
464 }