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.input; 6 7 import android.view.View; 8 9 import com.google.common.annotations.VisibleForTesting; 10 11 import org.chromium.content.browser.PositionObserver; 12 13 /** 14 * CursorController for selecting a range of text. 15 */ 16 public abstract class SelectionHandleController implements CursorController { 17 18 // The following constants match the ones in 19 // third_party/WebKit/public/web/WebTextDirection.h 20 private static final int TEXT_DIRECTION_DEFAULT = 0; 21 private static final int TEXT_DIRECTION_LTR = 1; 22 private static final int TEXT_DIRECTION_RTL = 2; 23 24 /** The cursor controller images, lazily created when shown. */ 25 private HandleView mStartHandle, mEndHandle; 26 27 /** Whether handles should show automatically when text is selected. */ 28 private boolean mAllowAutomaticShowing = true; 29 30 /** Whether selection anchors are active. */ 31 private boolean mIsShowing; 32 33 private View mParent; 34 35 private int mFixedHandleX; 36 private int mFixedHandleY; 37 38 private PositionObserver mPositionObserver; 39 40 public SelectionHandleController(View parent, PositionObserver positionObserver) { 41 mParent = parent; 42 mPositionObserver = positionObserver; 43 } 44 45 /** Automatically show selection anchors when text is selected. */ 46 public void allowAutomaticShowing() { 47 mAllowAutomaticShowing = true; 48 } 49 50 /** Hide selection anchors, and don't automatically show them. */ 51 public void hideAndDisallowAutomaticShowing() { 52 hide(); 53 mAllowAutomaticShowing = false; 54 } 55 56 @Override 57 public boolean isShowing() { 58 return mIsShowing; 59 } 60 61 @Override 62 public void hide() { 63 if (mIsShowing) { 64 if (mStartHandle != null) mStartHandle.hide(); 65 if (mEndHandle != null) mEndHandle.hide(); 66 mIsShowing = false; 67 } 68 } 69 70 void cancelFadeOutAnimation() { 71 hide(); 72 } 73 74 /** 75 * Updates the selection for a movement of the given handle (which 76 * should be the start handle or end handle) to coordinates x,y. 77 * Note that this will not actually result in the handle moving to (x,y): 78 * selectBetweenCoordinates(x1,y1,x2,y2) will trigger the selection and set the 79 * actual coordinates later via set[Start|End]HandlePosition. 80 */ 81 @Override 82 public void updatePosition(HandleView handle, int x, int y) { 83 selectBetweenCoordinates(mFixedHandleX, mFixedHandleY, x, y); 84 } 85 86 @Override 87 public void beforeStartUpdatingPosition(HandleView handle) { 88 HandleView fixedHandle = (handle == mStartHandle) ? mEndHandle : mStartHandle; 89 mFixedHandleX = fixedHandle.getAdjustedPositionX(); 90 mFixedHandleY = fixedHandle.getLineAdjustedPositionY(); 91 } 92 93 /** 94 * The concrete implementation must trigger a selection between the given 95 * coordinates and (possibly asynchronously) set the actual handle positions 96 * after the selection is made via set[Start|End]HandlePosition. 97 */ 98 protected abstract void selectBetweenCoordinates(int x1, int y1, int x2, int y2); 99 100 /** 101 * @return true iff this controller is being used to move the selection start. 102 */ 103 boolean isSelectionStartDragged() { 104 return mStartHandle != null && mStartHandle.isDragging(); 105 } 106 107 /** 108 * @return true iff this controller is being used to drag either the selection start or end. 109 */ 110 public boolean isDragging() { 111 return (mStartHandle != null && mStartHandle.isDragging()) || 112 (mEndHandle != null && mEndHandle.isDragging()); 113 } 114 115 @Override 116 public void onTouchModeChanged(boolean isInTouchMode) { 117 if (!isInTouchMode) { 118 hide(); 119 } 120 } 121 122 @Override 123 public void onDetached() {} 124 125 /** 126 * Moves the start handle so that it points at the given coordinates. 127 * @param x The start handle position X in physical pixels. 128 * @param y The start handle position Y in physical pixels. 129 */ 130 public void setStartHandlePosition(float x, float y) { 131 mStartHandle.positionAt((int) x, (int) y); 132 } 133 134 /** 135 * Moves the end handle so that it points at the given coordinates. 136 * @param x The end handle position X in physical pixels. 137 * @param y The end handle position Y in physical pixels. 138 */ 139 public void setEndHandlePosition(float x, float y) { 140 mEndHandle.positionAt((int) x, (int) y); 141 } 142 143 /** 144 * If the handles are not visible, sets their visibility to View.VISIBLE and begins fading them 145 * in. 146 */ 147 public void beginHandleFadeIn() { 148 mStartHandle.beginFadeIn(); 149 mEndHandle.beginFadeIn(); 150 } 151 152 /** 153 * Sets the start and end handles to the given visibility. 154 */ 155 public void setHandleVisibility(int visibility) { 156 mStartHandle.setVisibility(visibility); 157 mEndHandle.setVisibility(visibility); 158 } 159 160 /** 161 * Shows the handles if allowed. 162 * 163 * @param startDir Direction (left/right) of start handle. 164 * @param endDir Direction (left/right) of end handle. 165 */ 166 public void onSelectionChanged(int startDir, int endDir) { 167 if (mAllowAutomaticShowing) { 168 showHandles(startDir, endDir); 169 } 170 } 171 172 /** 173 * Sets both start and end position and show the handles. 174 * Note: this method does not trigger a selection, see 175 * selectBetweenCoordinates() 176 * 177 * @param startDir Direction (left/right) of start handle. 178 * @param endDir Direction (left/right) of end handle. 179 */ 180 public void showHandles(int startDir, int endDir) { 181 createHandlesIfNeeded(startDir, endDir); 182 showHandlesIfNeeded(); 183 } 184 185 @VisibleForTesting 186 public HandleView getStartHandleViewForTest() { 187 return mStartHandle; 188 } 189 190 @VisibleForTesting 191 public HandleView getEndHandleViewForTest() { 192 return mEndHandle; 193 } 194 195 private void createHandlesIfNeeded(int startDir, int endDir) { 196 if (mStartHandle == null) { 197 mStartHandle = new HandleView(this, 198 startDir == TEXT_DIRECTION_RTL ? HandleView.RIGHT : HandleView.LEFT, mParent, 199 mPositionObserver); 200 } 201 if (mEndHandle == null) { 202 mEndHandle = new HandleView(this, 203 endDir == TEXT_DIRECTION_RTL ? HandleView.LEFT : HandleView.RIGHT, mParent, 204 mPositionObserver); 205 } 206 } 207 208 private void showHandlesIfNeeded() { 209 if (!mIsShowing) { 210 mIsShowing = true; 211 mStartHandle.show(); 212 mEndHandle.show(); 213 setHandleVisibility(HandleView.VISIBLE); 214 } 215 } 216 } 217