content: package com.lvrenyang.widget;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.List;
  
  import com.lvrenyang.label.*;
  import com.lvrenyang.mylabelwork.R;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.content.res.Resources;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.Paint;
  import android.graphics.Path;
  import android.graphics.Point;
  import android.graphics.Rect;
  import android.graphics.Typeface;
  import android.graphics.Bitmap.Config;
  import android.graphics.Paint.FontMetrics;
  import android.graphics.Paint.Style;
  import android.graphics.Path.Direction;
  import android.graphics.drawable.BitmapDrawable;
  import android.graphics.drawable.Drawable;
  import android.text.Editable;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.SparseArray;
  import android.view.Gravity;
  import android.view.MotionEvent;
  import android.view.View;
  import android.widget.EditText;
  import android.widget.PopupWindow;
  
  @SuppressLint("ClickableViewAccessibility") public class VirtualKeyboard extends View {
  
  	private static final int ZOOM_RADIUS = 58;
  	private static final int ZOOM_SIZE = 131;
  	private static final int HANDLE_SIZE = 33;
  	private static final long ZOOM_DELAY_TIME = 250;
  	private static final int BUTTON_SIZE = 64;
  	private Bitmap kbdBitmap;
  	private ButtonDrawable btnDrawable;
  	private ButtonDrawable btnCtrlDrawable;
  	private ButtonDrawable btnSelectedDrawable;
  	private ButtonDrawable btnToggleSelectedDrawable;
  	private List<VirtualButton> buttons;
  	private Paint letterPaint;
  	private Rect srcRect;
  	private Point dstPoint;
  	private Bitmap zoomBitmap;
  	private VirtualButton pressedButton;
  	public Paint backgroundPaint;
  	private int state;
  	private PopupWindow zoom;
  	private ZoomView zoomView;
  	private Bitmap handleBitmap;
  	private ToggleVirtualButton shiftBtn;
  	private VirtualButton enterBtn;
  	private VirtualButton symbolsBtn;
  	private boolean zoomVisible;
  	private Canvas kbdCanvas;
  
  	public VirtualKeyboard(Context context, AttributeSet attrs) {
  		super(context, attrs);
  		Resources res = context.getResources();
  		btnDrawable = new ButtonDrawable(res, R.drawable.vk_button);
  		btnCtrlDrawable = new ButtonDrawable(res, R.drawable.vk_button_ctrl);
  		btnSelectedDrawable = new ButtonDrawable(res, R.drawable.vk_button_selected);
  		btnToggleSelectedDrawable = new ButtonDrawable(res, R.drawable.vk_button_toggle_selected);
  		
  		BitmapDrawable zoomDrawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.vk_zoom);
  		zoomBitmap = zoomDrawable.getBitmap();
  		BitmapDrawable handleDrawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.vk_zoom_handle);
  		handleBitmap = handleDrawable.getBitmap();
  		
  		buttons = new ArrayList<VirtualButton>();
  		letterPaint = new Paint();
  		letterPaint.setAntiAlias(true);
  		letterPaint.setColor(0xff000000);
  		letterPaint.setTypeface(Typeface.DEFAULT_BOLD);
  		letterPaint.setTextSize(40);
  		backgroundPaint = new Paint();
  		backgroundPaint.setColor(0xffffffff);
  		backgroundPaint.setStyle(Style.FILL);
  		
  		srcRect = new Rect(0, 0, 2 * ZOOM_RADIUS, 2 * ZOOM_RADIUS);
  		dstPoint = new Point(0, 0);
  		
  		pressedButton = null;
  		state = 0;
  		
  		zoomView = new ZoomView(getContext());
  		zoom = new PopupWindow(zoomView, ZOOM_SIZE, ZOOM_SIZE);
  		zoom.setAnimationStyle(android.R.style.Animation_Toast);
  	}
  	
  	@Override
  	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  		int w = MeasureSpec.getSize(widthMeasureSpec);
  		int h = MeasureSpec.getSize(heightMeasureSpec);
  		createBitmap(2 * w + BUTTON_SIZE, 2 * h + BUTTON_SIZE);
  		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  	}
  	
  	private void createBitmap(int w, int h) {
  		kbdBitmap = Bitmap.createBitmap(w, h, Config.ARGB_8888);
  		kbdCanvas = new Canvas(kbdBitmap);
  		Resources res = getResources();
  		VirtualButton vButton;
  		
  		String packageName = R.class.getPackage().getName();
  		XmlPullParser xpp = getResources().getXml(R.xml.layout);
  		try {
  			while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
  				if (xpp.getEventType() == XmlPullParser.START_TAG) {
  					String name = xpp.getName();
  					if (!name.equals("Layout")) { 
  						String sX = xpp.getAttributeValue(null, "x");
  						float x = 0;
  						if (sX != null) {
  							x = Float.parseFloat(sX);
  						}
  						String sY = xpp.getAttributeValue(null, "y");
  						float y = 0;
  						if (sY != null) {
  							y = Float.parseFloat(sY);
  						}
  						String sWidth = xpp.getAttributeValue(null, "width");
  						float width = 1f;
  						if (sWidth != null) {
  							width = Float.parseFloat(sWidth);
  						}
  						String sText = xpp.getAttributeValue(null, "text");
  						String sDrawable = xpp.getAttributeValue(null, "drawable");
  						if (name.equals("ShiftButton")) {
  							vButton = shiftBtn = new ToggleVirtualButton(x, y, width, "");
  						} else
  						if (name.equals("BackspaceButton")) {
  							vButton = new VirtualButton(x, y, width, "\b\b\b\b");
  						} else
  						if (name.equals("SymbolsButton")) {
  							vButton = symbolsBtn = new ToggleVirtualButton(x, y, width, sText);
  						} else
  						if (name.equals("EnterButton")) {
  							vButton = enterBtn = new ControlVirtualButton(x, y, width, sText);
  						} else {
  							if (sText.length() < 4) {
  								Log.d("VirtualKeyboard", " Button x:" + x + " y:" + y + " has text shorter than 4 characters !!");
  								sText = "????";
  							}
  							vButton = new VirtualButton(x, y, width, sText);
  						}
  						if (sDrawable != null) {
  							int id = res.getIdentifier(sDrawable, null, packageName);
  							if (id == 0) {
  								throw new RuntimeException("resource: " + sDrawable + " not found");
  							}
  							vButton.setBitmap(id);
  						}
  						buttons.add(vButton);
  					}
  				}
  				xpp.next();
  			}
  		} catch (XmlPullParserException e) {
  			Log.d("VirtualKeyboard", "XmlPullParserException", e);
  		} catch (IOException e) {
  			Log.d("VirtualKeyboard", "IOException", e);
  		}
  		
  		drawLayout();
  	}
  
  	@Override
  	protected void onDraw(Canvas canvas) {
  		canvas.scale(0.5f, 0.5f);
  		canvas.translate(-BUTTON_SIZE/2, -BUTTON_SIZE/2);
  		canvas.drawBitmap(kbdBitmap, 0, 0, null);
  	}
  	
  	@Override
  	public boolean onTouchEvent(MotionEvent event) {
  		int action = event.getAction();
  		if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) {
  			// offset getX & getY by BUTTON_SIZE/4: bitmap padding
  			int x = (int) event.getX() + BUTTON_SIZE/4;
  			int y = (int) event.getY() + BUTTON_SIZE/4;
  			srcRect.offsetTo(2 * x - ZOOM_RADIUS, 2 * y - ZOOM_RADIUS);
  			dstPoint.set(x - ZOOM_RADIUS, y - 3 * ZOOM_RADIUS / 2);
  			int xx = x * 2;
  			int yy = y * 2;
  			boolean in = false;
  			for (VirtualButton button : buttons) {
  				if (button.contains(xx, yy)) {
  					in = true;
  					if (pressedButton == button) {
  						break;
  					}
  					if (pressedButton != null) {
  						pressedButton.up(false);
  					}
  					pressedButton = button;
  					pressedButton.down();
  					break;
  				}
  			}
  			if (!in && pressedButton != null) {
  				pressedButton.up(false);
  				pressedButton = null;
  			}
  			if (pressedButton != null) {
  				srcRect.offsetTo(2 * x - ZOOM_RADIUS, pressedButton.getRect().centerY() - ZOOM_RADIUS);
  				dstPoint.set(x - ZOOM_RADIUS, pressedButton.getRect().top/2 - 3 * ZOOM_RADIUS / 2);
  			}
  			if (srcRect.left < 0) {
  				srcRect.offset(-srcRect.left, 0);
  			} else 
  			if (srcRect.right > kbdBitmap.getWidth()) {
  				srcRect.offset(kbdBitmap.getWidth() - srcRect.right, 0);
  			}
  			if (srcRect.top < 0) {
  				srcRect.offset(0, -srcRect.top);
  			} else
  			if (srcRect.bottom > kbdBitmap.getHeight()) {
  				srcRect.offset(0, kbdBitmap.getHeight() - srcRect.bottom);
  			}
  			dstPoint.offset(-BUTTON_SIZE/4, -BUTTON_SIZE/4);
  				
  			if (y < BUTTON_SIZE/4) {
  				// hide zoom if out of bounds
  				zoom.dismiss();
  				invalidate();
  				return true;
  			}
  			if (action == MotionEvent.ACTION_DOWN) {
  				removeCallbacks(showZoom);
  				postDelayed(showZoom, ZOOM_DELAY_TIME);
  			} else {
  				if (zoomVisible && !zoom.isShowing()) {
  					// zoom was hidden since out of bounds (y < 0)
  					showZoom.run();
  				}
  				zoom.update(getLeft() + dstPoint.x, getTop() + dstPoint.y, -1, -1);
  			}
  			zoomView.invalidate();
  		} else
  		if (action == MotionEvent.ACTION_UP) {
  			if (pressedButton != null) {
  				pressedButton.up(true);
  				
  				if (pressedButton == shiftBtn) {
  					state ^= 1;
  					drawLayout();
  				} else
  				if (pressedButton == symbolsBtn) {
  					state ^= 2;
  					drawLayout();
  				} else
  				if (pressedButton == enterBtn) {
  					// TODO implement me?
  				} else {
  					updateEditText(pressedButton.text.charAt(state));
  				}
  				pressedButton = null;
  			}
  			pressedButton = null;
  			removeCallbacks(showZoom);
  			zoomVisible = false;
  			zoom.dismiss();
  		}
  		invalidate();
  		return true;
  	}
  
  	private void drawLayout() {
  //		long t0 = System.currentTimeMillis();
  		kbdCanvas.drawARGB(255, 255, 255, 255);
  		for (VirtualButton button : buttons) {
  			button.draw(kbdCanvas);
  		}
  //		long t1 = System.currentTimeMillis();
  //		Log.d("VirtualKeyboard", "drawLayout took " + (t1-t0) + "ms");
  	}
  
  	private void updateEditText(char c) {
  		int start = edit.getSelectionStart();
  		int end = edit.getSelectionEnd();
  		if (start > end) {
  			int tmp = end;
  			end = start;
  			start = tmp;
  		}
  		Editable text = edit.getText();
  		if (c != '\b') {
  			text.replace(start, end, String.valueOf(c), 0, 1);
  			start++;
  		} else {
  			if (start == end && start > 0) {
  				start--;
  			}
  			if (start + end == 0) {
  				// start of buffer & no selection: just return
  				return;
  			}
  			text.delete(start, end);
  		}
  		edit.setText(text);
  		edit.setSelection(start);
  	}
  
  	Runnable showZoom = new Runnable() {
  		public void run() {
  			zoomVisible = true;
  			zoom.showAtLocation(VirtualKeyboard.this,
  					Gravity.NO_GRAVITY,
  					getLeft() + dstPoint.x, 
  					getTop() + dstPoint.y);
  		}
  	};
  
  
  	// tmp
  	private EditText edit;
  	public void setUp(EditText edit) {
  		this.edit = edit;
  	}
  	// end of tmp
  
  	class VirtualButton {
  		private Rect rect;
  		protected String text;
  		private Bitmap bitmap;
  		
  		public VirtualButton(float x, float y, String letters) {
  			this(x, y, 1, letters);
  		}
  		public VirtualButton(float x, float y, float w, String letters) {
  			int left = (int) (x * BUTTON_SIZE);
  			int top = (int) (y * BUTTON_SIZE);
  			int right = (int) ((x + w) * BUTTON_SIZE);
  			int bottom = (int) ((y + 1) * BUTTON_SIZE);
  			this.rect = new Rect(left, top, right, bottom);
  			this.rect.offset(BUTTON_SIZE/2, BUTTON_SIZE/2);
  			this.text = letters;
  		}
  		public void setBitmap(int resId) {
  			BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(resId);
  			bitmap = drawable.getBitmap();
  		}
  		public Rect getRect() {
  			return rect;
  		}
  
  		public void up(boolean inside) {
  			draw(kbdCanvas, btnDrawable, text.substring(state, state+1));
  		}
  
  		public void down() {
  			draw(kbdCanvas, null, text.substring(state, state+1));
  		}
  
  		public boolean contains(int x, int y) {
  			return rect.contains(x, y);
  		}
  
  		public void draw(Canvas canvas) {
  			draw(canvas, btnDrawable, text.substring(state, state+1));
  		}
  		
  		void draw(Canvas canvas, ButtonDrawable buttonDrawable, String t) {
  			canvas.drawRect(rect, backgroundPaint);
  			if (buttonDrawable != null) {
  				Bitmap bitmap = buttonDrawable.getBitmap(rect);
  				canvas.drawBitmap(bitmap, rect.left, rect.top, null);
  			} else {
  				Bitmap bitmap = btnSelectedDrawable.getBitmap(rect);
  				canvas.drawBitmap(bitmap, rect.left, rect.top, null);
  			}
  			if (bitmap != null) {
  				canvas.drawBitmap(bitmap,
  						rect.centerX() - bitmap.getWidth() / 2,
  						rect.centerY() - bitmap.getHeight() / 2,
  						null);
  			} else {
  				float x = (rect.width() - letterPaint.measureText(t)) / 2;
  				FontMetrics fm = letterPaint.getFontMetrics();
  				float y = (rect.height() - letterPaint.getTextSize()) / 2 - fm.ascent - fm.descent;
  				// TODO fix it
  				y += 4;
  				canvas.drawText(t, rect.left+x, rect.top+y, letterPaint);
  			}
  		}
  	}
  	
  	class ControlVirtualButton extends VirtualButton {
  		public ControlVirtualButton(float x, float y, float w, String letters) {
  			super(x, y, w, letters);
  		}
  		public ControlVirtualButton(float x, float y, String letters) {
  			super(x, y, letters);
  		}
  		@Override
  		public void draw(Canvas canvas) {
  			draw(canvas, btnCtrlDrawable, text);
  		}
  		public void up(boolean inside) {
  			draw(kbdCanvas, btnCtrlDrawable, text);
  		}
  		public void down() {
  			draw(kbdCanvas, null, text);
  		}
  	}
  	class ToggleVirtualButton extends ControlVirtualButton {
  		private boolean down;
  		public ToggleVirtualButton(float x, float y, float w, String letters) {
  			super(x, y, w, letters);
  		}
  		public ToggleVirtualButton(float x, float y, String letters) {
  			super(x, y, letters);
  		}
  		@Override
  		public void draw(Canvas canvas) {
  			draw(canvas, down? btnToggleSelectedDrawable : btnCtrlDrawable, text);
  		}
  		@Override
  		public void up(boolean inside) {
  			if (inside) {
  				down = !down;
  			}
  			draw(kbdCanvas, down? btnToggleSelectedDrawable : btnCtrlDrawable, text);
  		}
  	}
  	
  	class ZoomView extends View {
  		private Paint zoomPaint;
  		private Rect rect;
  		private Path clip;
  
  		public ZoomView(Context context) {
  			super(context);
  			zoomPaint = new Paint();
  			zoomPaint.setAntiAlias(true);
  			zoomPaint.setColor(0xff008000);
  			zoomPaint.setStyle(Style.STROKE);
  			rect = new Rect(2, 2, 2 + ZOOM_RADIUS * 2, 2 + ZOOM_RADIUS * 2);
  			clip = new Path();
  			clip.addCircle(ZOOM_RADIUS+2, ZOOM_RADIUS+2, ZOOM_RADIUS, Direction.CW);
  		}
  		
  		@Override
  		protected void onDraw(Canvas canvas) {
  			canvas.save();
  			canvas.clipPath(clip);
  			// draw zoom
  			zoomPaint.setAlpha(255);
  			canvas.drawBitmap(kbdBitmap, srcRect, rect, zoomPaint);
  			canvas.restore();
  			// draw zoom frame
  			zoomPaint.setAlpha(220);
  			canvas.drawBitmap(zoomBitmap, 0, 0, zoomPaint);
  			// draw zoom handle
  			zoomPaint.setAlpha(255);
  			canvas.drawBitmap(handleBitmap, ZOOM_SIZE-HANDLE_SIZE, ZOOM_SIZE-HANDLE_SIZE, zoomPaint);
  		}
  	}
  	
  	class ButtonDrawable {
  		private Drawable drawable;
  		private SparseArray<Bitmap> bitmapCache;
  		public ButtonDrawable(Resources res, int id) {
  			drawable = res.getDrawable(id);
  			bitmapCache = new SparseArray<Bitmap>();
  		}
  		public Bitmap getBitmap(Rect rect) {
  			int w = rect.width();
  			int h = rect.height();
  			int key = (w << 16) + h;
  			Bitmap bitmap = bitmapCache.get(key);
  			if (bitmap == null) {
  				drawable.setBounds(0, 0, w, h);
  				bitmap =  Bitmap.createBitmap(w, h, Config.ARGB_8888);
  				Canvas canvas = new Canvas(bitmap);
  				drawable.draw(canvas);
  				bitmapCache.put(key, bitmap);
  			}
  			return bitmap;
  		}
  	}
  }
  