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  */
     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 
     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 
    114   public void destroy() {
    115     // tell bridge to destroy its bitmap
    116     bridge.parentDestroyed();
    117   }
    118 
    119   @Override
    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 
    128   public void onFontSizeChanged(float size) {
    129     scaleCursors();
    130   }
    131 
    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
    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 
    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    */
    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    */
    253   public void setNotifications(boolean value) {
    254     notifications = value;
    255   }
    256 
    257   @Override
    258   public boolean onCheckIsTextEditor() {
    259     return true;
    260   }
    261 
    262   @Override
    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