TextView: Open context menu on right click.
- Construct context menu in #onCreateContextMenu. - Ignore non-primary buttons for selection. - Don't open context menu on long click. Bug: 19544351 Bug: 19397276 Bug: 24872170 Change-Id: I4690e1485682be4f9b504b8fb6af936b979f13b8
This commit is contained in:
@@ -72,6 +72,7 @@ import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ActionMode.Callback;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.DisplayListCanvas;
|
||||
import android.view.DragEvent;
|
||||
import android.view.Gravity;
|
||||
@@ -135,13 +136,16 @@ public class Editor {
|
||||
// Tag used when the Editor maintains its own separate UndoManager.
|
||||
private static final String UNDO_OWNER_TAG = "Editor";
|
||||
|
||||
// Ordering constants used to place the Action Mode items in their menu.
|
||||
private static final int MENU_ITEM_ORDER_CUT = 1;
|
||||
private static final int MENU_ITEM_ORDER_COPY = 2;
|
||||
private static final int MENU_ITEM_ORDER_PASTE = 3;
|
||||
private static final int MENU_ITEM_ORDER_SHARE = 4;
|
||||
private static final int MENU_ITEM_ORDER_SELECT_ALL = 5;
|
||||
private static final int MENU_ITEM_ORDER_REPLACE = 6;
|
||||
// Ordering constants used to place the Action Mode or context menu items in their menu.
|
||||
private static final int MENU_ITEM_ORDER_UNDO = 1;
|
||||
private static final int MENU_ITEM_ORDER_REDO = 2;
|
||||
private static final int MENU_ITEM_ORDER_CUT = 3;
|
||||
private static final int MENU_ITEM_ORDER_COPY = 4;
|
||||
private static final int MENU_ITEM_ORDER_PASTE = 5;
|
||||
private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 6;
|
||||
private static final int MENU_ITEM_ORDER_SHARE = 7;
|
||||
private static final int MENU_ITEM_ORDER_SELECT_ALL = 8;
|
||||
private static final int MENU_ITEM_ORDER_REPLACE = 9;
|
||||
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 10;
|
||||
|
||||
// Each Editor manages its own undo stack.
|
||||
@@ -184,6 +188,7 @@ public class Editor {
|
||||
|
||||
boolean mDiscardNextActionUp;
|
||||
boolean mIgnoreActionUpEvent;
|
||||
private boolean mIgnoreNextMouseActionUpOrDown;
|
||||
|
||||
long mShowCursor;
|
||||
Blink mBlink;
|
||||
@@ -209,6 +214,8 @@ public class Editor {
|
||||
boolean mPreserveDetachedSelection;
|
||||
boolean mTemporaryDetach;
|
||||
|
||||
boolean mIsBeingLongClicked;
|
||||
|
||||
SuggestionsPopupWindow mSuggestionsPopupWindow;
|
||||
SuggestionRangeSpan mSuggestionRangeSpan;
|
||||
Runnable mShowSuggestionRunnable;
|
||||
@@ -224,6 +231,7 @@ public class Editor {
|
||||
private PositionListener mPositionListener;
|
||||
|
||||
float mLastDownPositionX, mLastDownPositionY;
|
||||
private float mContextMenuAnchorX, mContextMenuAnchorY;
|
||||
Callback mCustomSelectionActionModeCallback;
|
||||
Callback mCustomInsertionActionModeCallback;
|
||||
|
||||
@@ -239,6 +247,9 @@ public class Editor {
|
||||
// Only for mouse input.
|
||||
private static final int TAP_STATE_TRIPLE_CLICK = 3;
|
||||
|
||||
// The button state as of the last time #onTouchEvent is called.
|
||||
private int mLastButtonState;
|
||||
|
||||
private Runnable mInsertionActionModeRunnable;
|
||||
|
||||
// The span controller helps monitoring the changes to which the Editor needs to react:
|
||||
@@ -1314,7 +1325,33 @@ public class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldFilterOutTouchEvent(MotionEvent event) {
|
||||
if (!event.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
||||
return false;
|
||||
}
|
||||
final boolean primaryButtonStateChanged =
|
||||
((mLastButtonState ^ event.getButtonState()) & MotionEvent.BUTTON_PRIMARY) != 0;
|
||||
final int action = event.getActionMasked();
|
||||
if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP)
|
||||
&& !primaryButtonStateChanged) {
|
||||
return true;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_MOVE
|
||||
&& !event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onTouchEvent(MotionEvent event) {
|
||||
final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
|
||||
mLastButtonState = event.getButtonState();
|
||||
if (filterOutEvent) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
||||
mDiscardNextActionUp = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
updateTapState(event);
|
||||
updateFloatingToolbarVisibility(event);
|
||||
|
||||
@@ -2318,6 +2355,84 @@ public class Editor {
|
||||
text.setSpan(mSpanController, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
|
||||
void setContextMenuAnchor(float x, float y) {
|
||||
mContextMenuAnchorX = x;
|
||||
mContextMenuAnchorY = y;
|
||||
}
|
||||
|
||||
void onCreateContextMenu(ContextMenu menu) {
|
||||
if (mIsBeingLongClicked || Float.isNaN(mContextMenuAnchorX)
|
||||
|| Float.isNaN(mContextMenuAnchorY)) {
|
||||
return;
|
||||
}
|
||||
final int offset = mTextView.getOffsetForPosition(mContextMenuAnchorX, mContextMenuAnchorY);
|
||||
if (offset == -1) {
|
||||
return;
|
||||
}
|
||||
final boolean isOnSelection = mTextView.hasSelection()
|
||||
&& offset >= mTextView.getSelectionStart() && offset <= mTextView.getSelectionEnd();
|
||||
if (!isOnSelection) {
|
||||
// Right clicked position is not on the selection. Remove the selection and move the
|
||||
// cursor to the right clicked position.
|
||||
stopTextActionMode();
|
||||
Selection.setSelection((Spannable) mTextView.getText(), offset);
|
||||
}
|
||||
|
||||
// TODO: Add suggestions in the context menu.
|
||||
|
||||
menu.add(Menu.NONE, TextView.ID_UNDO, MENU_ITEM_ORDER_UNDO,
|
||||
com.android.internal.R.string.undo)
|
||||
.setAlphabeticShortcut('z')
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
|
||||
.setEnabled(mTextView.canUndo());
|
||||
menu.add(Menu.NONE, TextView.ID_REDO, MENU_ITEM_ORDER_REDO,
|
||||
com.android.internal.R.string.redo)
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
|
||||
.setEnabled(mTextView.canRedo());
|
||||
|
||||
menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
|
||||
com.android.internal.R.string.cut)
|
||||
.setAlphabeticShortcut('x')
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
|
||||
.setEnabled(mTextView.canCut());
|
||||
menu.add(Menu.NONE, TextView.ID_COPY, MENU_ITEM_ORDER_COPY,
|
||||
com.android.internal.R.string.copy)
|
||||
.setAlphabeticShortcut('c')
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
|
||||
.setEnabled(mTextView.canCopy());
|
||||
menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE,
|
||||
com.android.internal.R.string.paste)
|
||||
.setAlphabeticShortcut('v')
|
||||
.setEnabled(mTextView.canPaste())
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
|
||||
menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT,
|
||||
com.android.internal.R.string.paste_as_plain_text)
|
||||
.setEnabled(mTextView.canPaste())
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
|
||||
menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
|
||||
com.android.internal.R.string.share)
|
||||
.setEnabled(mTextView.canShare())
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
|
||||
menu.add(Menu.NONE, TextView.ID_SELECT_ALL, MENU_ITEM_ORDER_SELECT_ALL,
|
||||
com.android.internal.R.string.selectAll)
|
||||
.setAlphabeticShortcut('a')
|
||||
.setEnabled(mTextView.canSelectAllText())
|
||||
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
|
||||
|
||||
mPreserveDetachedSelection = true;
|
||||
}
|
||||
|
||||
private final MenuItem.OnMenuItemClickListener mOnContextMenuItemClickListener =
|
||||
new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (mProcessTextIntentActionsHandler.performMenuItemAction(item)) {
|
||||
return true;
|
||||
}
|
||||
return mTextView.onTextContextMenuItem(item.getItemId());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
|
||||
* pop-up should be displayed.
|
||||
@@ -2710,6 +2825,9 @@ public class Editor {
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
if (!isShowing()) {
|
||||
return;
|
||||
}
|
||||
mPopupWindow.dismiss();
|
||||
getPositionListener().removeSubscriber(this);
|
||||
}
|
||||
@@ -2759,8 +2877,10 @@ public class Editor {
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
if (!isShowing()) {
|
||||
return;
|
||||
}
|
||||
super.dismiss();
|
||||
|
||||
getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
|
||||
|
||||
// Safe cast since show() checks that mTextView.getText() is an Editable
|
||||
|
||||
@@ -111,6 +111,7 @@ import android.util.TypedValue;
|
||||
import android.view.AccessibilityIterators.TextSegmentIterator;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Choreographer;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.DragEvent;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
@@ -8497,6 +8498,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateContextMenu(ContextMenu menu) {
|
||||
if (mEditor != null) {
|
||||
mEditor.onCreateContextMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showContextMenu() {
|
||||
if (mEditor != null) {
|
||||
mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
|
||||
}
|
||||
return super.showContextMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showContextMenu(float x, float y) {
|
||||
if (mEditor != null) {
|
||||
mEditor.setContextMenuAnchor(x, y);
|
||||
}
|
||||
return super.showContextMenu(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True iff this TextView contains a text that can be edited, or if this is
|
||||
* a selectable TextView.
|
||||
@@ -9398,12 +9422,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
public boolean performLongClick() {
|
||||
boolean handled = false;
|
||||
|
||||
if (mEditor != null) {
|
||||
mEditor.mIsBeingLongClicked = true;
|
||||
}
|
||||
|
||||
if (super.performLongClick()) {
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (mEditor != null) {
|
||||
handled |= mEditor.performLongClick(handled);
|
||||
mEditor.mIsBeingLongClicked = false;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
|
||||
@@ -2453,6 +2453,9 @@
|
||||
<!-- Item on EditText context menu. This action is used to paste from the clipboard into the eidt field -->
|
||||
<string name="paste">Paste</string>
|
||||
|
||||
<!-- Item on EditText context menu. This action is used to paste from the clipboard into the eidt field without formatting -->
|
||||
<string name="paste_as_plain_text">Paste as plain text</string>
|
||||
|
||||
<!-- Item on EditText context menu. This action is used to replace the current word by other suggested words, suggested by the IME or the spell checker -->
|
||||
<string name="replace">Replace\u2026</string>
|
||||
|
||||
@@ -2465,6 +2468,12 @@
|
||||
<!-- Item on EditText context menu. Added only when the context menu is not empty, it enable selection context mode. [CHAR LIMIT=20] -->
|
||||
<string name="selectTextMode">Select text</string>
|
||||
|
||||
<!-- Item on EditText context menu. This action is used to undo a text edit operation. -->
|
||||
<string name="undo">Undo</string>
|
||||
|
||||
<!-- Item on EditText context menu. This action is used to redo a text edit operation. -->
|
||||
<string name="redo">Redo</string>
|
||||
|
||||
<!-- Text selection contextual mode title, displayed in the CAB. [CHAR LIMIT=20] -->
|
||||
<string name="textSelectionCABTitle">Text selection</string>
|
||||
|
||||
|
||||
@@ -456,7 +456,10 @@
|
||||
<java-symbol type="string" name="notification_title" />
|
||||
<java-symbol type="string" name="permission_request_notification_with_subtitle" />
|
||||
<java-symbol type="string" name="prepend_shortcut_label" />
|
||||
<java-symbol type="string" name="paste_as_plain_text" />
|
||||
<java-symbol type="string" name="replace" />
|
||||
<java-symbol type="string" name="undo" />
|
||||
<java-symbol type="string" name="redo" />
|
||||
<java-symbol type="string" name="textSelectionCABTitle" />
|
||||
<java-symbol type="string" name="BaMmi" />
|
||||
<java-symbol type="string" name="CLIRDefaultOffNextCallOff" />
|
||||
|
||||
Reference in New Issue
Block a user