OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.content.browser; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.content.res.TypedArray; |
| 9 import android.graphics.Canvas; |
| 10 import android.graphics.Rect; |
| 11 import android.graphics.drawable.Drawable; |
| 12 import android.os.SystemClock; |
| 13 import android.util.TypedValue; |
| 14 import android.view.Gravity; |
| 15 import android.view.LayoutInflater; |
| 16 import android.view.MotionEvent; |
| 17 import android.view.View; |
| 18 import android.view.ViewConfiguration; |
| 19 import android.view.ViewGroup; |
| 20 import android.view.ViewParent; |
| 21 import android.view.WindowManager; |
| 22 import android.view.View.OnClickListener; |
| 23 import android.view.ViewGroup.LayoutParams; |
| 24 import android.widget.PopupWindow; |
| 25 import android.widget.TextView; |
| 26 |
| 27 /** |
| 28 * View that displays a selection or insertion handle for text editing. |
| 29 */ |
| 30 class HandleView extends View { |
| 31 private Drawable mDrawable; |
| 32 private final PopupWindow mContainer; |
| 33 private int mPositionX; |
| 34 private int mPositionY; |
| 35 private final CursorController mController; |
| 36 private boolean mIsDragging; |
| 37 private float mTouchToWindowOffsetX; |
| 38 private float mTouchToWindowOffsetY; |
| 39 private float mHotspotX; |
| 40 private float mHotspotY; |
| 41 private int mLineOffsetY; |
| 42 private int mHeight; |
| 43 private int mLastParentX; |
| 44 private int mLastParentY; |
| 45 private float mDownPositionX, mDownPositionY; |
| 46 private int mContainerPositionX, mContainerPositionY; |
| 47 private long mTouchTimer; |
| 48 private boolean mIsInsertionHandle = false; |
| 49 private Runnable mLongPressCallback; |
| 50 |
| 51 private View mParent; |
| 52 private InsertionHandleController.PastePopupMenu mPastePopupWindow; |
| 53 |
| 54 private final int mTextSelectHandleLeftRes; |
| 55 private final int mTextSelectHandleRightRes; |
| 56 private final int mTextSelectHandleRes; |
| 57 |
| 58 private Drawable mSelectHandleLeft; |
| 59 private Drawable mSelectHandleRight; |
| 60 private Drawable mSelectHandleCenter; |
| 61 |
| 62 private final int[] mTempCoords = new int[2]; |
| 63 private final Rect mTempRect = new Rect(); |
| 64 |
| 65 static final int LEFT = 0; |
| 66 static final int CENTER = 1; |
| 67 static final int RIGHT = 2; |
| 68 |
| 69 // Number of dips to subtract from the handle's y position to give a suitabl
e |
| 70 // y coordinate for the corresponding text position. This is to compensate f
or the fact |
| 71 // that the handle position is at the base of the line of text. |
| 72 private static final float LINE_OFFSET_Y_DIP = 5.0f; |
| 73 |
| 74 private static final int[] TEXT_VIEW_HANDLE_ATTRS = { |
| 75 android.R.attr.textSelectHandleLeft, |
| 76 android.R.attr.textSelectHandle, |
| 77 android.R.attr.textSelectHandleRight, |
| 78 }; |
| 79 |
| 80 HandleView(CursorController controller, int pos, View parent) { |
| 81 super(parent.getContext()); |
| 82 Context context = parent.getContext(); |
| 83 mParent = parent; |
| 84 mController = controller; |
| 85 mContainer = new PopupWindow(context, null, android.R.attr.textSelectHan
dleWindowStyle); |
| 86 mContainer.setSplitTouchEnabled(true); |
| 87 mContainer.setClippingEnabled(false); |
| 88 |
| 89 TypedArray a = context.obtainStyledAttributes(TEXT_VIEW_HANDLE_ATTRS); |
| 90 mTextSelectHandleLeftRes = a.getResourceId(a.getIndex(LEFT), 0); |
| 91 mTextSelectHandleRes = a.getResourceId(a.getIndex(CENTER), 0); |
| 92 mTextSelectHandleRightRes = a.getResourceId(a.getIndex(RIGHT), 0); |
| 93 a.recycle(); |
| 94 |
| 95 setOrientation(pos); |
| 96 |
| 97 // Convert line offset dips to pixels. |
| 98 mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_D
IP, |
| 99 LINE_OFFSET_Y_DIP, context.getResources().getDisplayMetrics()); |
| 100 } |
| 101 |
| 102 void setOrientation(int pos) { |
| 103 int handleWidth; |
| 104 switch (pos) { |
| 105 case LEFT: { |
| 106 if (mSelectHandleLeft == null) { |
| 107 mSelectHandleLeft = getContext().getResources().getDrawable( |
| 108 mTextSelectHandleLeftRes); |
| 109 } |
| 110 mDrawable = mSelectHandleLeft; |
| 111 handleWidth = mDrawable.getIntrinsicWidth(); |
| 112 mHotspotX = (handleWidth * 3) / 4; |
| 113 break; |
| 114 } |
| 115 |
| 116 case RIGHT: { |
| 117 if (mSelectHandleRight == null) { |
| 118 mSelectHandleRight = getContext().getResources().getDrawable( |
| 119 mTextSelectHandleRightRes); |
| 120 } |
| 121 mDrawable = mSelectHandleRight; |
| 122 handleWidth = mDrawable.getIntrinsicWidth(); |
| 123 mHotspotX = handleWidth / 4; |
| 124 break; |
| 125 } |
| 126 |
| 127 case CENTER: |
| 128 default: { |
| 129 if (mSelectHandleCenter == null) { |
| 130 mSelectHandleCenter = getContext().getResources().getDrawable( |
| 131 mTextSelectHandleRes); |
| 132 } |
| 133 mDrawable = mSelectHandleCenter; |
| 134 handleWidth = mDrawable.getIntrinsicWidth(); |
| 135 mHotspotX = handleWidth / 2; |
| 136 mIsInsertionHandle = true; |
| 137 break; |
| 138 } |
| 139 } |
| 140 |
| 141 final int handleHeight = mDrawable.getIntrinsicHeight(); |
| 142 mHotspotY = 0; |
| 143 mHeight = handleHeight; |
| 144 invalidate(); |
| 145 } |
| 146 |
| 147 @Override |
| 148 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 149 setMeasuredDimension(mDrawable.getIntrinsicWidth(), |
| 150 mDrawable.getIntrinsicHeight()); |
| 151 } |
| 152 |
| 153 void show() { |
| 154 if (!isPositionVisible()) { |
| 155 hide(); |
| 156 return; |
| 157 } |
| 158 mContainer.setContentView(this); |
| 159 final int[] coords = mTempCoords; |
| 160 mParent.getLocationInWindow(coords); |
| 161 mContainerPositionX = coords[0] + mPositionX; |
| 162 mContainerPositionY = coords[1] + mPositionY; |
| 163 mContainer.showAtLocation(mParent, 0, mContainerPositionX, mContainerPos
itionY); |
| 164 |
| 165 // Hide paste view when handle is moved on screen. |
| 166 if (mPastePopupWindow != null) { |
| 167 mPastePopupWindow.hide(); |
| 168 } |
| 169 } |
| 170 |
| 171 void hide() { |
| 172 mIsDragging = false; |
| 173 mContainer.dismiss(); |
| 174 if (mPastePopupWindow != null) { |
| 175 mPastePopupWindow.hide(); |
| 176 } |
| 177 } |
| 178 |
| 179 boolean isShowing() { |
| 180 return mContainer.isShowing(); |
| 181 } |
| 182 |
| 183 private boolean isPositionVisible() { |
| 184 // Always show a dragging handle. |
| 185 if (mIsDragging) { |
| 186 return true; |
| 187 } |
| 188 |
| 189 final Rect clip = mTempRect; |
| 190 clip.left = 0; |
| 191 clip.top = 0; |
| 192 clip.right = mParent.getWidth(); |
| 193 clip.bottom = mParent.getHeight(); |
| 194 |
| 195 final ViewParent parent = mParent.getParent(); |
| 196 if (parent == null || !parent.getChildVisibleRect(mParent, clip, null))
{ |
| 197 return false; |
| 198 } |
| 199 |
| 200 final int[] coords = mTempCoords; |
| 201 mParent.getLocationInWindow(coords); |
| 202 final int posX = coords[0] + mPositionX + (int) mHotspotX; |
| 203 final int posY = coords[1] + mPositionY + (int) mHotspotY; |
| 204 |
| 205 return posX >= clip.left && posX <= clip.right && |
| 206 posY >= clip.top && posY <= clip.bottom; |
| 207 } |
| 208 |
| 209 void moveTo(int x, int y) { |
| 210 mPositionX = x; |
| 211 mPositionY = y; |
| 212 if (isPositionVisible()) { |
| 213 int[] coords = null; |
| 214 if (mContainer.isShowing()) { |
| 215 coords = mTempCoords; |
| 216 mParent.getLocationInWindow(coords); |
| 217 final int containerPositionX = coords[0] + mPositionX; |
| 218 final int containerPositionY = coords[1] + mPositionY; |
| 219 |
| 220 if (containerPositionX != mContainerPositionX || |
| 221 containerPositionY != mContainerPositionY) { |
| 222 mContainerPositionX = containerPositionX; |
| 223 mContainerPositionY = containerPositionY; |
| 224 |
| 225 mContainer.update(mContainerPositionX, mContainerPositionY, |
| 226 getRight() - getLeft(), getBottom() - getTop()); |
| 227 |
| 228 // Hide paste popup window as soon as a scroll occurs. |
| 229 if (mPastePopupWindow != null) { |
| 230 mPastePopupWindow.hide(); |
| 231 } |
| 232 } |
| 233 } else { |
| 234 show(); |
| 235 } |
| 236 |
| 237 if (mIsDragging) { |
| 238 if (coords == null) { |
| 239 coords = mTempCoords; |
| 240 mParent.getLocationInWindow(coords); |
| 241 } |
| 242 if (coords[0] != mLastParentX || coords[1] != mLastParentY) { |
| 243 mTouchToWindowOffsetX += coords[0] - mLastParentX; |
| 244 mTouchToWindowOffsetY += coords[1] - mLastParentY; |
| 245 mLastParentX = coords[0]; |
| 246 mLastParentY = coords[1]; |
| 247 } |
| 248 // Hide paste popup window as soon as the handle is dragged. |
| 249 if (mPastePopupWindow != null) { |
| 250 mPastePopupWindow.hide(); |
| 251 } |
| 252 } |
| 253 } else { |
| 254 hide(); |
| 255 } |
| 256 } |
| 257 |
| 258 @Override |
| 259 protected void onDraw(Canvas c) { |
| 260 mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop()
); |
| 261 mDrawable.draw(c); |
| 262 } |
| 263 |
| 264 @Override |
| 265 public boolean onTouchEvent(MotionEvent ev) { |
| 266 switch (ev.getActionMasked()) { |
| 267 case MotionEvent.ACTION_DOWN: { |
| 268 mDownPositionX = ev.getRawX(); |
| 269 mDownPositionY = ev.getRawY(); |
| 270 mTouchToWindowOffsetX = mDownPositionX - mPositionX; |
| 271 mTouchToWindowOffsetY = mDownPositionY - mPositionY; |
| 272 final int[] coords = mTempCoords; |
| 273 mParent.getLocationInWindow(coords); |
| 274 mLastParentX = coords[0]; |
| 275 mLastParentY = coords[1]; |
| 276 mIsDragging = true; |
| 277 mController.beforeStartUpdatingPosition(this); |
| 278 mTouchTimer = SystemClock.uptimeMillis(); |
| 279 break; |
| 280 } |
| 281 |
| 282 case MotionEvent.ACTION_MOVE: { |
| 283 updatePosition(ev.getRawX(), ev.getRawY()); |
| 284 break; |
| 285 } |
| 286 |
| 287 case MotionEvent.ACTION_UP: |
| 288 if (mIsInsertionHandle) { |
| 289 long delay = SystemClock.uptimeMillis() - mTouchTimer; |
| 290 if (delay < ViewConfiguration.getTapTimeout()) { |
| 291 if (mPastePopupWindow != null && mPastePopupWindow.isSho
wing()) { |
| 292 // Tapping on the handle dismisses the displayed pas
te view, |
| 293 mPastePopupWindow.hide(); |
| 294 } else { |
| 295 showPastePopupWindow(); |
| 296 } |
| 297 } |
| 298 } |
| 299 mIsDragging = false; |
| 300 break; |
| 301 |
| 302 case MotionEvent.ACTION_CANCEL: |
| 303 mIsDragging = false; |
| 304 break; |
| 305 } |
| 306 return true; |
| 307 } |
| 308 |
| 309 boolean isDragging() { |
| 310 return mIsDragging; |
| 311 } |
| 312 |
| 313 /** |
| 314 * @return Returns the x position of the handle |
| 315 */ |
| 316 int getPositionX() { |
| 317 return mPositionX; |
| 318 } |
| 319 |
| 320 /** |
| 321 * @return Returns the y position of the handle |
| 322 */ |
| 323 int getPositionY() { |
| 324 return mPositionY; |
| 325 } |
| 326 |
| 327 private void updatePosition(float rawX, float rawY) { |
| 328 final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX; |
| 329 final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY - mLineOf
fsetY; |
| 330 |
| 331 mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY
)); |
| 332 } |
| 333 |
| 334 void positionAt(int x, int y) { |
| 335 moveTo((int)(x - mHotspotX), (int)(y - mHotspotY)); |
| 336 } |
| 337 |
| 338 // Returns the x coordinate of the position that the handle appears to be po
inting to. |
| 339 int getAdjustedPositionX() { |
| 340 return (int) (mPositionX + mHotspotX); |
| 341 } |
| 342 |
| 343 // Returns the y coordinate of the position that the handle appears to be po
inting to. |
| 344 int getAdjustedPositionY() { |
| 345 return (int) (mPositionY + mHotspotY); |
| 346 } |
| 347 |
| 348 // Returns a suitable y coordinate for the text position corresponding to th
e handle. |
| 349 // As the handle points to a position on the base of the line of text, this
method |
| 350 // returns a coordinate a small number of pixels higher (i.e. a slightly sma
ller number) |
| 351 // than getAdjustedPositionY. |
| 352 int getLineAdjustedPositionY() { |
| 353 return (int) (mPositionY + mHotspotY - mLineOffsetY); |
| 354 } |
| 355 |
| 356 Drawable getDrawable() { |
| 357 return mDrawable; |
| 358 } |
| 359 |
| 360 void showPastePopupWindow() { |
| 361 InsertionHandleController ihc = (InsertionHandleController) mController; |
| 362 if (mIsInsertionHandle && ihc.canPaste()) { |
| 363 if (mPastePopupWindow == null) { |
| 364 // Lazy initialization: create when actually shown only. |
| 365 mPastePopupWindow = ihc.new PastePopupMenu(); |
| 366 } |
| 367 mPastePopupWindow.show(); |
| 368 } |
| 369 } |
| 370 } |
OLD | NEW |