• 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  * @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 
TerminalView(Context context, TerminalBridge bridge)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 
destroy()115   public void destroy() {
116     // tell bridge to destroy its bitmap
117     bridge.parentDestroyed();
118   }
119 
120   @Override
onSizeChanged(int w, int h, int oldw, int oldh)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 
onFontSizeChanged(float size)129   public void onFontSizeChanged(float size) {
130     scaleCursors();
131   }
132 
scaleCursors()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
onDraw(Canvas canvas)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 
notifyUser(String message)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    */
forceSize(int width, int height)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    */
setNotifications(boolean value)254   public void setNotifications(boolean value) {
255     notifications = value;
256   }
257 
258   @Override
onCheckIsTextEditor()259   public boolean onCheckIsTextEditor() {
260     return true;
261   }
262 
263   @Override
onCreateInputConnection(EditorInfo outAttrs)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