| 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 |