/* * ConnectBot: simple, powerful, open-source SSH client for Android * Copyright 2007 Kenny Root, Jeffrey Sharkey * * 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 org.connectbot; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelXorXfermode; import android.graphics.RectF; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.widget.Toast; import de.mud.terminal.VDUBuffer; import org.connectbot.service.FontSizeChangedListener; import org.connectbot.service.TerminalBridge; import org.connectbot.service.TerminalKeyListener; import org.connectbot.util.SelectionArea; /** * User interface {@link View} for showing a TerminalBridge in an {@link Activity}. Handles drawing * bitmap updates and passing keystrokes down to terminal. * @author jsharkey */ public class TerminalView extends View implements FontSizeChangedListener { private final Context context; public final TerminalBridge bridge; private final Paint paint; private final Paint cursorPaint; private final Paint cursorStrokePaint; // Cursor paints to distinguish modes private Path ctrlCursor, altCursor, shiftCursor; private RectF tempSrc, tempDst; private Matrix scaleMatrix; private static final Matrix.ScaleToFit scaleType = Matrix.ScaleToFit.FILL; private Toast notification = null; private String lastNotification = null; private volatile boolean notifications = true; public TerminalView(Context context, TerminalBridge bridge) { super(context); this.context = context; this.bridge = bridge; paint = new Paint(); setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); setFocusable(true); setFocusableInTouchMode(true); cursorPaint = new Paint(); cursorPaint.setColor(bridge.getForegroundColor()); cursorPaint.setXfermode(new PixelXorXfermode(bridge.getBackgroundColor())); cursorPaint.setAntiAlias(true); cursorStrokePaint = new Paint(cursorPaint); cursorStrokePaint.setStrokeWidth(0.1f); cursorStrokePaint.setStyle(Paint.Style.STROKE); /* * Set up our cursor indicators on a 1x1 Path object which we can later transform to our * character width and height */ // TODO make this into a resource somehow shiftCursor = new Path(); shiftCursor.lineTo(0.5f, 0.33f); shiftCursor.lineTo(1.0f, 0.0f); altCursor = new Path(); altCursor.moveTo(0.0f, 1.0f); altCursor.lineTo(0.5f, 0.66f); altCursor.lineTo(1.0f, 1.0f); ctrlCursor = new Path(); ctrlCursor.moveTo(0.0f, 0.25f); ctrlCursor.lineTo(1.0f, 0.5f); ctrlCursor.lineTo(0.0f, 0.75f); // For creating the transform when the terminal resizes tempSrc = new RectF(); tempSrc.set(0.0f, 0.0f, 1.0f, 1.0f); tempDst = new RectF(); scaleMatrix = new Matrix(); bridge.addFontSizeChangedListener(this); // connect our view up to the bridge setOnKeyListener(bridge.getKeyHandler()); } public void destroy() { // tell bridge to destroy its bitmap bridge.parentDestroyed(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); bridge.parentChanged(this); scaleCursors(); } public void onFontSizeChanged(float size) { scaleCursors(); } private void scaleCursors() { // Create a scale matrix to scale our 1x1 representation of the cursor tempDst.set(0.0f, 0.0f, bridge.charWidth, bridge.charHeight); scaleMatrix.setRectToRect(tempSrc, tempDst, scaleType); } @Override public void onDraw(Canvas canvas) { if (bridge.getBitmap() != null) { // draw the bitmap bridge.onDraw(); // draw the bridge bitmap if it exists canvas.drawBitmap(bridge.getBitmap(), 0, 0, paint); VDUBuffer buffer = bridge.getVDUBuffer(); // also draw cursor if visible if (buffer.isCursorVisible()) { int cursorColumn = buffer.getCursorColumn(); final int cursorRow = buffer.getCursorRow(); final int columns = buffer.getColumns(); if (cursorColumn == columns) { cursorColumn = columns - 1; } if (cursorColumn < 0 || cursorRow < 0) { return; } int currentAttribute = buffer.getAttributes(cursorColumn, cursorRow); boolean onWideCharacter = (currentAttribute & VDUBuffer.FULLWIDTH) != 0; int x = cursorColumn * bridge.charWidth; int y = (buffer.getCursorRow() + buffer.screenBase - buffer.windowBase) * bridge.charHeight; // Save the current clip and translation canvas.save(); canvas.translate(x, y); canvas.clipRect(0, 0, bridge.charWidth * (onWideCharacter ? 2 : 1), bridge.charHeight); canvas.drawPaint(cursorPaint); // Make sure we scale our decorations to the correct size. canvas.concat(scaleMatrix); int metaState = bridge.getKeyHandler().getMetaState(); if ((metaState & TerminalKeyListener.META_SHIFT_ON) != 0) { canvas.drawPath(shiftCursor, cursorStrokePaint); } else if ((metaState & TerminalKeyListener.META_SHIFT_LOCK) != 0) { canvas.drawPath(shiftCursor, cursorPaint); } if ((metaState & TerminalKeyListener.META_ALT_ON) != 0) { canvas.drawPath(altCursor, cursorStrokePaint); } else if ((metaState & TerminalKeyListener.META_ALT_LOCK) != 0) { canvas.drawPath(altCursor, cursorPaint); } if ((metaState & TerminalKeyListener.META_CTRL_ON) != 0) { canvas.drawPath(ctrlCursor, cursorStrokePaint); } else if ((metaState & TerminalKeyListener.META_CTRL_LOCK) != 0) { canvas.drawPath(ctrlCursor, cursorPaint); } // Restore previous clip region canvas.restore(); } // draw any highlighted area if (bridge.isSelectingForCopy()) { SelectionArea area = bridge.getSelectionArea(); canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipRect(area.getLeft() * bridge.charWidth, area.getTop() * bridge.charHeight, (area .getRight() + 1) * bridge.charWidth, (area.getBottom() + 1) * bridge.charHeight); canvas.drawPaint(cursorPaint); canvas.restore(); } } } public void notifyUser(String message) { if (!notifications) { return; } if (notification != null) { // Don't keep telling the user the same thing. if (lastNotification != null && lastNotification.equals(message)) { return; } notification.setText(message); notification.show(); } else { notification = Toast.makeText(context, message, Toast.LENGTH_SHORT); notification.show(); } lastNotification = message; } /** * Ask the {@link TerminalBridge} we're connected to to resize to a specific size. * @param width * @param height */ public void forceSize(int width, int height) { bridge.resizeComputed(width, height, getWidth(), getHeight()); } /** * Sets the ability for the TerminalView to display Toast notifications to the user. * @param value * whether to enable notifications or not */ public void setNotifications(boolean value) { notifications = value; } @Override public boolean onCheckIsTextEditor() { return true; } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_ENTER_ACTION | EditorInfo.IME_ACTION_NONE; outAttrs.inputType = EditorInfo.TYPE_NULL; return new BaseInputConnection(this, false); } }