From f2b233d44c796ceab3048a0130682fb5da3db557 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Tue, 7 Apr 2015 21:02:13 +0900 Subject: [PATCH] Introduce Ctrl+Backspace/Ctrl+ForwardDelete shortcut. Ctrl+Backspace deletes preceding one word and Ctrl+ForwardDelete delets following one word. Used Chrome's implementation/behvior as a reference. Bug: 17810742 Change-Id: I7b2ca515b2f017e457f7104ec7b8e7473ca4e081 --- .../android/text/method/BaseKeyListener.java | 76 +++++++++++++++++-- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java index 63607fa34872e..07c1ec3a9ff99 100644 --- a/core/java/android/text/method/BaseKeyListener.java +++ b/core/java/android/text/method/BaseKeyListener.java @@ -22,6 +22,8 @@ import android.text.*; import android.text.method.TextKeyListener.Capitalize; import android.widget.TextView; +import java.text.BreakIterator; + /** * Abstract base class for key listeners. * @@ -63,9 +65,9 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode, KeyEvent event, boolean isForwardDelete) { - // Ensure the key event does not have modifiers except ALT or SHIFT. + // Ensure the key event does not have modifiers except ALT or SHIFT or CTRL. if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState() - & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK))) { + & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK))) { return false; } @@ -74,18 +76,28 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener return true; } - // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible. - if (getMetaState(content, META_ALT_ON, event) == 1) { - if (deleteLine(view, content)) { - return true; + // MetaKeyKeyListener doesn't track control key state. Need to check the KeyEvent instead. + boolean isCtrlActive = ((event.getMetaState() & KeyEvent.META_CTRL_ON) != 0); + boolean isShiftActive = (getMetaState(content, META_SHIFT_ON, event) == 1); + boolean isAltActive = (getMetaState(content, META_ALT_ON, event) == 1); + + if (isCtrlActive) { + if (isAltActive || isShiftActive) { + // Ctrl+Alt, Ctrl+Shift, Ctrl+Alt+Shift should not delete any characters. + return false; } + return deleteUntilWordBoundary(view, content, isForwardDelete); + } + + // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible. + if (isAltActive && deleteLine(view, content)) { + return true; } // Delete a character. final int start = Selection.getSelectionEnd(content); final int end; - if (isForwardDelete || event.isShiftPressed() - || getMetaState(content, META_SHIFT_ON) == 1) { + if (isForwardDelete || event.isShiftPressed() || isShiftActive) { end = TextUtils.getOffsetAfter(content, start); } else { end = TextUtils.getOffsetBefore(content, start); @@ -97,6 +109,54 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener return false; } + private boolean deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete) { + int currentCursorOffset = Selection.getSelectionStart(content); + + // If there is a selection, do nothing. + if (currentCursorOffset != Selection.getSelectionEnd(content)) { + return false; + } + + // Early exit if there is no contents to delete. + if ((!isForwardDelete && currentCursorOffset == 0) || + (isForwardDelete && currentCursorOffset == content.length())) { + return false; + } + + WordIterator wordIterator = null; + if (view instanceof TextView) { + wordIterator = ((TextView)view).getWordIterator(); + } + + if (wordIterator == null) { + // Default locale is used for WordIterator since the appropriate locale is not clear + // here. + // TODO: Use appropriate locale for WordIterator. + wordIterator = new WordIterator(); + } + + int deleteFrom; + int deleteTo; + + if (isForwardDelete) { + deleteFrom = currentCursorOffset; + wordIterator.setCharSequence(content, deleteFrom, content.length()); + deleteTo = wordIterator.following(currentCursorOffset); + if (deleteTo == BreakIterator.DONE) { + deleteTo = content.length(); + } + } else { + deleteTo = currentCursorOffset; + wordIterator.setCharSequence(content, 0, deleteTo); + deleteFrom = wordIterator.preceding(currentCursorOffset); + if (deleteFrom == BreakIterator.DONE) { + deleteFrom = 0; + } + } + content.delete(deleteFrom, deleteTo); + return true; + } + private boolean deleteSelection(View view, Editable content) { int selectionStart = Selection.getSelectionStart(content); int selectionEnd = Selection.getSelectionEnd(content);