• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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