/* * Copyright (C) 2007 The Android Open Source Project * Copyright (C) 2011 John Pritchard, Syntelos * * 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 ob.droid.term; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Typeface; /** * A TranscriptScreen is a screen that remembers data that's been scrolled. The * old data is stored in a ring buffer to minimize the amount of copying that * needs to be done. The transcript does its own drawing, to avoid having to * expose its internal data structures. */ class TranscriptScreen implements Screen { /** * The width of the transcript, in characters. Fixed at initialization. */ private int mColumns; /** * The total number of rows in the transcript and the screen. Fixed at * initialization. */ private int mTotalRows; /** * The number of rows in the active portion of the transcript. Doesn't * include the screen. */ private int mActiveTranscriptRows; /** * Which row is currently the topmost line of the transcript. Used to * implement a circular buffer. */ private int mHead; /** * The number of active rows, includes both the transcript and the screen. */ private int mActiveRows; /** * The number of rows in the screen. */ private int mScreenRows; /** * The data for both the screen and the transcript. The first mScreenRows * * mLineWidth characters are the screen, the rest are the transcript. * The low byte encodes the ASCII character, the high byte encodes the * foreground and background colors, plus underline and bold. */ private char[] mData; /** * The data's stored as color-encoded chars, but the drawing routines require chars, so we * need a temporary buffer to hold a row's worth of characters. */ private char[] mRowBuffer; /** * Flags that keep track of whether the current line logically wraps to the * next line. This is used when resizing the screen and when copying to the * clipboard or an email attachment */ private boolean[] mLineWrap; /** * Create a transcript screen. * * @param columns the width of the screen in characters. * @param totalRows the height of the entire text area, in rows of text. * @param screenRows the height of just the screen, not including the * transcript that holds lines that have scrolled off the top of the * screen. */ public TranscriptScreen(int columns, int totalRows, int screenRows, int foreColor, int backColor) { init(columns, totalRows, screenRows, foreColor, backColor); } private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) { mColumns = columns; mTotalRows = totalRows; mActiveTranscriptRows = 0; mHead = 0; mActiveRows = screenRows; mScreenRows = screenRows; int totalSize = columns * totalRows; mData = new char[totalSize]; blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); mRowBuffer = new char[columns]; mLineWrap = new boolean[totalRows]; consistencyCheck(); } /** * Convert a row value from the public external coordinate system to our * internal private coordinate system. External coordinate system: * -mActiveTranscriptRows to mScreenRows-1, with the screen being * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of * mData are the visible rows. mScreenRows..mActiveRows - 1 are the * transcript, stored as a circular buffer. * * @param row a row in the external coordinate system. * @return The row corresponding to the input argument in the private * coordinate system. */ private int externalToInternalRow(int row) { if (row < -mActiveTranscriptRows || row >= mScreenRows) { throw new IllegalArgumentException(); } if (row >= 0) { return row; // This is a visible row. } return mScreenRows + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows); } private int getOffset(int externalLine) { return externalToInternalRow(externalLine) * mColumns; } private int getOffset(int x, int y) { return getOffset(y) + x; } public void setLineWrap(int row) { mLineWrap[externalToInternalRow(row)] = true; } /** * Store byte b into the screen at location (x, y) * * @param x X coordinate (also known as column) * @param y Y coordinate (also known as row) * @param b ASCII character to store * @param foreColor the foreground color * @param backColor the background color */ public void set(int x, int y, byte b, int foreColor, int backColor) { mData[getOffset(x, y)] = encode(b, foreColor, backColor); } private char encode(int b, int foreColor, int backColor) { return (char) ((foreColor << 12) | (backColor << 8) | b); } /** * Scroll the screen down one line. To scroll the whole screen of a 24 line * screen, the arguments would be (0, 24). * * @param topMargin First line that is scrolled. * @param bottomMargin One line after the last line that is scrolled. */ public void scroll(int topMargin, int bottomMargin, int foreColor, int backColor) { if (topMargin > bottomMargin - 2 || topMargin > mScreenRows - 2 || bottomMargin > mScreenRows) { throw new IllegalArgumentException(); } // Adjust the transcript so that the last line of the transcript // is ready to receive the newly scrolled data consistencyCheck(); int expansionRows = Math.min(1, mTotalRows - mActiveRows); int rollRows = 1 - expansionRows; mActiveRows += expansionRows; mActiveTranscriptRows += expansionRows; if (mActiveTranscriptRows > 0) { mHead = (mHead + rollRows) % mActiveTranscriptRows; } consistencyCheck(); // Block move the scroll line to the transcript int topOffset = getOffset(topMargin); int destOffset = getOffset(-1); System.arraycopy(mData, topOffset, mData, destOffset, mColumns); int topLine = externalToInternalRow(topMargin); int destLine = externalToInternalRow(-1); System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1); // Block move the scrolled data up int numScrollChars = (bottomMargin - topMargin - 1) * mColumns; System.arraycopy(mData, topOffset + mColumns, mData, topOffset, numScrollChars); int numScrollLines = (bottomMargin - topMargin - 1); System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine, numScrollLines); // Erase the bottom line of the scroll region blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor); mLineWrap[externalToInternalRow(bottomMargin-1)] = false; } private void consistencyCheck() { checkPositive(mColumns); checkPositive(mTotalRows); checkRange(0, mActiveTranscriptRows, mTotalRows); if (mActiveTranscriptRows == 0) { checkEqual(mHead, 0); } else { checkRange(0, mHead, mActiveTranscriptRows-1); } checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows); checkRange(0, mScreenRows, mTotalRows); checkEqual(mTotalRows, mLineWrap.length); checkEqual(mTotalRows*mColumns, mData.length); checkEqual(mColumns, mRowBuffer.length); } private void checkPositive(int n) { if (n < 0) { throw new IllegalArgumentException("checkPositive " + n); } } private void checkRange(int a, int b, int c) { if (a > b || b > c) { throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c); } } private void checkEqual(int a, int b) { if (a != b) { throw new IllegalArgumentException("checkEqual " + a + " == " + b); } } /** * Block copy characters from one position in the screen to another. The two * positions can overlap. All characters of the source and destination must * be within the bounds of the screen, or else an InvalidParemeterException * will be thrown. * * @param sx source X coordinate * @param sy source Y coordinate * @param w width * @param h height * @param dx destination X coordinate * @param dy destination Y coordinate */ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows) { throw new IllegalArgumentException(); } if (sy <= dy) { // Move in increasing order for (int y = 0; y < h; y++) { int srcOffset = getOffset(sx, sy + y); int dstOffset = getOffset(dx, dy + y); System.arraycopy(mData, srcOffset, mData, dstOffset, w); } } else { // Move in decreasing order for (int y = 0; y < h; y++) { int y2 = h - (y + 1); int srcOffset = getOffset(sx, sy + y2); int dstOffset = getOffset(dx, dy + y2); System.arraycopy(mData, srcOffset, mData, dstOffset, w); } } } /** * Block set characters. All characters must be within the bounds of the * screen, or else and InvalidParemeterException will be thrown. Typically * this is called with a "val" argument of 32 to clear a block of * characters. * * @param sx source X * @param sy source Y * @param w width * @param h height * @param val value to set. */ public void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int backColor) { if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { throw new IllegalArgumentException(); } char[] data = mData; char encodedVal = encode(val, foreColor, backColor); for (int y = 0; y < h; y++) { int offset = getOffset(sx, sy + y); for (int x = 0; x < w; x++) { data[offset + x] = encodedVal; } } } /** * Draw a row of text. Out-of-bounds rows are blank, not errors. * * @param row The row of text to draw. * @param canvas The canvas to draw to. * @param x The x coordinate origin of the drawing * @param y The y coordinate origin of the drawing * @param renderer The renderer to use to draw the text * @param cx the cursor X coordinate, -1 means don't draw it */ public final void drawText(int row, Canvas canvas, float x, float y, TextRenderer renderer, int cx) { // Out-of-bounds rows are blank. if (row < -mActiveTranscriptRows || row >= mScreenRows) { return; } // Copy the data from the byte array to a char array so they can // be drawn. int offset = getOffset(row); char[] rowBuffer = mRowBuffer; char[] data = mData; int columns = mColumns; int lastColors = 0; int lastRunStart = -1; final int CURSOR_MASK = 0x10000; for (int i = 0; i < columns; i++) { char c = data[offset + i]; int colors = (char) (c & 0xff00); if (cx == i) { // Set cursor background color: colors |= CURSOR_MASK; } rowBuffer[i] = (char) (c & 0x00ff); if (colors != lastColors) { if (lastRunStart >= 0) { renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, lastRunStart, i - lastRunStart, (lastColors & CURSOR_MASK) != 0, 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); } lastColors = colors; lastRunStart = i; } } if (lastRunStart >= 0) { renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, lastRunStart, columns - lastRunStart, (lastColors & CURSOR_MASK) != 0, 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); } } /** * Get the count of active rows. * * @return the count of active rows. */ public int getActiveRows() { return mActiveRows; } /** * Get the count of active transcript rows. * * @return the count of active transcript rows. */ public int getActiveTranscriptRows() { return mActiveTranscriptRows; } public String getTranscriptText() { return internalGetTranscriptText(true); } private String internalGetTranscriptText(boolean stripColors) { StringBuilder builder = new StringBuilder(); char[] rowBuffer = mRowBuffer; char[] data = mData; int columns = mColumns; for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) { int offset = getOffset(row); int lastPrintingChar = -1; for (int column = 0; column < columns; column++) { char c = data[offset + column]; if (stripColors) { c = (char) (c & 0xff); } if ((c & 0xff) != ' ') { lastPrintingChar = column; } rowBuffer[column] = c; } if (mLineWrap[externalToInternalRow(row)]) { builder.append(rowBuffer, 0, columns); } else { builder.append(rowBuffer, 0, lastPrintingChar + 1); builder.append('\n'); } } return builder.toString(); } public void resize(int columns, int rows, int foreColor, int backColor) { init(columns, mTotalRows, rows, foreColor, backColor); } }