Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/HandleView.java

Issue 11068010: Show selection handles around selected text. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Addressed sky's comments Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698