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.ClipboardManager; | |
8 import android.content.Context; | |
9 import android.content.res.TypedArray; | |
10 import android.graphics.drawable.Drawable; | |
11 import android.view.Gravity; | |
12 import android.view.LayoutInflater; | |
13 import android.view.View; | |
14 import android.view.View.OnClickListener; | |
15 import android.view.ViewGroup; | |
16 import android.view.ViewGroup.LayoutParams; | |
17 import android.widget.PopupWindow; | |
18 | |
19 import com.google.common.annotations.VisibleForTesting; | |
20 | |
21 import org.chromium.content.browser.PositionObserver; | |
22 | |
23 /** | |
24 * CursorController for inserting text at the cursor position. | |
25 */ | |
26 public abstract class InsertionHandleController implements CursorController { | |
27 | |
28 /** The handle view, lazily created when first shown */ | |
29 private HandleView mHandle; | |
30 | |
31 /** The view over which the insertion handle should be shown */ | |
32 private final View mParent; | |
33 | |
34 /** True iff the insertion handle is currently showing */ | |
35 private boolean mIsShowing; | |
36 | |
37 /** True iff the insertion handle can be shown automatically when selection
changes */ | |
38 private boolean mAllowAutomaticShowing; | |
39 | |
40 private final Context mContext; | |
41 | |
42 private final PositionObserver mPositionObserver; | |
43 | |
44 public InsertionHandleController(View parent, PositionObserver positionObser
ver) { | |
45 mParent = parent; | |
46 | |
47 mContext = parent.getContext(); | |
48 mPositionObserver = positionObserver; | |
49 } | |
50 | |
51 /** Allows the handle to be shown automatically when cursor position changes
*/ | |
52 public void allowAutomaticShowing() { | |
53 mAllowAutomaticShowing = true; | |
54 } | |
55 | |
56 /** Disallows the handle from being shown automatically when cursor position
changes */ | |
57 public void hideAndDisallowAutomaticShowing() { | |
58 hide(); | |
59 mAllowAutomaticShowing = false; | |
60 } | |
61 | |
62 /** | |
63 * Shows the handle. | |
64 */ | |
65 public void showHandle() { | |
66 createHandleIfNeeded(); | |
67 showHandleIfNeeded(); | |
68 } | |
69 | |
70 void showPastePopup() { | |
71 if (mIsShowing) { | |
72 mHandle.showPastePopupWindow(); | |
73 } | |
74 } | |
75 | |
76 public void showHandleWithPastePopup() { | |
77 showHandle(); | |
78 showPastePopup(); | |
79 } | |
80 | |
81 /** | |
82 * @return whether the handle is being dragged. | |
83 */ | |
84 public boolean isDragging() { | |
85 return mHandle != null && mHandle.isDragging(); | |
86 } | |
87 | |
88 /** Shows the handle at the given coordinates, as long as automatic showing
is allowed */ | |
89 public void onCursorPositionChanged() { | |
90 if (mAllowAutomaticShowing) { | |
91 showHandle(); | |
92 } | |
93 } | |
94 | |
95 /** | |
96 * Moves the handle so that it points at the given coordinates. | |
97 * @param x Handle x in physical pixels. | |
98 * @param y Handle y in physical pixels. | |
99 */ | |
100 public void setHandlePosition(float x, float y) { | |
101 mHandle.positionAt((int) x, (int) y); | |
102 } | |
103 | |
104 /** | |
105 * If the handle is not visible, sets its visibility to View.VISIBLE and beg
ins fading it in. | |
106 */ | |
107 public void beginHandleFadeIn() { | |
108 mHandle.beginFadeIn(); | |
109 } | |
110 | |
111 /** | |
112 * Sets the handle to the given visibility. | |
113 */ | |
114 public void setHandleVisibility(int visibility) { | |
115 mHandle.setVisibility(visibility); | |
116 } | |
117 | |
118 int getHandleX() { | |
119 return mHandle.getAdjustedPositionX(); | |
120 } | |
121 | |
122 int getHandleY() { | |
123 return mHandle.getAdjustedPositionY(); | |
124 } | |
125 | |
126 @VisibleForTesting | |
127 public HandleView getHandleViewForTest() { | |
128 return mHandle; | |
129 } | |
130 | |
131 @Override | |
132 public void onTouchModeChanged(boolean isInTouchMode) { | |
133 if (!isInTouchMode) { | |
134 hide(); | |
135 } | |
136 } | |
137 | |
138 @Override | |
139 public void hide() { | |
140 if (mIsShowing) { | |
141 if (mHandle != null) mHandle.hide(); | |
142 mIsShowing = false; | |
143 } | |
144 } | |
145 | |
146 @Override | |
147 public boolean isShowing() { | |
148 return mIsShowing; | |
149 } | |
150 | |
151 @Override | |
152 public void beforeStartUpdatingPosition(HandleView handle) {} | |
153 | |
154 @Override | |
155 public void updatePosition(HandleView handle, int x, int y) { | |
156 setCursorPosition(x, y); | |
157 } | |
158 | |
159 /** | |
160 * The concrete implementation must cause the cursor position to move to the
given | |
161 * coordinates and (possibly asynchronously) set the insertion handle positi
on | |
162 * after the cursor position change is made via setHandlePosition. | |
163 * @param x | |
164 * @param y | |
165 */ | |
166 protected abstract void setCursorPosition(int x, int y); | |
167 | |
168 /** Pastes the contents of clipboard at the current insertion point */ | |
169 protected abstract void paste(); | |
170 | |
171 /** Returns the current line height in pixels */ | |
172 protected abstract int getLineHeight(); | |
173 | |
174 @Override | |
175 public void onDetached() {} | |
176 | |
177 boolean canPaste() { | |
178 return ((ClipboardManager) mContext.getSystemService( | |
179 Context.CLIPBOARD_SERVICE)).hasPrimaryClip(); | |
180 } | |
181 | |
182 private void createHandleIfNeeded() { | |
183 if (mHandle == null) { | |
184 mHandle = new HandleView(this, HandleView.CENTER, mParent, mPosition
Observer); | |
185 } | |
186 } | |
187 | |
188 private void showHandleIfNeeded() { | |
189 if (!mIsShowing) { | |
190 mIsShowing = true; | |
191 mHandle.show(); | |
192 setHandleVisibility(HandleView.VISIBLE); | |
193 } | |
194 } | |
195 | |
196 /* | |
197 * This class is based on TextView.PastePopupMenu. | |
198 */ | |
199 class PastePopupMenu implements OnClickListener { | |
200 private final PopupWindow mContainer; | |
201 private int mPositionX; | |
202 private int mPositionY; | |
203 private final View[] mPasteViews; | |
204 private final int[] mPasteViewLayouts; | |
205 | |
206 public PastePopupMenu() { | |
207 mContainer = new PopupWindow(mContext, null, | |
208 android.R.attr.textSelectHandleWindowStyle); | |
209 mContainer.setSplitTouchEnabled(true); | |
210 mContainer.setClippingEnabled(false); | |
211 | |
212 mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); | |
213 mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); | |
214 | |
215 final int[] POPUP_LAYOUT_ATTRS = { | |
216 android.R.attr.textEditPasteWindowLayout, | |
217 android.R.attr.textEditNoPasteWindowLayout, | |
218 android.R.attr.textEditSidePasteWindowLayout, | |
219 android.R.attr.textEditSideNoPasteWindowLayout, | |
220 }; | |
221 | |
222 mPasteViews = new View[POPUP_LAYOUT_ATTRS.length]; | |
223 mPasteViewLayouts = new int[POPUP_LAYOUT_ATTRS.length]; | |
224 | |
225 TypedArray attrs = mContext.obtainStyledAttributes(POPUP_LAYOUT_ATTR
S); | |
226 for (int i = 0; i < attrs.length(); ++i) { | |
227 mPasteViewLayouts[i] = attrs.getResourceId(attrs.getIndex(i), 0)
; | |
228 } | |
229 attrs.recycle(); | |
230 } | |
231 | |
232 private int viewIndex(boolean onTop) { | |
233 return (onTop ? 0 : 1 << 1) + (canPaste() ? 0 : 1 << 0); | |
234 } | |
235 | |
236 private void updateContent(boolean onTop) { | |
237 final int viewIndex = viewIndex(onTop); | |
238 View view = mPasteViews[viewIndex]; | |
239 | |
240 if (view == null) { | |
241 final int layout = mPasteViewLayouts[viewIndex]; | |
242 LayoutInflater inflater = (LayoutInflater) mContext. | |
243 getSystemService(Context.LAYOUT_INFLATER_SERVICE); | |
244 if (inflater != null) { | |
245 view = inflater.inflate(layout, null); | |
246 } | |
247 | |
248 if (view == null) { | |
249 throw new IllegalArgumentException("Unable to inflate TextEd
it paste window"); | |
250 } | |
251 | |
252 final int size = View.MeasureSpec.makeMeasureSpec(0, View.Measur
eSpec.UNSPECIFIED); | |
253 view.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRA
P_CONTENT, | |
254 ViewGroup.LayoutParams.WRAP_CONTENT)); | |
255 view.measure(size, size); | |
256 | |
257 view.setOnClickListener(this); | |
258 | |
259 mPasteViews[viewIndex] = view; | |
260 } | |
261 | |
262 mContainer.setContentView(view); | |
263 } | |
264 | |
265 void show() { | |
266 updateContent(true); | |
267 positionAtCursor(); | |
268 } | |
269 | |
270 void hide() { | |
271 mContainer.dismiss(); | |
272 } | |
273 | |
274 boolean isShowing() { | |
275 return mContainer.isShowing(); | |
276 } | |
277 | |
278 @Override | |
279 public void onClick(View v) { | |
280 if (canPaste()) { | |
281 paste(); | |
282 } | |
283 hide(); | |
284 } | |
285 | |
286 void positionAtCursor() { | |
287 View contentView = mContainer.getContentView(); | |
288 int width = contentView.getMeasuredWidth(); | |
289 int height = contentView.getMeasuredHeight(); | |
290 | |
291 int lineHeight = getLineHeight(); | |
292 | |
293 mPositionX = (int) (mHandle.getAdjustedPositionX() - width / 2.0f); | |
294 mPositionY = mHandle.getAdjustedPositionY() - height - lineHeight; | |
295 | |
296 final int[] coords = new int[2]; | |
297 mParent.getLocationInWindow(coords); | |
298 coords[0] += mPositionX; | |
299 coords[1] += mPositionY; | |
300 | |
301 final int screenWidth = mContext.getResources().getDisplayMetrics().
widthPixels; | |
302 if (coords[1] < 0) { | |
303 updateContent(false); | |
304 // Update dimensions from new view | |
305 contentView = mContainer.getContentView(); | |
306 width = contentView.getMeasuredWidth(); | |
307 height = contentView.getMeasuredHeight(); | |
308 | |
309 // Vertical clipping, move under edited line and to the side of
insertion cursor | |
310 // TODO bottom clipping in case there is no system bar | |
311 coords[1] += height; | |
312 coords[1] += lineHeight; | |
313 | |
314 // Move to right hand side of insertion cursor by default. TODO
RTL text. | |
315 final Drawable handle = mHandle.getDrawable(); | |
316 final int handleHalfWidth = handle.getIntrinsicWidth() / 2; | |
317 | |
318 if (mHandle.getAdjustedPositionX() + width < screenWidth) { | |
319 coords[0] += handleHalfWidth + width / 2; | |
320 } else { | |
321 coords[0] -= handleHalfWidth + width / 2; | |
322 } | |
323 } else { | |
324 // Horizontal clipping | |
325 coords[0] = Math.max(0, coords[0]); | |
326 coords[0] = Math.min(screenWidth - width, coords[0]); | |
327 } | |
328 | |
329 mContainer.showAtLocation(mParent, Gravity.NO_GRAVITY, coords[0], co
ords[1]); | |
330 } | |
331 } | |
332 } | |
OLD | NEW |