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