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