diff options
author | 2010-08-26 12:22:58 -0700 | |
---|---|---|
committer | 2010-09-01 14:16:50 -0700 | |
commit | b00a1d0c0adbdfc507676772201e979e539a2801 (patch) | |
tree | c55b1fbfcfa5f1521b01efe2cc02d6411aed0cee /java/src/com/android/inputmethod/latin/EditingUtil.java | |
parent | c6cb2ec1f3264a7b626022bcfdc8da180b87920c (diff) | |
download | latinime-b00a1d0c0adbdfc507676772201e979e539a2801.tar.gz latinime-b00a1d0c0adbdfc507676772201e979e539a2801.tar.xz latinime-b00a1d0c0adbdfc507676772201e979e539a2801.zip |
Correction improvements - use the new InputConnection APIs for underlining and
fetching the selected text.
Bug: 2950652
Some refinements to correction behavior, such as retaining the tap-to-save
when in correction mode.
Use reflection to access the new InputConnection APIs with fallbacks, in case
we run on older OS versions.
Some refactoring to separate lookup for voice alternatives and typed alternatives.
Change-Id: I7c4178bf7e6b47ee274c49fa7a694f8c2d50cea7
Diffstat (limited to 'java/src/com/android/inputmethod/latin/EditingUtil.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/EditingUtil.java | 188 |
1 files changed, 148 insertions, 40 deletions
diff --git a/java/src/com/android/inputmethod/latin/EditingUtil.java b/java/src/com/android/inputmethod/latin/EditingUtil.java index be31cb787..781d7fd4a 100644 --- a/java/src/com/android/inputmethod/latin/EditingUtil.java +++ b/java/src/com/android/inputmethod/latin/EditingUtil.java @@ -16,10 +16,13 @@ package com.android.inputmethod.latin; +import android.text.TextUtils; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.regex.Pattern; /** @@ -31,6 +34,11 @@ public class EditingUtil { */ private static final int LOOKBACK_CHARACTER_NUM = 15; + // Cache Method pointers + private static boolean sMethodsInitialized; + private static Method sMethodGetSelectedText; + private static Method sMethodSetComposingRegion; + private EditingUtil() {}; /** @@ -65,36 +73,16 @@ public class EditingUtil { return extracted.startOffset + extracted.selectionStart; } - private static int getSelectionEnd(InputConnection connection) { - ExtractedText extracted = connection.getExtractedText( - new ExtractedTextRequest(), 0); - if (extracted == null) { - return -1; - } - return extracted.startOffset + extracted.selectionEnd; - } - /** * @param connection connection to the current text field. * @param sep characters which may separate words + * @param range the range object to store the result into * @return the word that surrounds the cursor, including up to one trailing * separator. For example, if the field contains "he|llo world", where | * represents the cursor, then "hello " will be returned. */ public static String getWordAtCursor( - InputConnection connection, String separators) { - return getWordAtCursor(connection, separators, null); - } - - /** - * @param connection connection to the current text field. - * @param sep characters which may separate words - * @return the word that surrounds the cursor, including up to one trailing - * separator. For example, if the field contains "he|llo world", where | - * represents the cursor, then "hello " will be returned. - */ - public static String getWordAtCursor( - InputConnection connection, String separators, Range range) { + InputConnection connection, String separators, Range range) { Range r = getWordRangeAtCursor(connection, separators, range); return (r == null) ? null : r.word; } @@ -204,26 +192,146 @@ public class EditingUtil { } } + public static class SelectedWord { + public int start; + public int end; + public CharSequence word; + } + /** - * Checks if the cursor is touching/inside a word or the selection is for a whole - * word and no more and no less. - * @param range the Range object that contains the bounds of the word around the cursor - * @param start the start of the selection - * @param end the end of the selection, which could be the same as the start, if text is not - * in selection mode - * @return false if the selection is a partial word or straddling multiple words, true if - * the selection is a full word or there is no selection. + * Takes a character sequence with a single character and checks if the character occurs + * in a list of word separators or is empty. + * @param singleChar A CharSequence with null, zero or one character + * @param wordSeparators A String containing the word separators + * @return true if the character is at a word boundary, false otherwise */ - public static boolean isFullWordOrInside(Range range, int start, int end) { - // Is the cursor inside or touching a word? - if (start == end) return true; - - // Is it a selection? Then is the start of the selection the start of the word and - // the size of the selection the size of the word? Then return true - if (start < end - && (range.charsBefore == 0 && range.charsAfter == end - start)) { - return true; + private static boolean isWordBoundary(CharSequence singleChar, String wordSeparators) { + return TextUtils.isEmpty(singleChar) || wordSeparators.contains(singleChar); + } + + /** + * Checks if the cursor is inside a word or the current selection is a whole word. + * @param ic the InputConnection for accessing the text field + * @param selStart the start position of the selection within the text field + * @param selEnd the end position of the selection within the text field. This could be + * the same as selStart, if there's no selection. + * @param wordSeparators the word separator characters for the current language + * @return an object containing the text and coordinates of the selected/touching word, + * null if the selection/cursor is not marking a whole word. + */ + public static SelectedWord getWordAtCursorOrSelection(final InputConnection ic, + int selStart, int selEnd, String wordSeparators) { + if (selStart == selEnd) { + // There is just a cursor, so get the word at the cursor + EditingUtil.Range range = new EditingUtil.Range(); + CharSequence touching = getWordAtCursor(ic, wordSeparators, range); + if (!TextUtils.isEmpty(touching)) { + SelectedWord selWord = new SelectedWord(); + selWord.word = touching; + selWord.start = selStart - range.charsBefore; + selWord.end = selEnd + range.charsAfter; + return selWord; + } + } else { + // Is the previous character empty or a word separator? If not, return null. + CharSequence charsBefore = ic.getTextBeforeCursor(1, 0); + if (!isWordBoundary(charsBefore, wordSeparators)) { + return null; + } + + // Is the next character empty or a word separator? If not, return null. + CharSequence charsAfter = ic.getTextAfterCursor(1, 0); + if (!isWordBoundary(charsAfter, wordSeparators)) { + return null; + } + + // Extract the selection alone + CharSequence touching = getSelectedText(ic, selStart, selEnd); + if (TextUtils.isEmpty(touching)) return null; + // Is any part of the selection a separator? If so, return null. + final int length = touching.length(); + for (int i = 0; i < length; i++) { + if (wordSeparators.contains(touching.subSequence(i, i + 1))) { + return null; + } + } + // Prepare the selected word + SelectedWord selWord = new SelectedWord(); + selWord.start = selStart; + selWord.end = selEnd; + selWord.word = touching; + return selWord; + } + return null; + } + + /** + * Cache method pointers for performance + */ + private static void initializeMethodsForReflection() { + try { + // These will either both exist or not, so no need for separate try/catch blocks. + // If other methods are added later, use separate try/catch blocks. + sMethodGetSelectedText = InputConnection.class.getMethod("getSelectedText", int.class); + sMethodSetComposingRegion = InputConnection.class.getMethod("setComposingRegion", + int.class, int.class); + } catch (NoSuchMethodException exc) { + // Ignore + } + sMethodsInitialized = true; + } + + /** + * Returns the selected text between the selStart and selEnd positions. + */ + private static CharSequence getSelectedText(InputConnection ic, int selStart, int selEnd) { + // Use reflection, for backward compatibility + CharSequence result = null; + if (!sMethodsInitialized) { + initializeMethodsForReflection(); + } + if (sMethodGetSelectedText != null) { + try { + result = (CharSequence) sMethodGetSelectedText.invoke(ic, 0); + return result; + } catch (InvocationTargetException exc) { + // Ignore + } catch (IllegalArgumentException e) { + // Ignore + } catch (IllegalAccessException e) { + // Ignore + } + } + // Reflection didn't work, try it the poor way, by moving the cursor to the start, + // getting the text after the cursor and moving the text back to selected mode. + // TODO: Verify that this works properly in conjunction with + // LatinIME#onUpdateSelection + ic.setSelection(selStart, selEnd); + result = ic.getTextAfterCursor(selEnd - selStart, 0); + ic.setSelection(selStart, selEnd); + return result; + } + + /** + * Tries to set the text into composition mode if there is support for it in the framework. + */ + public static void underlineWord(InputConnection ic, SelectedWord word) { + // Use reflection, for backward compatibility + // If method not found, there's nothing we can do. It still works but just wont underline + // the word. + if (!sMethodsInitialized) { + initializeMethodsForReflection(); + } + if (sMethodSetComposingRegion != null) { + try { + sMethodSetComposingRegion.invoke(ic, word.start, word.end); + } catch (InvocationTargetException exc) { + // Ignore + } catch (IllegalArgumentException e) { + // Ignore + } catch (IllegalAccessException e) { + // Ignore + } } - return false; } } |