Home | History | Annotate | Download | only in connectbot
      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