// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.content.browser.input; import android.view.View; import com.google.common.annotations.VisibleForTesting; import org.chromium.content.browser.PositionObserver; /** * CursorController for selecting a range of text. */ public abstract class SelectionHandleController implements CursorController { // The following constants match the ones in // third_party/WebKit/public/web/WebTextDirection.h private static final int TEXT_DIRECTION_DEFAULT = 0; private static final int TEXT_DIRECTION_LTR = 1; private static final int TEXT_DIRECTION_RTL = 2; /** The cursor controller images, lazily created when shown. */ private HandleView mStartHandle, mEndHandle; /** Whether handles should show automatically when text is selected. */ private boolean mAllowAutomaticShowing = true; /** Whether selection anchors are active. */ private boolean mIsShowing; private View mParent; private int mFixedHandleX; private int mFixedHandleY; private PositionObserver mPositionObserver; public SelectionHandleController(View parent, PositionObserver positionObserver) { mParent = parent; mPositionObserver = positionObserver; } /** Automatically show selection anchors when text is selected. */ public void allowAutomaticShowing() { mAllowAutomaticShowing = true; } /** Hide selection anchors, and don't automatically show them. */ public void hideAndDisallowAutomaticShowing() { hide(); mAllowAutomaticShowing = false; } @Override public boolean isShowing() { return mIsShowing; } @Override public void hide() { if (mIsShowing) { if (mStartHandle != null) mStartHandle.hide(); if (mEndHandle != null) mEndHandle.hide(); mIsShowing = false; } } void cancelFadeOutAnimation() { hide(); } /** * Updates the selection for a movement of the given handle (which * should be the start handle or end handle) to coordinates x,y. * Note that this will not actually result in the handle moving to (x,y): * selectBetweenCoordinates(x1,y1,x2,y2) will trigger the selection and set the * actual coordinates later via set[Start|End]HandlePosition. */ @Override public void updatePosition(HandleView handle, int x, int y) { selectBetweenCoordinates(mFixedHandleX, mFixedHandleY, x, y); } @Override public void beforeStartUpdatingPosition(HandleView handle) { HandleView fixedHandle = (handle == mStartHandle) ? mEndHandle : mStartHandle; mFixedHandleX = fixedHandle.getAdjustedPositionX(); mFixedHandleY = fixedHandle.getLineAdjustedPositionY(); } /** * The concrete implementation must trigger a selection between the given * coordinates and (possibly asynchronously) set the actual handle positions * after the selection is made via set[Start|End]HandlePosition. */ protected abstract void selectBetweenCoordinates(int x1, int y1, int x2, int y2); /** * @return true iff this controller is being used to move the selection start. */ boolean isSelectionStartDragged() { return mStartHandle != null && mStartHandle.isDragging(); } /** * @return true iff this controller is being used to drag either the selection start or end. */ public boolean isDragging() { return (mStartHandle != null && mStartHandle.isDragging()) || (mEndHandle != null && mEndHandle.isDragging()); } @Override public void onTouchModeChanged(boolean isInTouchMode) { if (!isInTouchMode) { hide(); } } @Override public void onDetached() {} /** * Moves the start handle so that it points at the given coordinates. * @param x The start handle position X in physical pixels. * @param y The start handle position Y in physical pixels. */ public void setStartHandlePosition(float x, float y) { mStartHandle.positionAt((int) x, (int) y); } /** * Moves the end handle so that it points at the given coordinates. * @param x The end handle position X in physical pixels. * @param y The end handle position Y in physical pixels. */ public void setEndHandlePosition(float x, float y) { mEndHandle.positionAt((int) x, (int) y); } /** * If the handles are not visible, sets their visibility to View.VISIBLE and begins fading them * in. */ public void beginHandleFadeIn() { mStartHandle.beginFadeIn(); mEndHandle.beginFadeIn(); } /** * Sets the start and end handles to the given visibility. */ public void setHandleVisibility(int visibility) { mStartHandle.setVisibility(visibility); mEndHandle.setVisibility(visibility); } /** * Shows the handles if allowed. * * @param startDir Direction (left/right) of start handle. * @param endDir Direction (left/right) of end handle. */ public void onSelectionChanged(int startDir, int endDir) { if (mAllowAutomaticShowing) { showHandles(startDir, endDir); } } /** * Sets both start and end position and show the handles. * Note: this method does not trigger a selection, see * selectBetweenCoordinates() * * @param startDir Direction (left/right) of start handle. * @param endDir Direction (left/right) of end handle. */ public void showHandles(int startDir, int endDir) { createHandlesIfNeeded(startDir, endDir); showHandlesIfNeeded(); } @VisibleForTesting public HandleView getStartHandleViewForTest() { return mStartHandle; } @VisibleForTesting public HandleView getEndHandleViewForTest() { return mEndHandle; } private void createHandlesIfNeeded(int startDir, int endDir) { if (mStartHandle == null) { mStartHandle = new HandleView(this, startDir == TEXT_DIRECTION_RTL ? HandleView.RIGHT : HandleView.LEFT, mParent, mPositionObserver); } if (mEndHandle == null) { mEndHandle = new HandleView(this, endDir == TEXT_DIRECTION_RTL ? HandleView.LEFT : HandleView.RIGHT, mParent, mPositionObserver); } } private void showHandlesIfNeeded() { if (!mIsShowing) { mIsShowing = true; mStartHandle.show(); mEndHandle.show(); setHandleVisibility(HandleView.VISIBLE); } } }