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