1 /* 2 * ConnectBot: simple, powerful, open-source SSH client for Android 3 * Copyright 2007 Kenny Root, Jeffrey Sharkey 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.connectbot; 19 20 import android.app.Activity; 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.Matrix; 24 import android.graphics.Paint; 25 import android.graphics.Path; 26 import android.graphics.PixelXorXfermode; 27 import android.graphics.RectF; 28 import android.view.View; 29 import android.view.ViewGroup.LayoutParams; 30 import android.view.inputmethod.BaseInputConnection; 31 import android.view.inputmethod.EditorInfo; 32 import android.view.inputmethod.InputConnection; 33 import android.widget.Toast; 34 import de.mud.terminal.VDUBuffer; 35 36 import org.connectbot.service.FontSizeChangedListener; 37 import org.connectbot.service.TerminalBridge; 38 import org.connectbot.service.TerminalKeyListener; 39 import org.connectbot.util.SelectionArea; 40 41 /** 42 * User interface {@link View} for showing a TerminalBridge in an {@link Activity}. Handles drawing 43 * bitmap updates and passing keystrokes down to terminal. 44 */ 45 public class TerminalView extends View implements FontSizeChangedListener { 46 47 private final Context context; 48 public final TerminalBridge bridge; 49 private final Paint paint; 50 private final Paint cursorPaint; 51 private final Paint cursorStrokePaint; 52 53 // Cursor paints to distinguish modes 54 private Path ctrlCursor, altCursor, shiftCursor; 55 private RectF tempSrc, tempDst; 56 private Matrix scaleMatrix; 57 private static final Matrix.ScaleToFit scaleType = Matrix.ScaleToFit.FILL; 58 59 private Toast notification = null; 60 private String lastNotification = null; 61 private volatile boolean notifications = true; 62 TerminalView(Context context, TerminalBridge bridge)63 public TerminalView(Context context, TerminalBridge bridge) { 64 super(context); 65 66 this.context = context; 67 this.bridge = bridge; 68 paint = new Paint(); 69 70 setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); 71 setFocusable(true); 72 setFocusableInTouchMode(true); 73 74 cursorPaint = new Paint(); 75 cursorPaint.setColor(bridge.getForegroundColor()); 76 cursorPaint.setXfermode(new PixelXorXfermode(bridge.getBackgroundColor())); 77 cursorPaint.setAntiAlias(true); 78 79 cursorStrokePaint = new Paint(cursorPaint); 80 cursorStrokePaint.setStrokeWidth(0.1f); 81 cursorStrokePaint.setStyle(Paint.Style.STROKE); 82 83 /* 84 * Set up our cursor indicators on a 1x1 Path object which we can later transform to our 85 * character width and height 86 */ 87 // TODO make this into a resource somehow 88 shiftCursor = new Path(); 89 shiftCursor.lineTo(0.5f, 0.33f); 90 shiftCursor.lineTo(1.0f, 0.0f); 91 92 altCursor = new Path(); 93 altCursor.moveTo(0.0f, 1.0f); 94 altCursor.lineTo(0.5f, 0.66f); 95 altCursor.lineTo(1.0f, 1.0f); 96 97 ctrlCursor = new Path(); 98 ctrlCursor.moveTo(0.0f, 0.25f); 99 ctrlCursor.lineTo(1.0f, 0.5f); 100 ctrlCursor.lineTo(0.0f, 0.75f); 101 102 // For creating the transform when the terminal resizes 103 tempSrc = new RectF(); 104 tempSrc.set(0.0f, 0.0f, 1.0f, 1.0f); 105 tempDst = new RectF(); 106 scaleMatrix = new Matrix(); 107 108 bridge.addFontSizeChangedListener(this); 109 110 // connect our view up to the bridge 111 setOnKeyListener(bridge.getKeyHandler()); 112 } 113 destroy()114 public void destroy() { 115 // tell bridge to destroy its bitmap 116 bridge.parentDestroyed(); 117 } 118 119 @Override onSizeChanged(int w, int h, int oldw, int oldh)120 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 121 super.onSizeChanged(w, h, oldw, oldh); 122 123 bridge.parentChanged(this); 124 125 scaleCursors(); 126 } 127 onFontSizeChanged(float size)128 public void onFontSizeChanged(float size) { 129 scaleCursors(); 130 } 131 scaleCursors()132 private void scaleCursors() { 133 // Create a scale matrix to scale our 1x1 representation of the cursor 134 tempDst.set(0.0f, 0.0f, bridge.charWidth, bridge.charHeight); 135 scaleMatrix.setRectToRect(tempSrc, tempDst, scaleType); 136 } 137 138 @Override onDraw(Canvas canvas)139 public void onDraw(Canvas canvas) { 140 if (bridge.getBitmap() != null) { 141 // draw the bitmap 142 bridge.onDraw(); 143 144 // draw the bridge bitmap if it exists 145 canvas.drawBitmap(bridge.getBitmap(), 0, 0, paint); 146 147 VDUBuffer buffer = bridge.getVDUBuffer(); 148 149 // also draw cursor if visible 150 if (buffer.isCursorVisible()) { 151 152 int cursorColumn = buffer.getCursorColumn(); 153 final int cursorRow = buffer.getCursorRow(); 154 155 final int columns = buffer.getColumns(); 156 157 if (cursorColumn == columns) { 158 cursorColumn = columns - 1; 159 } 160 161 if (cursorColumn < 0 || cursorRow < 0) { 162 return; 163 } 164 165 int currentAttribute = buffer.getAttributes(cursorColumn, cursorRow); 166 boolean onWideCharacter = (currentAttribute & VDUBuffer.FULLWIDTH) != 0; 167 168 int x = cursorColumn * bridge.charWidth; 169 int y = (buffer.getCursorRow() + buffer.screenBase - buffer.windowBase) * bridge.charHeight; 170 171 // Save the current clip and translation 172 canvas.save(); 173 174 canvas.translate(x, y); 175 canvas.clipRect(0, 0, bridge.charWidth * (onWideCharacter ? 2 : 1), bridge.charHeight); 176 canvas.drawPaint(cursorPaint); 177 178 // Make sure we scale our decorations to the correct size. 179 canvas.concat(scaleMatrix); 180 181 int metaState = bridge.getKeyHandler().getMetaState(); 182 183 if ((metaState & TerminalKeyListener.META_SHIFT_ON) != 0) { 184 canvas.drawPath(shiftCursor, cursorStrokePaint); 185 } else if ((metaState & TerminalKeyListener.META_SHIFT_LOCK) != 0) { 186 canvas.drawPath(shiftCursor, cursorPaint); 187 } 188 189 if ((metaState & TerminalKeyListener.META_ALT_ON) != 0) { 190 canvas.drawPath(altCursor, cursorStrokePaint); 191 } else if ((metaState & TerminalKeyListener.META_ALT_LOCK) != 0) { 192 canvas.drawPath(altCursor, cursorPaint); 193 } 194 195 if ((metaState & TerminalKeyListener.META_CTRL_ON) != 0) { 196 canvas.drawPath(ctrlCursor, cursorStrokePaint); 197 } else if ((metaState & TerminalKeyListener.META_CTRL_LOCK) != 0) { 198 canvas.drawPath(ctrlCursor, cursorPaint); 199 } 200 201 // Restore previous clip region 202 canvas.restore(); 203 } 204 205 // draw any highlighted area 206 if (bridge.isSelectingForCopy()) { 207 SelectionArea area = bridge.getSelectionArea(); 208 canvas.save(Canvas.CLIP_SAVE_FLAG); 209 canvas.clipRect(area.getLeft() * bridge.charWidth, area.getTop() * bridge.charHeight, (area 210 .getRight() + 1) 211 * bridge.charWidth, (area.getBottom() + 1) * bridge.charHeight); 212 canvas.drawPaint(cursorPaint); 213 canvas.restore(); 214 } 215 } 216 } 217 notifyUser(String message)218 public void notifyUser(String message) { 219 if (!notifications) { 220 return; 221 } 222 223 if (notification != null) { 224 // Don't keep telling the user the same thing. 225 if (lastNotification != null && lastNotification.equals(message)) { 226 return; 227 } 228 229 notification.setText(message); 230 notification.show(); 231 } else { 232 notification = Toast.makeText(context, message, Toast.LENGTH_SHORT); 233 notification.show(); 234 } 235 236 lastNotification = message; 237 } 238 239 /** 240 * Ask the {@link TerminalBridge} we're connected to to resize to a specific size. 241 * @param width 242 * @param height 243 */ forceSize(int width, int height)244 public void forceSize(int width, int height) { 245 bridge.resizeComputed(width, height, getWidth(), getHeight()); 246 } 247 248 /** 249 * Sets the ability for the TerminalView to display Toast notifications to the user. 250 * @param value 251 * whether to enable notifications or not 252 */ setNotifications(boolean value)253 public void setNotifications(boolean value) { 254 notifications = value; 255 } 256 257 @Override onCheckIsTextEditor()258 public boolean onCheckIsTextEditor() { 259 return true; 260 } 261 262 @Override onCreateInputConnection(EditorInfo outAttrs)263 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 264 outAttrs.imeOptions |= 265 EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_ENTER_ACTION 266 | EditorInfo.IME_ACTION_NONE; 267 outAttrs.inputType = EditorInfo.TYPE_NULL; 268 return new BaseInputConnection(this, false); 269 } 270 } 271