| OLD | NEW |
| (Empty) |
| 1 // Copyright 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.input; | |
| 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.MotionEvent; | |
| 15 import android.view.View; | |
| 16 import android.view.ViewConfiguration; | |
| 17 import android.view.ViewParent; | |
| 18 import android.view.animation.AnimationUtils; | |
| 19 import android.widget.PopupWindow; | |
| 20 | |
| 21 import org.chromium.content.browser.PositionObserver; | |
| 22 | |
| 23 /** | |
| 24 * View that displays a selection or insertion handle for text editing. | |
| 25 * | |
| 26 * While a HandleView is logically a child of some other view, it does not exist
in that View's | |
| 27 * hierarchy. | |
| 28 * | |
| 29 */ | |
| 30 public class HandleView extends View { | |
| 31 private static final float FADE_DURATION = 200.f; | |
| 32 | |
| 33 private Drawable mDrawable; | |
| 34 private final PopupWindow mContainer; | |
| 35 | |
| 36 // The position of the handle relative to the parent view. | |
| 37 private int mPositionX; | |
| 38 private int mPositionY; | |
| 39 | |
| 40 // The position of the parent relative to the application's root view. | |
| 41 private int mParentPositionX; | |
| 42 private int mParentPositionY; | |
| 43 | |
| 44 // The offset from this handles position to the "tip" of the handle. | |
| 45 private float mHotspotX; | |
| 46 private float mHotspotY; | |
| 47 | |
| 48 private final CursorController mController; | |
| 49 private boolean mIsDragging; | |
| 50 private float mTouchToWindowOffsetX; | |
| 51 private float mTouchToWindowOffsetY; | |
| 52 | |
| 53 private final int mLineOffsetY; | |
| 54 private float mDownPositionX, mDownPositionY; | |
| 55 private long mTouchTimer; | |
| 56 private boolean mIsInsertionHandle = false; | |
| 57 private float mAlpha; | |
| 58 private long mFadeStartTime; | |
| 59 | |
| 60 private final View mParent; | |
| 61 private InsertionHandleController.PastePopupMenu mPastePopupWindow; | |
| 62 | |
| 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 private final PositionObserver mParentPositionObserver; | |
| 70 private final PositionObserver.Listener mParentPositionListener; | |
| 71 | |
| 72 // Number of dips to subtract from the handle's y position to give a suitabl
e | |
| 73 // y coordinate for the corresponding text position. This is to compensate f
or the fact | |
| 74 // that the handle position is at the base of the line of text. | |
| 75 private static final float LINE_OFFSET_Y_DIP = 5.0f; | |
| 76 | |
| 77 private static final int[] TEXT_VIEW_HANDLE_ATTRS = { | |
| 78 android.R.attr.textSelectHandleLeft, | |
| 79 android.R.attr.textSelectHandle, | |
| 80 android.R.attr.textSelectHandleRight, | |
| 81 }; | |
| 82 | |
| 83 HandleView(CursorController controller, int pos, View parent, | |
| 84 PositionObserver parentPositionObserver) { | |
| 85 super(parent.getContext()); | |
| 86 mParent = parent; | |
| 87 Context context = mParent.getContext(); | |
| 88 mController = controller; | |
| 89 mContainer = new PopupWindow(context, null, android.R.attr.textSelectHan
dleWindowStyle); | |
| 90 mContainer.setSplitTouchEnabled(true); | |
| 91 mContainer.setClippingEnabled(false); | |
| 92 mContainer.setAnimationStyle(0); | |
| 93 | |
| 94 setOrientation(pos); | |
| 95 | |
| 96 // Convert line offset dips to pixels. | |
| 97 mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_D
IP, | |
| 98 LINE_OFFSET_Y_DIP, context.getResources().getDisplayMetrics()); | |
| 99 | |
| 100 mAlpha = 1.f; | |
| 101 | |
| 102 mParentPositionListener = new PositionObserver.Listener() { | |
| 103 @Override | |
| 104 public void onPositionChanged(int x, int y) { | |
| 105 updateParentPosition(x, y); | |
| 106 } | |
| 107 }; | |
| 108 mParentPositionObserver = parentPositionObserver; | |
| 109 } | |
| 110 | |
| 111 void setOrientation(int pos) { | |
| 112 Context context = mParent.getContext(); | |
| 113 TypedArray a = context.getTheme().obtainStyledAttributes(TEXT_VIEW_HANDL
E_ATTRS); | |
| 114 mDrawable = a.getDrawable(pos); | |
| 115 a.recycle(); | |
| 116 | |
| 117 mIsInsertionHandle = (pos == CENTER); | |
| 118 | |
| 119 int handleWidth = mDrawable.getIntrinsicWidth(); | |
| 120 switch (pos) { | |
| 121 case LEFT: { | |
| 122 mHotspotX = (handleWidth * 3) / 4f; | |
| 123 break; | |
| 124 } | |
| 125 | |
| 126 case RIGHT: { | |
| 127 mHotspotX = handleWidth / 4f; | |
| 128 break; | |
| 129 } | |
| 130 | |
| 131 case CENTER: | |
| 132 default: { | |
| 133 mHotspotX = handleWidth / 2f; | |
| 134 break; | |
| 135 } | |
| 136 } | |
| 137 mHotspotY = 0; | |
| 138 | |
| 139 invalidate(); | |
| 140 } | |
| 141 | |
| 142 @Override | |
| 143 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| 144 setMeasuredDimension(mDrawable.getIntrinsicWidth(), | |
| 145 mDrawable.getIntrinsicHeight()); | |
| 146 } | |
| 147 | |
| 148 private void updateParentPosition(int parentPositionX, int parentPositionY)
{ | |
| 149 // Hide paste popup window as soon as a scroll occurs. | |
| 150 if (mPastePopupWindow != null) mPastePopupWindow.hide(); | |
| 151 | |
| 152 mTouchToWindowOffsetX += parentPositionX - mParentPositionX; | |
| 153 mTouchToWindowOffsetY += parentPositionY - mParentPositionY; | |
| 154 mParentPositionX = parentPositionX; | |
| 155 mParentPositionY = parentPositionY; | |
| 156 onPositionChanged(); | |
| 157 } | |
| 158 | |
| 159 private int getContainerPositionX() { | |
| 160 return mParentPositionX + mPositionX; | |
| 161 } | |
| 162 | |
| 163 private int getContainerPositionY() { | |
| 164 return mParentPositionY + mPositionY; | |
| 165 } | |
| 166 | |
| 167 private void onPositionChanged() { | |
| 168 // Deferring View invalidation while the handles are hidden prevents | |
| 169 // scheduling conflicts with the compositor. | |
| 170 if (getVisibility() != VISIBLE) return; | |
| 171 mContainer.update(getContainerPositionX(), getContainerPositionY(), | |
| 172 getRight() - getLeft(), getBottom() - getTop()); | |
| 173 } | |
| 174 | |
| 175 private void showContainer() { | |
| 176 mContainer.showAtLocation(mParent, 0, getContainerPositionX(), getContai
nerPositionY()); | |
| 177 } | |
| 178 | |
| 179 void show() { | |
| 180 // While hidden, the parent position may have become stale. It must be u
pdated before | |
| 181 // checking isPositionVisible(). | |
| 182 updateParentPosition(mParentPositionObserver.getPositionX(), | |
| 183 mParentPositionObserver.getPositionY()); | |
| 184 if (!isPositionVisible()) { | |
| 185 hide(); | |
| 186 return; | |
| 187 } | |
| 188 mParentPositionObserver.addListener(mParentPositionListener); | |
| 189 mContainer.setContentView(this); | |
| 190 showContainer(); | |
| 191 | |
| 192 // Hide paste view when handle is moved on screen. | |
| 193 if (mPastePopupWindow != null) { | |
| 194 mPastePopupWindow.hide(); | |
| 195 } | |
| 196 } | |
| 197 | |
| 198 void hide() { | |
| 199 mIsDragging = false; | |
| 200 mContainer.dismiss(); | |
| 201 mParentPositionObserver.removeListener(mParentPositionListener); | |
| 202 if (mPastePopupWindow != null) { | |
| 203 mPastePopupWindow.hide(); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 boolean isShowing() { | |
| 208 return mContainer.isShowing(); | |
| 209 } | |
| 210 | |
| 211 private boolean isPositionVisible() { | |
| 212 // Always show a dragging handle. | |
| 213 if (mIsDragging) { | |
| 214 return true; | |
| 215 } | |
| 216 | |
| 217 final Rect clip = mTempRect; | |
| 218 clip.left = 0; | |
| 219 clip.top = 0; | |
| 220 clip.right = mParent.getWidth(); | |
| 221 clip.bottom = mParent.getHeight(); | |
| 222 | |
| 223 final ViewParent parent = mParent.getParent(); | |
| 224 if (parent == null || !parent.getChildVisibleRect(mParent, clip, null))
{ | |
| 225 return false; | |
| 226 } | |
| 227 | |
| 228 final int posX = getContainerPositionX() + (int) mHotspotX; | |
| 229 final int posY = getContainerPositionY() + (int) mHotspotY; | |
| 230 | |
| 231 return posX >= clip.left && posX <= clip.right && | |
| 232 posY >= clip.top && posY <= clip.bottom; | |
| 233 } | |
| 234 | |
| 235 // x and y are in physical pixels. | |
| 236 void moveTo(int x, int y) { | |
| 237 int previousPositionX = mPositionX; | |
| 238 int previousPositionY = mPositionY; | |
| 239 | |
| 240 mPositionX = x; | |
| 241 mPositionY = y; | |
| 242 if (isPositionVisible()) { | |
| 243 if (mContainer.isShowing()) { | |
| 244 onPositionChanged(); | |
| 245 // Hide paste popup window as soon as the handle is dragged. | |
| 246 if (mPastePopupWindow != null && | |
| 247 (previousPositionX != mPositionX || previousPositionY !=
mPositionY)) { | |
| 248 mPastePopupWindow.hide(); | |
| 249 } | |
| 250 } else { | |
| 251 show(); | |
| 252 } | |
| 253 | |
| 254 if (mIsDragging) { | |
| 255 // Hide paste popup window as soon as the handle is dragged. | |
| 256 if (mPastePopupWindow != null) { | |
| 257 mPastePopupWindow.hide(); | |
| 258 } | |
| 259 } | |
| 260 } else { | |
| 261 hide(); | |
| 262 } | |
| 263 } | |
| 264 | |
| 265 @Override | |
| 266 protected void onDraw(Canvas c) { | |
| 267 updateAlpha(); | |
| 268 mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop()
); | |
| 269 mDrawable.draw(c); | |
| 270 } | |
| 271 | |
| 272 @Override | |
| 273 public boolean onTouchEvent(MotionEvent ev) { | |
| 274 switch (ev.getActionMasked()) { | |
| 275 case MotionEvent.ACTION_DOWN: { | |
| 276 mDownPositionX = ev.getRawX(); | |
| 277 mDownPositionY = ev.getRawY(); | |
| 278 mTouchToWindowOffsetX = mDownPositionX - mPositionX; | |
| 279 mTouchToWindowOffsetY = mDownPositionY - mPositionY; | |
| 280 mIsDragging = true; | |
| 281 mController.beforeStartUpdatingPosition(this); | |
| 282 mTouchTimer = SystemClock.uptimeMillis(); | |
| 283 break; | |
| 284 } | |
| 285 | |
| 286 case MotionEvent.ACTION_MOVE: { | |
| 287 updatePosition(ev.getRawX(), ev.getRawY()); | |
| 288 break; | |
| 289 } | |
| 290 | |
| 291 case MotionEvent.ACTION_UP: | |
| 292 if (mIsInsertionHandle) { | |
| 293 long delay = SystemClock.uptimeMillis() - mTouchTimer; | |
| 294 if (delay < ViewConfiguration.getTapTimeout()) { | |
| 295 if (mPastePopupWindow != null && mPastePopupWindow.isSho
wing()) { | |
| 296 // Tapping on the handle dismisses the displayed pas
te view, | |
| 297 mPastePopupWindow.hide(); | |
| 298 } else { | |
| 299 showPastePopupWindow(); | |
| 300 } | |
| 301 } | |
| 302 } | |
| 303 mIsDragging = false; | |
| 304 break; | |
| 305 | |
| 306 case MotionEvent.ACTION_CANCEL: | |
| 307 mIsDragging = false; | |
| 308 break; | |
| 309 | |
| 310 default: | |
| 311 return false; | |
| 312 } | |
| 313 return true; | |
| 314 } | |
| 315 | |
| 316 boolean isDragging() { | |
| 317 return mIsDragging; | |
| 318 } | |
| 319 | |
| 320 /** | |
| 321 * @return Returns the x position of the handle | |
| 322 */ | |
| 323 int getPositionX() { | |
| 324 return mPositionX; | |
| 325 } | |
| 326 | |
| 327 /** | |
| 328 * @return Returns the y position of the handle | |
| 329 */ | |
| 330 int getPositionY() { | |
| 331 return mPositionY; | |
| 332 } | |
| 333 | |
| 334 private void updatePosition(float rawX, float rawY) { | |
| 335 final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX; | |
| 336 final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY - mLineOf
fsetY; | |
| 337 | |
| 338 mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY
)); | |
| 339 } | |
| 340 | |
| 341 // x and y are in physical pixels. | |
| 342 void positionAt(int x, int y) { | |
| 343 moveTo(x - Math.round(mHotspotX), y - Math.round(mHotspotY)); | |
| 344 } | |
| 345 | |
| 346 // Returns the x coordinate of the position that the handle appears to be po
inting to relative | |
| 347 // to the handles "parent" view. | |
| 348 int getAdjustedPositionX() { | |
| 349 return mPositionX + Math.round(mHotspotX); | |
| 350 } | |
| 351 | |
| 352 // Returns the y coordinate of the position that the handle appears to be po
inting to relative | |
| 353 // to the handles "parent" view. | |
| 354 int getAdjustedPositionY() { | |
| 355 return mPositionY + Math.round(mHotspotY); | |
| 356 } | |
| 357 | |
| 358 // Returns the x coordinate of the postion that the handle appears to be poi
nting to relative to | |
| 359 // the root view of the application. | |
| 360 int getRootViewRelativePositionX() { | |
| 361 return getContainerPositionX() + Math.round(mHotspotX); | |
| 362 } | |
| 363 | |
| 364 // Returns the y coordinate of the postion that the handle appears to be poi
nting to relative to | |
| 365 // the root view of the application. | |
| 366 int getRootViewRelativePositionY() { | |
| 367 return getContainerPositionY() + Math.round(mHotspotY); | |
| 368 } | |
| 369 | |
| 370 // Returns a suitable y coordinate for the text position corresponding to th
e handle. | |
| 371 // As the handle points to a position on the base of the line of text, this
method | |
| 372 // returns a coordinate a small number of pixels higher (i.e. a slightly sma
ller number) | |
| 373 // than getAdjustedPositionY. | |
| 374 int getLineAdjustedPositionY() { | |
| 375 return (int) (mPositionY + mHotspotY - mLineOffsetY); | |
| 376 } | |
| 377 | |
| 378 Drawable getDrawable() { | |
| 379 return mDrawable; | |
| 380 } | |
| 381 | |
| 382 private void updateAlpha() { | |
| 383 if (mAlpha == 1.f) return; | |
| 384 mAlpha = Math.min(1.f, | |
| 385 (AnimationUtils.currentAnimationTimeMillis() - mFadeStartTime) /
FADE_DURATION); | |
| 386 mDrawable.setAlpha((int) (255 * mAlpha)); | |
| 387 invalidate(); | |
| 388 } | |
| 389 | |
| 390 /** | |
| 391 * If the handle is not visible, sets its visibility to View.VISIBLE and beg
ins fading it in. | |
| 392 */ | |
| 393 void beginFadeIn() { | |
| 394 if (getVisibility() == VISIBLE) return; | |
| 395 mAlpha = 0.f; | |
| 396 mFadeStartTime = AnimationUtils.currentAnimationTimeMillis(); | |
| 397 setVisibility(VISIBLE); | |
| 398 // Position updates may have been deferred while the handle was hidden. | |
| 399 onPositionChanged(); | |
| 400 } | |
| 401 | |
| 402 void showPastePopupWindow() { | |
| 403 InsertionHandleController ihc = (InsertionHandleController) mController; | |
| 404 if (mIsInsertionHandle && ihc.canPaste()) { | |
| 405 if (mPastePopupWindow == null) { | |
| 406 // Lazy initialization: create when actually shown only. | |
| 407 mPastePopupWindow = ihc.new PastePopupMenu(); | |
| 408 } | |
| 409 mPastePopupWindow.show(); | |
| 410 } | |
| 411 } | |
| 412 } | |
| OLD | NEW |