package tw.kenshinn.keyboardTerm; import java.io.IOException; import java.lang.reflect.Array; import java.net.SocketException; import java.util.ArrayList; import java.util.Arrays; import android.R.bool; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.Bitmap.Config; import android.graphics.Region.Op; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.preference.Preference; import android.preference.PreferenceManager; import android.text.InputType; import android.text.method.MetaKeyKeyListener; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import com.roiding.rterm.bean.Host; import com.roiding.rterm.util.ChineseUtils; import de.mud.jta.Wrapper; import de.mud.telnet.TelnetWrapper; import de.mud.terminal.VDUBuffer; import de.mud.terminal.VDUDisplay; import de.mud.terminal.vt320; public class TerminalView extends View implements VDUDisplay { final String TAG = "TerminalView"; private ArrayList<Url>[] urls; // private static final String M_FIXCHARS_STRING = "gjy"; // private static final String M_FIXCHARS2_STRING = "pq_"; private static final int TERM_WIDTH = 80; private static final int TERM_HEIGHT = 24; public float CHAR_WIDTH; public float CHAR_HEIGHT; private float CHAR_POS_FIX; private int SCREEN_WIDTH; private int SCREEN_HEIGHT; private static final int SCROLLBACK = 0; private static final int DEFAULT_FG_COLOR = 7; private static final int DEFAULT_BG_COLOR = 0; private boolean warningLoginAgain = false; private static final int DELAY_WARNING_LOGIN_AGAIN = 15000; public static final KeyCharacterMap DEFAULT_KEYMAP = KeyCharacterMap .load(KeyCharacterMap.BUILT_IN_KEYBOARD); public Bitmap bitmap; public VDUBuffer buffer; public int color[]; public boolean connected; private boolean fullRedraw = true; private final Paint defaultPaint = new Paint(); private Paint specialPaint = new Paint(); private final Paint cursorPaint = new Paint(); private Typeface specialTypeface; private float specialDecent; private final Canvas canvas = new Canvas(); private boolean ctrlPressed; private long metaState = 0; private SharedPreferences mPref; private String mRadio = ""; public Wrapper connection; public TerminalActivity terminalActivity; public Host host; public boolean debug = false; static final int URL_RANGE = 2; // (2mm) private float mUrlRangePx = 0; public TerminalView(TerminalActivity context, AttributeSet attrs) { super(context, attrs); this.terminalActivity = context; setFocusable(true); setFocusableInTouchMode(true); init(); } public void init() { mUrlRangePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, URL_RANGE, terminalActivity.getResources().getDisplayMetrics()); resetColors(); mPref = PreferenceManager.getDefaultSharedPreferences(this.getContext()); buffer = new vt320() { public void beep() { Log.i(TAG, "beep"); } public void write(byte[] b) { try { connection.write(b); } catch (Exception e) { terminalActivity.disconnect(e); } } }; buffer.setBufferSize(SCROLLBACK); buffer.setDisplay(this); buffer.setScreenSize(TERM_WIDTH, TERM_HEIGHT, true); defaultPaint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG); defaultPaint.setTypeface(Typeface.MONOSPACE); specialTypeface = Typeface.createFromAsset(this.getResources().getAssets(), "SpecialChar.ttf"); // Workaround to create array of ArrayList generic type. urls = (ArrayList<Url>[]) Array.newInstance(ArrayList.class, TERM_HEIGHT); } /** * Change the size of virtual screen. * * This will change the size of canvas we draw on. * Which is planed could be bigger than actual screen size and scrolled by user. * @param w width * @param h height */ public void setVScreenSize(int w, int h){ Log.d(TAG,"Set VScreen to ("+w+","+h+")"); // Prepare bitmap and canvas for us to draw on bitmap = Bitmap.createBitmap(w, h,Config.ARGB_8888); canvas.setBitmap(bitmap); defaultPaint.setColor(Color.BLACK); canvas.drawRect(0, 0, w, h, defaultPaint); //Calculate char size CHAR_WIDTH = (float) w / TERM_WIDTH; CHAR_HEIGHT = (float) h / TERM_HEIGHT; //defaultPaint.setTextSize(CHAR_HEIGHT / 21 * 19.4f); defaultPaint.setTextSize(CHAR_HEIGHT); defaultPaint.setTextScaleX(CHAR_WIDTH*2/CHAR_HEIGHT); // Because canvas.drawText() use baseline to position // Calculate the distance between baseline and top line for convenience // I think we have to make a private method for decent measure? Rect bound = new Rect(); defaultPaint.getTextBounds("龜", 0, 1,bound); // I know this is dirty, anyone have a better solution? CHAR_POS_FIX = CHAR_HEIGHT - bound.bottom; specialPaint = new Paint(defaultPaint); //specialPaint.setTextSize(CHAR_HEIGHT); specialPaint.setTypeface(specialTypeface); specialPaint.getTextBounds("▇", 0, 1,bound); specialDecent = CHAR_HEIGHT - bound.bottom; // Draw fullRedraw = true; redraw(); this.postInvalidate(); terminalActivity.refreshView(); } @Override protected void onSizeChanged (int w, int h, int oldw, int oldh){ SCREEN_HEIGHT = h; SCREEN_WIDTH = w; String radio = mPref.getString("settings_screen_radio", "0"); //This is when android start us. use this w/h as base size unless user zoom if(oldw == 0){ if(!mRadio.equals(radio)) { if(radio.equals("5:3")) { h = (int)(w * 0.6f); } else if (radio.equals("6:4")) { h = (int)(w * (4f / 6f)); } else if (radio.equals("8:5")) { h = (int)(w * (5f / 8f)); } mRadio = radio; } setVScreenSize(w,h); } } /** * Render magnifier. * * @param canvas * @param drawArea * @param Focus */ /* I can't come up with good way to write magnifier. * So.. just do it the worst say. */ public void renderMagnifier(Canvas c,Rect drawArea,RectF focus){ // Create a bitmap that can cover canvas all text Rect renderRegion = new Rect( (int)(focus.left / CHAR_WIDTH), (int)(focus.top / CHAR_HEIGHT), (int)Math.ceil(focus.right / CHAR_WIDTH), (int)Math.ceil(focus.bottom / CHAR_HEIGHT) ); float ratioX = drawArea.width() / focus.width(); float ratioY = drawArea.height() / focus.height(); Bitmap magBitmap = Bitmap.createBitmap((int)(renderRegion.width()*CHAR_WIDTH*ratioX),(int)(renderRegion.height()*CHAR_HEIGHT*ratioY), Config.ARGB_8888); Canvas magedCanvas = new Canvas(magBitmap); Paint paint = new Paint(defaultPaint); paint.setTextSize(CHAR_HEIGHT*ratioY); paint.setTextScaleX(paint.getTextScaleX()*ratioY/ratioX); renderText(magedCanvas,new RectF(0,0,magedCanvas.getWidth(),magedCanvas.getHeight()),paint,renderRegion.top,renderRegion.left); c.clipRect(drawArea); c.drawBitmap(magBitmap, drawArea.left - (focus.left - renderRegion.left * CHAR_WIDTH)*ratioX , drawArea.top - (focus.top - renderRegion.top * CHAR_HEIGHT)*ratioY , new Paint()); } public void renderText(Canvas canvas,int row){ RectF r = new RectF(0,row*CHAR_HEIGHT,CHAR_WIDTH * TERM_WIDTH,(row+1)*CHAR_HEIGHT); renderText(canvas,r,null,row,0); } private static int round(float x) { /* I got this from com.android.internal.util.FastMath. */ /* I don't think it well make us any faster, but can help us type less code */ long lx = (long)(x * (65536 * 256f)); return (int)((lx + 0x800000) >> 24); } public void renderText(Canvas canvas,RectF drawArea,Paint paint,int row, int col){ // We round floats ourself because the direct use RectF to paint cause dirty lines. // TODO: The better approach: mix the color if a pixel is shared. // TODO: Draw rect chars our self. canvas.clipRect(drawArea,Op.REPLACE); float decent = CHAR_POS_FIX, sp_decent = specialDecent, charWidth = CHAR_WIDTH, charHeight = CHAR_HEIGHT; if(paint == null){ paint = defaultPaint; }else{ charWidth = paint.getTextSize() / 2 * paint.getTextScaleX(); charHeight = paint.getTextSize(); Rect bound = new Rect(); paint.getTextBounds("龜", 0, 1,bound); decent = charHeight - bound.bottom; Paint sPaint = new Paint(paint); sPaint.setTypeface(specialTypeface); sPaint.getTextBounds("▇", 0, 1,bound); sp_decent = charHeight - bound.bottom; } int toRow = row + (int)Math.ceil(drawArea.height()/charHeight); int toCol = col + (int)Math.ceil(drawArea.width()/charWidth); if(toRow >= buffer.getRows()) toRow = buffer.getRows() -1; if(toCol >= buffer.getColumns()) toCol = buffer.getColumns() -1; for(int r=row; r <= toRow ;r++){ int c; boolean stateHigh = false; RectF localRect = new RectF( drawArea.left, drawArea.top + (r-row) * charHeight, drawArea.right, drawArea.top + (r-row+1) * charHeight); for(c=0; c<col; c++) //We should move charset decode to terminal class stateHigh = (!stateHigh && buffer.getChar(c, r) >= 128); if(stateHigh){ c--; stateHigh = false; localRect.left -= charWidth; } for(;c <= toCol; c++){ int ptr = 0; int currAttr = buffer.charAttributes[buffer.windowBase + r][c]; // ptr is the "length" of same attr char while(c+ptr <= toCol && currAttr == buffer.charAttributes[buffer.windowBase + r][c+ptr]){ stateHigh = (!stateHigh && buffer.getChar(c+ptr, r) >= 128); ptr++; } if(stateHigh && c+ptr+1 < TERM_WIDTH - 1){ ptr++; stateHigh = false; } int color[] = getColor(currAttr); paint.setColor(color[1]); localRect.right = localRect.left + ptr*charWidth; canvas.drawRect(round(localRect.left), round(localRect.top), round(localRect.right), round(localRect.bottom), paint); char[] chars = new char[ptr]; System.arraycopy(buffer.charArray[buffer.windowBase + r], c, chars, 0, ptr); String encoding = (host != null)? host.getEncoding():"Big5"; String string = ChineseUtils.decode(chars, encoding,this.getResources()); paint.setColor(color[0]); // Since Android's MONOFACE is not really MONOFACE..... We have to postion by our self int colCount = 0; String ch = null; float chDecent = decent; for(int pos = 0; pos < string.length(); pos++){ ch = string.substring(pos, pos+1); chDecent = decent; if( (colCount < ptr && chars[colCount] >= 0x21 && chars[colCount] <= 0x7E) || (colCount+1 < ptr && ((chars[colCount] == 0xA1 && chars[colCount+1] >= 0x41) || (chars[colCount] == 0xA2 && ( chars[colCount+1] < 0x49 || (chars[colCount+1] > 0x62 && chars[colCount+1]< 0xAE)))))){ paint.setTypeface(specialTypeface); chDecent = sp_decent; }else{ paint.setTypeface(Typeface.MONOSPACE); } canvas.drawText( ch, localRect.left + colCount*charWidth, localRect.top+chDecent, paint); if(ch.charAt(0) > 128) colCount++; colCount++; } int lastColor[] = getColor(buffer.charAttributes[buffer.windowBase + r][c+ptr-1]); if(!Arrays.equals(color,lastColor) && ch != null){ /* null happens */ localRect.left = localRect.right - charWidth; color = getColor( buffer.charAttributes[buffer.windowBase + r][c+ptr-1]); canvas.clipRect(localRect, Op.REPLACE); paint.setColor(color[1]); canvas.drawRect(round(localRect.left), round(localRect.top), round(localRect.right), round(localRect.bottom), paint); paint.setColor(color[0]); canvas.drawText(ch , localRect.left-charWidth, localRect.top+chDecent, paint); } c+= ptr -1; canvas.clipRect(drawArea,Op.REPLACE); localRect.left = localRect.right; } } } @Override public void onDraw(Canvas viewCanvas) { Log.v(TAG,"onDraw()"); // draw if (this.bitmap == null) return; boolean entireDirty = buffer.update[0] || fullRedraw; // walk through all lines in the buffer for (int l = 0; l < buffer.height; l++) { // check if this line needs redraw. if (!entireDirty && !buffer.update[l + 1]) continue; // reset dirty flag for this line buffer.update[l + 1] = false; // redraw renderText(canvas,l); // reset urls for this line if (urls != null) urls[l] = new ArrayList<Url>(); // look for url int pos = 0, addr; String current = String.valueOf(buffer.charArray[buffer.windowBase + l]); while( (pos = current.indexOf("://",pos)) != -1){ // substring's returns a string contains [start,end-1] if(pos >= 4 && current.substring(pos-4, pos).equalsIgnoreCase("http")) addr = 4; else if(pos >= 5 && current.substring(pos-5, pos).equalsIgnoreCase("https")) addr = 5; else break; pos -= addr; addr += 3; Log.v(TAG,"URL START AT"+pos); while(pos+addr+1<current.length()){ char thisChar = current.charAt(pos+addr+1); if(!Character.isLetterOrDigit(thisChar) && ! "./@?&=-_;%#!~".contains(Character.toString(thisChar)) ) break; addr++; } String url = current.substring(pos,pos+addr+1); Log.v(TAG,"URL is:"+url); urls[l].add(new Url(pos,pos+addr,l,url)); defaultPaint.setColor(Color.BLUE); canvas.drawLine(pos*CHAR_WIDTH, (l+1)*CHAR_HEIGHT -1, (pos+addr+1)*CHAR_WIDTH, (l+1)*CHAR_HEIGHT-1, defaultPaint); pos+=addr+1; } } // reset entire-buffer flags buffer.update[0] = false; fullRedraw = false; // Calculate cursor int curCol = this.buffer.getCursorColumn(), curRow = (this.buffer.getCursorRow() + this.buffer.screenBase - this.buffer.windowBase); float cursorX = curCol * CHAR_WIDTH, cursorY = curRow * CHAR_HEIGHT; float scroll = 0; // albb0920: Manually scroll, requestRectangleOnScreen(cursor,true) doesn't seems to work well // Rewrite this if we can get requestRectangleOnScreen work well. int viewPos[] = new int[2]; getLocationOnScreen(viewPos); //Log.v(TAG,"On Screen Pos: "+viewPos[0]+","+viewPos[1]); if(cursorY+viewPos[1]<0){ // ime is up and we were blocked. scroll = -1 * (cursorY+viewPos[1]) + (SCREEN_HEIGHT + viewPos[1] - CHAR_HEIGHT)/2; if(scroll > -1 *viewPos[1]) scroll = -1 *viewPos[1]; Log.v(TAG,"SCROLL FIX"+scroll); } viewCanvas.drawBitmap(this.bitmap, 0, 0+scroll, defaultPaint); // draw cursor if (this.buffer.isCursorVisible()) { cursorPaint.setColor(Color.argb(128, 0, 255, 0)); viewCanvas.drawRect(cursorX, cursorY+scroll, cursorX + CHAR_WIDTH, cursorY + CHAR_HEIGHT + scroll, cursorPaint); } } /** * Chinese InputMethod * * @author Chen slepher (slepheric@gmail.com) */ @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { // We need this private flag to make candidate selection for IME usable. // This is the value for API level between 5 and 11, value has changed since then. // It seems that after lv11, setting this flag(0x2000000) is no longer necessary. // sync from lunaterm 1.2b final int IME_FLAG_NO_FULLSCREEN = 0x80000000; outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | IME_FLAG_NO_FULLSCREEN; outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; //albb0920.100706: Without this, HTC_CIME's Chewing KB refuse to work InputConnection ic = new TermInputConnection(this); return ic; } @Override public boolean onCheckIsTextEditor(){ return true; } private class TermInputConnection extends BaseInputConnection { public TermInputConnection(View targetView) { super(targetView, false); } @Override public boolean performEditorAction(int actionCode) { if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { long eventTime = SystemClock.uptimeMillis(); sendKeyEvent(new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0)); sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0)); return true; } return false; } public boolean setComposingText (CharSequence text, int newCursorPosition){ Log.v(TAG,"setComposingText: "+text); if((TerminalActivity.termActFlags & TerminalActivity.FLAG_SHOW_EXTRACT_UI) !=0 && text.length()>0 && text.charAt(0)>128){ terminalActivity.showInputHelper(); return false; } return super.setComposingText(text, newCursorPosition); } } /** * Chinese InputMethod * * @author Chen slepher (slepheric@gmail.com) */ @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { Log.d(TAG, "onKeyMultiple:" + keyCode); if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { try { String ime_input = event.getCharacters(); connection.write(ime_input.getBytes(host.getEncoding())); return true; } catch (SocketException e) { nodifyParent(e); } catch (IOException e) { try { connection.flush(); } catch (IOException ioe) { nodifyParent(e); } } catch (NullPointerException npe) { Log.d(TAG, "Input before connection established ignored."); return true; } } return super.onKeyMultiple(keyCode, repeatCount, event); } private CharSequence urlResult = null; public boolean checkUrlClick(MotionEvent event){ int y = (int) event.getY(); // sync lunaterm int x = (int) event.getX(); // sync lunaterm int l = (int) (y / CHAR_HEIGHT); int w = (int) (x / CHAR_WIDTH); int l_min = (int)((y - mUrlRangePx) / CHAR_HEIGHT); int l_max = (int)((y + mUrlRangePx) / CHAR_HEIGHT); if (urls != null) { final ArrayList<CharSequence> list = new ArrayList<CharSequence>(); for(int i = l_min; i <= l_max; i++) { if(i >= 0 && i < urls.length && urls[i] != null) { for (Url url : urls[i]) { if (url.pointIn(w, i)) { list.add(url.url.trim()); //Log.v("Kenshinn", "set Url Handled"); // terminalActivity.showUrlDialog(url.url.trim()); // return true; } } } } if(list.size() > 0) { urlResult = null; urlResult = list.get(0); if(list.size() > 1) { CharSequence[] array = new CharSequence[list.size()]; list.toArray(array); AlertDialog.Builder listBuilder = terminalActivity.listBuilder; listBuilder.setSingleChoiceItems(array, 0, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { urlResult = list.get(item); } }); listBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if(urlResult != null) { terminalActivity.showUrlDialog(urlResult.toString()); } } }); listBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { urlResult = null; } }); AlertDialog dialog = listBuilder.create(); dialog.show(); return true; } else { if(urlResult != null) { terminalActivity.showUrlDialog(urlResult.toString()); return true; } } } } Log.d(TAG, "onTouchEvent:" + y + "/" + CHAR_HEIGHT + "=" + l); return false; } // @Override // public boolean onTouchEvent(MotionEvent event) { // Log.v("Kenshinn", "TerminalView.onTouchEvent: " + event.getAction()); // int eventaction = event.getAction(); // Log.d(TAG, "onTouchEvent:" + eventaction); // // switch (eventaction) { // // case MotionEvent.ACTION_DOWN: // touch down so check if the // // int y = (int) event.getRawY(); // int x = (int) event.getRawX(); // int l = (int) (y / CHAR_HEIGHT); // int w = (int) (x / CHAR_WIDTH); // // if (urls[l] != null) { // for (Url url : urls[l]) { // if (url.pointIn(w, l)) // Log.v("Kenshinn", "set Url Handled"); // terminalActivity.getGestureView().setUrlHandled(true); // terminalActivity.showUrlDialog(url.url.trim()); // } // } // Log.d(TAG, "onTouchEvent:" + y + "/" + CHAR_HEIGHT + "=" + l); // return true; // // case MotionEvent.ACTION_MOVE: // touch drag with the ball // break; // // case MotionEvent.ACTION_UP: // break; // // } // // return false; // } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { metaState = MetaKeyKeyListener.handleKeyUp(metaState, keyCode, event); return super.onKeyUp(keyCode, event); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (connection == null) return false; try { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { terminalActivity.changeFunctionKeyGalleryDisplay(); return true; } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { terminalActivity.inputMethodManager.toggleSoftInput( InputMethodManager.SHOW_FORCED, 0); return true; } if (metaState == (metaState = MetaKeyKeyListener.handleKeyDown( metaState, keyCode, event))) { boolean result = processSpecialChar(keyCode, MetaKeyKeyListener .getMetaState(metaState)); metaState = MetaKeyKeyListener .adjustMetaAfterKeypress(metaState); if (result) return true; } } catch (SocketException e) { e.printStackTrace(); nodifyParent(e); } catch (IOException e) { e.printStackTrace(); try { connection.flush(); } catch (IOException ioe) { nodifyParent(e); } } catch (NullPointerException npe) { Log.d(TAG, "Input before connection established ignored."); return true; } return false; } public void write(byte[] b) throws IOException { connection.write(b); } public boolean processSpecialChar(int keyCode, int mState) throws IOException { boolean printing = (DEFAULT_KEYMAP.isPrintingKey(keyCode) || keyCode == KeyEvent.KEYCODE_SPACE); if (printing) { int key = DEFAULT_KEYMAP.get(keyCode, mState); if (ctrlPressed) { // Support CTRL-a through CTRL-z if (key >= 0x61 && key <= 0x7A) key -= 0x60; // Support CTRL-A through CTRL-_ else if (key >= 0x41 && key <= 0x5F) key -= 0x40; else if (key == 0x20) key = 0x00; ctrlPressed = false; } connection.write(key); return true; } switch (keyCode) { case KeyEvent.KEYCODE_DEL: connection.write(0x08); return true; case KeyEvent.KEYCODE_ENTER: ((vt320) buffer).keyTyped(vt320.KEY_ENTER, ' ', mState); return true; case KeyEvent.KEYCODE_DPAD_LEFT: ((vt320) buffer).keyPressed(vt320.KEY_LEFT, ' ', mState); return true; case KeyEvent.KEYCODE_DPAD_UP: ((vt320) buffer).keyPressed(vt320.KEY_UP, ' ', mState); invalidate(); return true; case KeyEvent.KEYCODE_DPAD_DOWN: ((vt320) buffer).keyPressed(vt320.KEY_DOWN, ' ', mState); invalidate(); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: ((vt320) buffer).keyPressed(vt320.KEY_RIGHT, ' ', mState); return true; case KeyEvent.KEYCODE_TAB: ((vt320) buffer).keyPressed(vt320.KEY_TAB, ' ', mState); return true; case KeyEvent.KEYCODE_SEARCH: case KeyEvent.KEYCODE_DPAD_CENTER: // TODO: Add some visual indication of Ctrl state if (ctrlPressed) { ((vt320) buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); ctrlPressed = false; } else ctrlPressed = true; return true; } return false; } public void resetColors() { //TODO: Let user customize color plate color = new int[] { Color.rgb( 0, 0, 0), // black Color.rgb(128, 0, 0), // red Color.rgb( 0,128, 0), // green Color.rgb(128,128, 0), // yellow Color.rgb( 0, 0,128), // blue Color.rgb(128, 0,128), // purple Color.rgb( 0,128,128), // cyan Color.rgb(192,192,192), // light gray Color.rgb(128,128,128), // dark gray Color.rgb(255, 0, 0), // light red Color.rgb( 0,255, 0), // light green Color.rgb(255,255, 0), // yellow Color.rgb( 0, 0,255), // light blue Color.rgb(255, 0,255), // light purple Color.rgb( 0,255,255), // light cyan Color.rgb(255,255,255) // white }; } public VDUBuffer getVDUBuffer() { return buffer; } /* * albb0920: * This was used to tell OS where to pan. * Unfortunately, the method requestFocusRect() seems to be broken with HTC CIME * (If you request a rect that is in the middle of the screen, It will be broken, blame HTC) * And I can't find a good way to get IME height. * So we lie we are in that last line to force system scroll our whole view above IME. * And we manually scroll it back. WTF */ public void getFocusedRect(Rect cursor){ // Report the last line cursor.bottom = SCREEN_HEIGHT; cursor.top = (int) (cursor.bottom - CHAR_HEIGHT); cursor.left = 0; cursor.right = SCREEN_HEIGHT; } /** * Return color set of currAttr * @param currAttr * @return array(foreground,background) */ private int[] getColor(int currAttr){ int fg,bg; // reset default colors fg = DEFAULT_FG_COLOR; //albb0920: this will be convert to color later bg = color[DEFAULT_BG_COLOR]; // check if foreground color attribute is set if ((currAttr & ( VDUBuffer.COLOR_FG )) != 0) fg = ((currAttr & VDUBuffer.COLOR_FG) >> VDUBuffer.COLOR_FG_SHIFT) - 1; // bright color if ((currAttr & VDUBuffer.BOLD) != 0) fg |= 8; // albb0920.100709: now we know the fg color fg = color[fg]; // check if background color attribute is set if ((currAttr & VDUBuffer.COLOR_BG) != 0) bg = color[((currAttr & VDUBuffer.COLOR_BG) >> VDUBuffer.COLOR_BG_SHIFT) - 1]; // support character inversion by swapping background and // foreground color if ((currAttr & VDUBuffer.INVERT) != 0) { int swapc = bg; bg = fg; fg = swapc; } // if black-on-black, try correcting to grey // if(fg == Color.BLACK && bg == Color.BLACK) fg = Color.GRAY; defaultPaint .setUnderlineText((currAttr & VDUBuffer.UNDERLINE) != 0); return new int[] {fg,bg}; } public void redraw() { Log.v(TAG,"redraw()"); postInvalidate(); /* render should always be done in onDraw() */ } public void setVDUBuffer(VDUBuffer buffer) { this.buffer = buffer; } /** * Not Implemented. * We don't even have scroll buffer. So...(ry. */ public void updateScrollBar() { } public void setColor(int index, int red, int green, int blue) { if (index < color.length && index >= 16) color[index] = 0xff000000 | red << 16 | green << 8 | blue; } protected void startConnection(final Host host) { this.host = host; postDelayed(new Runnable() { @Override public void run() { warningLoginAgain = true; } }, DELAY_WARNING_LOGIN_AGAIN); new Thread(new Runnable() { public void run() { byte[] b = new byte[4096]; try { String hostProtocal = host.getProtocal(); String hostHost = host.getHost(); int hostPort = host.getPort(); if ("telnet".equalsIgnoreCase(hostProtocal)) { connection = new TelnetWrapper(); connection.connect(hostHost, hostPort); } /* else if ("ssh".equalsIgnoreCase(hostProtocal)) { connection = new SshWrapper(); connection.connect(hostHost, hostPort); } */ int delay = 200; if(mPref.getBoolean("settings_delay_login", false)) delay = 2000; TerminalView.this.postDelayed(new Runnable() { public void run() { try { loginHost(host); } catch (Exception e) { e.printStackTrace(); nodifyParent(e); } } }, delay); connected = true; while (true) { int n = connection.read(b); if (n > 0) { // long start = System.currentTimeMillis(); String fullString = new String(b, 0, n, "ISO8859-1"); // Log.i(TAG, fullString); ((vt320) buffer).putString(fullString, host .getEncoding()); // long end1 = System.currentTimeMillis(); // long end2 = System.currentTimeMillis(); // Log.i(TAG, (end1 - start) + "," + (end2 - end1)); } else if (n < 0) { break; } } nodifyParent(null); } catch (Exception e) { e.printStackTrace(); nodifyParent(e); } } }).start(); } public void loginAgain() { if(warningLoginAgain) { new AlertDialog.Builder(terminalActivity) .setTitle(R.string.terminal_login_again) .setMessage(R.string.terminal_login_again) .setPositiveButton(R.string.ok, new android.content.DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { loginHost(TerminalView.this.host); } catch (IOException e) { e.printStackTrace(); } } }) .setNegativeButton(R.string.cancel, null) .create().show(); } else { try { loginHost(this.host); } catch (IOException e) { e.printStackTrace(); } } } private void loginHost(final Host host) throws IOException { String hostUser = host.getUser(); String hostPass = host.getPass(); String hostProtocal = host.getProtocal(); if ("telnet".equalsIgnoreCase(hostProtocal)) { if (hostUser != null && hostPass != null && hostUser.length() > 0 && hostPass.length() > 0) { try { connection.send(hostUser + "\r"); connection.send(hostPass + "\r"); } catch (IOException ex) { } } } } private void nodifyParent(Exception e) { connected = false; terminalActivity.disconnect(e); } private class Url { public int startX; public int endX; public int y; public String url; public Url(int startX, int endX, int y, String url) { this.startX = startX; this.endX = endX; this.y = y; this.url = url; } public boolean pointIn(int x, int y) { return (this.y == y && x >= startX && x <= endX); } } }