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 * @author jsharkey 45 */ 46 public class TerminalView extends View implements FontSizeChangedListener { 47 48 private final Context context; 49 public final TerminalBridge bridge; 50 private final Paint paint; 51 private final Paint cursorPaint; 52 private final Paint cursorStrokePaint; 53 54 // Cursor paints to distinguish modes 55 private Path ctrlCursor, altCursor, shiftCursor; 56 private RectF tempSrc, tempDst; 57 private Matrix scaleMatrix; 58 private static final Matrix.ScaleToFit scaleType = Matrix.ScaleToFit.FILL; 59 60 private Toast notification = null; 61 private String lastNotification = null; 62 private volatile boolean notifications = true; 63 64 public TerminalView(Context context, TerminalBridge bridge) { 65 super(context); 66 67 this.context = context; 68 this.bridge = bridge; 69 paint = new Paint(); 70 71 setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); 72 setFocusable(true); 73 setFocusableInTouchMode(true); 74 75 cursorPaint = new Paint(); 76 cursorPaint.setColor(bridge.getForegroundColor()); 77 cursorPaint.setXfermode(new PixelXorXfermode(bridge.getBackgroundColor())); 78 cursorPaint.setAntiAlias(true); 79 80 cursorStrokePaint = new Paint(cursorPaint); 81 cursorStrokePaint.setStrokeWidth(0.1f); 82 cursorStrokePaint.setStyle(Paint.Style.STROKE); 83 84 /* 85 * Set up our cursor indicators on a 1x1 Path object which we can later transform to our 86 * character width and height 87 */ 88 // TODO make this into a resource somehow 89 shiftCursor = new Path(); 90 shiftCursor.lineTo(0.5f, 0.33f); 91 shiftCursor.lineTo(1.0f, 0.0f); 92 93 altCursor = new Path(); 94 altCursor.moveTo(0.0f, 1.0f); 95 altCursor.lineTo(0.5f, 0.66f); 96 altCursor.lineTo(1.0f, 1.0f); 97 98 ctrlCursor = new Path(); 99 ctrlCursor.moveTo(0.0f, 0.25f); 100 ctrlCursor.lineTo(1.0f, 0.5f); 101 ctrlCursor.lineTo(0.0f, 0.75f); 102 103 // For creating the transform when the terminal resizes 104 tempSrc = new RectF(); 105 tempSrc.set(0.0f, 0.0f, 1.0f, 1.0f); 106 tempDst = new RectF(); 107 scaleMatrix = new Matrix(); 108 109 bridge.addFontSizeChangedListener(this); 110 111 // connect our view up to the bridge 112 setOnKeyListener(bridge.getKeyHandler()); 113 } 114 115 public void destroy() { 116 // tell bridge to destroy its bitmap 117 bridge.parentDestroyed(); 118 } 119 120 @Override 121 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 122 super.onSizeChanged(w, h, oldw, oldh); 123 124 bridge.parentChanged(this); 125 126 scaleCursors(); 127 } 128 129 public void onFontSizeChanged(float size) { 130 scaleCursors(); 131 } 132 133 private void scaleCursors() { 134 // Create a scale matrix to scale our 1x1 representation of the cursor 135 tempDst.set(0.0f, 0.0f, bridge.charWidth, bridge.charHeight); 136 scaleMatrix.setRectToRect(tempSrc, tempDst, scaleType); 137 } 138 139 @Override 140 public void onDraw(Canvas canvas) { 141 if (bridge.getBitmap() != null) { 142 // draw the bitmap 143 bridge.onDraw(); 144 145 // draw the bridge bitmap if it exists 146 canvas.drawBitmap(bridge.getBitmap(), 0, 0, paint); 147 148 VDUBuffer buffer = bridge.getVDUBuffer(); 149 150 // also draw cursor if visible 151 if (buffer.isCursorVisible()) { 152 153 int cursorColumn = buffer.getCursorColumn(); 154 final int cursorRow = buffer.getCursorRow(); 155 156 final int columns = buffer.getColumns(); 157 158 if (cursorColumn == columns) { 159 cursorColumn = columns - 1; 160 } 161 162 if (cursorColumn < 0 || cursorRow < 0) { 163 return; 164 } 165 166 int currentAttribute = buffer.getAttributes(cursorColumn, cursorRow); 167 boolean onWideCharacter = (currentAttribute & VDUBuffer.FULLWIDTH) != 0; 168 169 int x = cursorColumn * bridge.charWidth; 170 int y = (buffer.getCursorRow() + buffer.screenBase - buffer.windowBase) * bridge.charHeight; 171 172 // Save the current clip and translation 173 canvas.save(); 174 175 canvas.translate(x, y); 176 canvas.clipRect(0, 0, bridge.charWidth * (onWideCharacter ? 2 : 1), bridge.charHeight); 177 canvas.drawPaint(cursorPaint); 178 179 // Make sure we scale our decorations to the correct size. 180 canvas.concat(scaleMatrix); 181 182 int metaState = bridge.getKeyHandler().getMetaState(); 183 184 if ((metaState & TerminalKeyListener.META_SHIFT_ON) != 0) { 185 canvas.drawPath(shiftCursor, cursorStrokePaint); 186 } else if ((metaState & TerminalKeyListener.META_SHIFT_LOCK) != 0) { 187 canvas.drawPath(shiftCursor, cursorPaint); 188 } 189 190 if ((metaState & TerminalKeyListener.META_ALT_ON) != 0) { 191 canvas.drawPath(altCursor, cursorStrokePaint); 192 } else if ((metaState & TerminalKeyListener.META_ALT_LOCK) != 0) { 193 canvas.drawPath(altCursor, cursorPaint); 194 } 195 196 if ((metaState & TerminalKeyListener.META_CTRL_ON) != 0) { 197 canvas.drawPath(ctrlCursor, cursorStrokePaint); 198 } else if ((metaState & TerminalKeyListener.META_CTRL_LOCK) != 0) { 199 canvas.drawPath(ctrlCursor, cursorPaint); 200 } 201 202 // Restore previous clip region 203 canvas.restore(); 204 } 205 206 // draw any highlighted area 207 if (bridge.isSelectingForCopy()) { 208 SelectionArea area = bridge.getSelectionArea(); 209 canvas.save(Canvas.CLIP_SAVE_FLAG); 210 canvas.clipRect(area.getLeft() * bridge.charWidth, area.getTop() * bridge.charHeight, (area 211 .getRight() + 1) 212 * bridge.charWidth, (area.getBottom() + 1) * bridge.charHeight); 213 canvas.drawPaint(cursorPaint); 214 canvas.restore(); 215 } 216 } 217 } 218 219 public void notifyUser(String message) { 220 if (!notifications) { 221 return; 222 } 223 224 if (notification != null) { 225 // Don't keep telling the user the same thing. 226 if (lastNotification != null && lastNotification.equals(message)) { 227 return; 228 } 229 230 notification.setText(message); 231 notification.show(); 232 } else { 233 notification = Toast.makeText(context, message, Toast.LENGTH_SHORT); 234 notification.show(); 235 } 236 237 lastNotification = message; 238 } 239 240 /** 241 * Ask the {@link TerminalBridge} we're connected to to resize to a specific size. 242 * @param width 243 * @param height 244 */ 245 public void forceSize(int width, int height) { 246 bridge.resizeComputed(width, height, getWidth(), getHeight()); 247 } 248 249 /** 250 * Sets the ability for the TerminalView to display Toast notifications to the user. 251 * @param value 252 * whether to enable notifications or not 253 */ 254 public void setNotifications(boolean value) { 255 notifications = value; 256 } 257 258 @Override 259 public boolean onCheckIsTextEditor() { 260 return true; 261 } 262 263 @Override 264 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 265 outAttrs.imeOptions |= 266 EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_ENTER_ACTION 267 | EditorInfo.IME_ACTION_NONE; 268 outAttrs.inputType = EditorInfo.TYPE_NULL; 269 return new BaseInputConnection(this, false); 270 } 271 } 272