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
5package org.chromium.content.browser.input;
6
7import android.view.View;
8
9import com.google.common.annotations.VisibleForTesting;
10
11import org.chromium.content.browser.PositionObserver;
12
13/**
14 * CursorController for selecting a range of text.
15 */
16public 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