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