| /* | |
| * 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. | |
| */ | |
| 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); | |
| } | |
| } |