Suggestion improvements (#93)
* Predictive mode: when automatic text case is selected, capitalized dictionary words are displayed correctly (Dutch and German) * Predictive mode: when generated and dictionary suggestions are displayed together, the dictionary suggestions come first * Removed a nonsense Bulgarian word * Added "it's" and "let's" to English * InputMode now takes care of the text case
This commit is contained in:
parent
10099f1c37
commit
f152232bbe
18 changed files with 223 additions and 155 deletions
|
|
@ -230042,7 +230042,6 @@
|
|||
домопритежателка
|
||||
домостроителство
|
||||
домоуправителите
|
||||
донякогадоникъде
|
||||
дореволюционната
|
||||
дореволюционните
|
||||
дореволюционното
|
||||
|
|
|
|||
|
|
@ -15658,6 +15658,7 @@ lester
|
|||
lester's
|
||||
lestrade
|
||||
lestrade's
|
||||
let's
|
||||
leta
|
||||
leta's
|
||||
letha
|
||||
|
|
@ -93569,6 +93570,7 @@ isthmus's
|
|||
isthmuses
|
||||
istle
|
||||
it'd
|
||||
it's
|
||||
it'll
|
||||
itacolumite
|
||||
italicization
|
||||
|
|
|
|||
|
|
@ -58,18 +58,12 @@ public class InputModeValidator {
|
|||
return newMode;
|
||||
}
|
||||
|
||||
public static int validateTextCase(T9Preferences prefs, int textCase, ArrayList<Integer> allowedTextCases) {
|
||||
if (allowedTextCases.size() > 0 && allowedTextCases.contains(textCase)) {
|
||||
return textCase;
|
||||
public static void validateTextCase(T9Preferences prefs, InputMode inputMode, int newTextCase) {
|
||||
if (!inputMode.setTextCase(newTextCase)) {
|
||||
inputMode.defaultTextCase();
|
||||
Logger.w("tt9/validateTextCase", "Invalid text case: " + newTextCase + " Enforcing: " + inputMode.getTextCase());
|
||||
}
|
||||
|
||||
int newCase = allowedTextCases.size() > 0 ? allowedTextCases.get(0) : InputMode.CASE_LOWER;
|
||||
prefs.saveTextCase(newCase);
|
||||
|
||||
if (textCase != newCase) {
|
||||
Logger.w("tt9/validateTextCase", "Invalid text case: " + textCase + " Enforcing: " + newCase);
|
||||
}
|
||||
|
||||
return newCase;
|
||||
prefs.saveTextCase(inputMode.getTextCase());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,6 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
private ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
||||
private InputMode mInputMode;
|
||||
|
||||
// text case
|
||||
private ArrayList<Integer> allowedTextCases = new ArrayList<>();
|
||||
private int mTextCase = InputMode.CASE_LOWER;
|
||||
|
||||
// language
|
||||
protected ArrayList<Integer> mEnabledLanguages;
|
||||
protected Language mLanguage;
|
||||
|
|
@ -45,21 +41,20 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
private void loadPreferences() {
|
||||
mLanguage = LanguageCollection.getLanguage(prefs.getInputLanguage());
|
||||
mEnabledLanguages = prefs.getEnabledLanguages();
|
||||
validateLanguages();
|
||||
|
||||
mInputMode = InputMode.getInstance(prefs.getInputMode());
|
||||
mTextCase = prefs.getTextCase();
|
||||
mInputMode = InputModeValidator.validateMode(prefs, mInputMode, allowedInputModes);
|
||||
|
||||
InputModeValidator.validateTextCase(prefs, mInputMode, prefs.getTextCase());
|
||||
}
|
||||
|
||||
|
||||
private void validateLanguages() {
|
||||
mEnabledLanguages = InputModeValidator.validateEnabledLanguages(prefs, mEnabledLanguages);
|
||||
mLanguage = InputModeValidator.validateLanguage(prefs, mLanguage, mEnabledLanguages);
|
||||
}
|
||||
|
||||
private void validatePreferences() {
|
||||
validateLanguages();
|
||||
mInputMode = InputModeValidator.validateMode(prefs, mInputMode, allowedInputModes);
|
||||
mTextCase = InputModeValidator.validateTextCase(prefs, mTextCase, allowedTextCases);
|
||||
}
|
||||
|
||||
|
||||
protected void onInit() {
|
||||
self = this;
|
||||
|
|
@ -74,19 +69,14 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
|
||||
|
||||
protected void onRestart(EditorInfo inputField) {
|
||||
determineNextTextCase();
|
||||
|
||||
// determine the valid state for the current input field and preferences
|
||||
determineAllowedInputModes(inputField);
|
||||
determineAllowedTextCases();
|
||||
mEnabledLanguages = prefs.getEnabledLanguages(); // in case we are back from Preferences screen, update the language list
|
||||
|
||||
// enforce a valid initial state
|
||||
validatePreferences();
|
||||
clearSuggestions();
|
||||
determineAllowedInputModes(inputField);
|
||||
determineNextTextCase(); // Only in some modes. If they support it, let's overwrite the default.
|
||||
|
||||
// build the UI
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
||||
clearSuggestions();
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode);
|
||||
softKeyHandler.show();
|
||||
if (!isInputViewShown()) {
|
||||
showWindow(true);
|
||||
|
|
@ -163,7 +153,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
|
||||
protected boolean onLeft() {
|
||||
if (mInputMode.clearWordStem()) {
|
||||
mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, getComposingText());
|
||||
mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, getComposingText());
|
||||
} else {
|
||||
jumpBeforeComposingText();
|
||||
}
|
||||
|
|
@ -175,7 +165,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
String filter = repeat ? mSuggestionView.getSuggestion(1) : getComposingText();
|
||||
|
||||
if (mInputMode.setWordStem(mLanguage, filter, repeat)) {
|
||||
mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, filter);
|
||||
mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, filter);
|
||||
} else if (filter.length() == 0) {
|
||||
mInputMode.reset();
|
||||
}
|
||||
|
|
@ -333,14 +323,14 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
|
||||
|
||||
private void getSuggestions() {
|
||||
if (!mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, mSuggestionView.getCurrentSuggestion())) {
|
||||
handleSuggestions(mInputMode.getSuggestions());
|
||||
if (!mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, mSuggestionView.getCurrentSuggestion())) {
|
||||
handleSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleSuggestions(ArrayList<String> suggestions) {
|
||||
setSuggestions(suggestions);
|
||||
private void handleSuggestions() {
|
||||
setSuggestions(mInputMode.getSuggestions(mLanguage));
|
||||
|
||||
// Put the first suggestion in the text field,
|
||||
// but cut it off to the length of the sequence (how many keys were pressed),
|
||||
|
|
@ -353,21 +343,24 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
|
||||
private final Handler handleSuggestionsAsync = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
handleSuggestions(msg.getData().getStringArrayList("suggestions"));
|
||||
public void handleMessage(Message m) {
|
||||
handleSuggestions();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private void setSuggestions(List<String> suggestions) {
|
||||
setSuggestions(suggestions, 0);
|
||||
}
|
||||
|
||||
private void setSuggestions(List<String> suggestions, int selectedIndex) {
|
||||
if (mSuggestionView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean show = suggestions != null && suggestions.size() > 0;
|
||||
|
||||
mSuggestionView.setSuggestions(suggestions, 0);
|
||||
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
|
||||
mSuggestionView.setSuggestions(suggestions, selectedIndex);
|
||||
setCandidatesViewShown(show);
|
||||
}
|
||||
|
||||
|
|
@ -410,29 +403,27 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
}
|
||||
// when typing a word or viewing scrolling the suggestions, only change the case
|
||||
else if (!isSuggestionViewHidden()) {
|
||||
determineAllowedTextCases();
|
||||
mInputMode.nextTextCase();
|
||||
ArrayList<String> switchedSuggestions = mInputMode.getSuggestions(mLanguage);
|
||||
|
||||
int modeIndex = (allowedTextCases.indexOf(mTextCase) + 1) % allowedTextCases.size();
|
||||
mTextCase = allowedTextCases.get(modeIndex);
|
||||
|
||||
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
|
||||
setSuggestions(switchedSuggestions, mSuggestionView.getCurrentIndex());
|
||||
setComposingText(getComposingText()); // no mistake, this forces the new text case
|
||||
}
|
||||
// make "abc" and "ABC" separate modes from user perspective
|
||||
else if (mInputMode.isABC() && mTextCase == InputMode.CASE_LOWER) {
|
||||
mTextCase = InputMode.CASE_UPPER;
|
||||
else if (mInputMode.isABC() && mInputMode.getTextCase() == InputMode.CASE_LOWER) {
|
||||
mInputMode.setTextCase(InputMode.CASE_UPPER);
|
||||
} else {
|
||||
int modeIndex = (allowedInputModes.indexOf(mInputMode.getId()) + 1) % allowedInputModes.size();
|
||||
mInputMode = InputMode.getInstance(allowedInputModes.get(modeIndex));
|
||||
|
||||
mTextCase = mInputMode.isPredictive() ? InputMode.CASE_CAPITALIZE : InputMode.CASE_LOWER;
|
||||
mInputMode.defaultTextCase();
|
||||
}
|
||||
|
||||
// save the settings for the next time
|
||||
prefs.saveInputMode(mInputMode);
|
||||
prefs.saveTextCase(mTextCase);
|
||||
prefs.saveTextCase(mInputMode.getTextCase());
|
||||
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -453,7 +444,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
// save it for the next time
|
||||
prefs.saveInputLanguage(mLanguage.getId());
|
||||
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -490,20 +481,11 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
}
|
||||
|
||||
|
||||
private void determineAllowedTextCases() {
|
||||
allowedTextCases = mInputMode.getAllowedTextCases();
|
||||
// @todo: determine the text case of the input and validate using the allowed ones [ https://github.com/sspanak/tt9/issues/48 ]
|
||||
}
|
||||
|
||||
|
||||
private void determineNextTextCase() {
|
||||
int nextTextCase = mInputMode.getNextWordTextCase(
|
||||
mTextCase,
|
||||
mInputMode.determineNextWordTextCase(
|
||||
InputFieldHelper.isThereText(currentInputConnection),
|
||||
(String) currentInputConnection.getTextBeforeCursor(50, 0)
|
||||
);
|
||||
|
||||
mTextCase = nextTextCase != -1 ? nextTextCase : mTextCase;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package io.github.sspanak.tt9.ime.modes;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
|
@ -19,7 +17,9 @@ abstract public class InputMode {
|
|||
public static final int CASE_UPPER = 0;
|
||||
public static final int CASE_CAPITALIZE = 1;
|
||||
public static final int CASE_LOWER = 2;
|
||||
public static final int CASE_DICTIONARY = 3; // do not force it, but use the dictionary word as-is
|
||||
protected ArrayList<Integer> allowedTextCases = new ArrayList<>();
|
||||
protected int textCase = CASE_LOWER;
|
||||
|
||||
// data
|
||||
protected ArrayList<String> suggestions = new ArrayList<>();
|
||||
|
|
@ -45,14 +45,16 @@ abstract public class InputMode {
|
|||
|
||||
// Suggestions
|
||||
public void onAcceptSuggestion(Language language, String suggestion) {}
|
||||
public ArrayList<String> getSuggestions() { return suggestions; }
|
||||
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) { return false; }
|
||||
protected void sendSuggestions(Handler handler, ArrayList<String> suggestions) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArrayList("suggestions", suggestions);
|
||||
Message msg = new Message();
|
||||
msg.setData(bundle);
|
||||
handler.sendMessage(msg);
|
||||
protected void onSuggestionsUpdated(Handler handler) { handler.sendEmptyMessage(0); }
|
||||
public boolean loadSuggestions(Handler handler, Language language, String lastWord) { return false; }
|
||||
|
||||
public ArrayList<String> getSuggestions(Language language) {
|
||||
ArrayList<String> newSuggestions = new ArrayList<>();
|
||||
for (String s : suggestions) {
|
||||
newSuggestions.add(adjustSuggestionTextCase(s, textCase, language));
|
||||
}
|
||||
|
||||
return newSuggestions;
|
||||
}
|
||||
|
||||
// Word
|
||||
|
|
@ -65,15 +67,38 @@ abstract public class InputMode {
|
|||
|
||||
// Utility
|
||||
abstract public int getId();
|
||||
public ArrayList<Integer> getAllowedTextCases() { return allowedTextCases; }
|
||||
// Perform any special logic to determine the text case of the next word, or return "-1" if there is no need to change it.
|
||||
public int getNextWordTextCase(int currentTextCase, boolean isThereText, String textBeforeCursor) { return -1; }
|
||||
abstract public int getSequenceLength(); // The number of key presses for the current word.
|
||||
public void reset() {
|
||||
suggestions = new ArrayList<>();
|
||||
word = null;
|
||||
}
|
||||
|
||||
// Text case
|
||||
public int getTextCase() { return textCase; }
|
||||
|
||||
public boolean setTextCase(int newTextCase) {
|
||||
if (!allowedTextCases.contains(newTextCase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
textCase = newTextCase;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void defaultTextCase() {
|
||||
textCase = allowedTextCases.get(0);
|
||||
}
|
||||
|
||||
public void nextTextCase() {
|
||||
int nextIndex = (allowedTextCases.indexOf(textCase) + 1) % allowedTextCases.size();
|
||||
textCase = allowedTextCases.get(nextIndex);
|
||||
}
|
||||
|
||||
public void determineNextWordTextCase(boolean isThereText, String textBeforeCursor) {}
|
||||
|
||||
// Based on the internal logic of the mode (punctuation or grammar rules), re-adjust the text case for when getSuggestions() is called.
|
||||
protected String adjustSuggestionTextCase(String word, int newTextCase, Language language) { return word; }
|
||||
|
||||
// Stem filtering.
|
||||
// Where applicable, return "true" if the mode supports it and the operation was possible.
|
||||
public boolean clearWordStem() { return false; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package io.github.sspanak.tt9.ime.modes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
|
||||
public class ModeABC extends InputMode {
|
||||
|
|
@ -19,10 +21,10 @@ public class ModeABC extends InputMode {
|
|||
word = null;
|
||||
|
||||
if (hold) {
|
||||
suggestions = null;
|
||||
suggestions = new ArrayList<>();
|
||||
word = String.valueOf(key);
|
||||
} else if (repeat) {
|
||||
suggestions = null;
|
||||
suggestions = new ArrayList<>();
|
||||
shouldSelectNextLetter = true;
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +32,11 @@ public class ModeABC extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
protected String adjustSuggestionTextCase(String word, int newTextCase, Language language) {
|
||||
return newTextCase == CASE_UPPER ? word.toUpperCase(language.getLocale()) : word.toLowerCase(language.getLocale());
|
||||
}
|
||||
|
||||
|
||||
final public boolean isABC() { return true; }
|
||||
public int getSequenceLength() { return 1; }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.os.Looper;
|
|||
import android.os.Message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
|
|
@ -29,11 +28,14 @@ public class ModePredictive extends InputMode {
|
|||
private String lastInputFieldWord = "";
|
||||
private static Handler handleSuggestionsExternal;
|
||||
|
||||
// auto text case selection
|
||||
private final Pattern endOfSentence = Pattern.compile("(?<!\\.)[.?!]\\s*$");
|
||||
|
||||
|
||||
ModePredictive() {
|
||||
allowedTextCases.add(CASE_UPPER);
|
||||
allowedTextCases.add(CASE_CAPITALIZE);
|
||||
allowedTextCases.add(CASE_LOWER);
|
||||
allowedTextCases.add(CASE_CAPITALIZE);
|
||||
allowedTextCases.add(CASE_UPPER);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -88,16 +90,6 @@ public class ModePredictive extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
final public boolean isPredictive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getSequenceLength() { return isEmoji ? 2 : digitSequence.length(); }
|
||||
|
||||
public boolean shouldTrackUpDown() { return true; }
|
||||
public boolean shouldTrackLeftRight() { return true; }
|
||||
|
||||
|
||||
/**
|
||||
* shouldAcceptCurrentSuggestion
|
||||
* In this mode, In addition to confirming the suggestion in the input field,
|
||||
|
|
@ -166,20 +158,21 @@ public class ModePredictive extends InputMode {
|
|||
|
||||
|
||||
/**
|
||||
* getSuggestionsAsync
|
||||
* loadSuggestions
|
||||
* Queries the dictionary database for a list of suggestions matching the current language and
|
||||
* sequence. Returns "false" when there is nothing to do.
|
||||
*
|
||||
* "lastWord" is used for generating suggestions when there are no results.
|
||||
* See: generatePossibleCompletions()
|
||||
*/
|
||||
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) {
|
||||
public boolean loadSuggestions(Handler handler, Language language, String lastWord) {
|
||||
if (isEmoji) {
|
||||
super.sendSuggestions(handler, suggestions);
|
||||
super.onSuggestionsUpdated(handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (digitSequence.length() == 0) {
|
||||
suggestions.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -209,17 +202,19 @@ public class ModePredictive extends InputMode {
|
|||
private final Handler handleSuggestions = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
ArrayList<String> suggestions = msg.getData().getStringArrayList("suggestions");
|
||||
suggestions = suggestions == null ? new ArrayList<>() : suggestions;
|
||||
ArrayList<String> dbSuggestions = msg.getData().getStringArrayList("suggestions");
|
||||
dbSuggestions = dbSuggestions == null ? new ArrayList<>() : dbSuggestions;
|
||||
|
||||
if (suggestions.size() == 0 && digitSequence.length() > 0) {
|
||||
suggestions = generatePossibleCompletions(currentLanguage, lastInputFieldWord);
|
||||
if (dbSuggestions.size() == 0 && digitSequence.length() > 0) {
|
||||
dbSuggestions = generatePossibleCompletions(currentLanguage, lastInputFieldWord);
|
||||
}
|
||||
|
||||
ArrayList<String> stemVariations = generatePossibleStemVariations(currentLanguage, suggestions);
|
||||
stemVariations.addAll(suggestions);
|
||||
suggestions.clear();
|
||||
suggestStem();
|
||||
suggestions.addAll(generatePossibleStemVariations(currentLanguage, dbSuggestions));
|
||||
suggestMoreWords(dbSuggestions);
|
||||
|
||||
ModePredictive.super.sendSuggestions(handleSuggestionsExternal, stemVariations);
|
||||
ModePredictive.super.onSuggestionsUpdated(handleSuggestionsExternal);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -269,22 +264,28 @@ public class ModePredictive extends InputMode {
|
|||
*
|
||||
* For example, if the filter is "extr", the current word is "extr_" and the user has pressed "1",
|
||||
* the database would have returned only "extra", but this function would also
|
||||
* generate: "extrb" and "extrc". This is useful for typing an unknown word, similar to the ones
|
||||
* in the dictionary.
|
||||
* generate: "extrb" and "extrc". This is useful for typing an unknown word, that is similar to
|
||||
* the ones in the dictionary.
|
||||
*/
|
||||
private ArrayList<String> generatePossibleStemVariations(Language language, ArrayList<String> currentSuggestions) {
|
||||
private ArrayList<String> generatePossibleStemVariations(Language language, ArrayList<String> dbSuggestions) {
|
||||
ArrayList<String> variations = new ArrayList<>();
|
||||
if (stem.length() == 0) {
|
||||
return variations;
|
||||
}
|
||||
|
||||
if (stem.length() == digitSequence.length() && !currentSuggestions.contains(stem)) {
|
||||
variations.add(stem);
|
||||
}
|
||||
|
||||
if (isStemFuzzy && stem.length() == digitSequence.length() - 1) {
|
||||
for (String word : generatePossibleCompletions(language, stem)) {
|
||||
if (!currentSuggestions.contains(word)) {
|
||||
ArrayList<String> allPossibleVariations = generatePossibleCompletions(language, stem);
|
||||
|
||||
// first add the known words, because it makes more sense to see them first
|
||||
for (String word : allPossibleVariations) {
|
||||
if (dbSuggestions.contains(word)) {
|
||||
variations.add(word);
|
||||
}
|
||||
}
|
||||
|
||||
// then add the unknown ones, so they can be used as possible beginnings of new words.
|
||||
for (String word : allPossibleVariations) {
|
||||
if (!dbSuggestions.contains(word)) {
|
||||
variations.add(word);
|
||||
}
|
||||
}
|
||||
|
|
@ -293,6 +294,30 @@ public class ModePredictive extends InputMode {
|
|||
return variations;
|
||||
}
|
||||
|
||||
/**
|
||||
* suggestStem
|
||||
* Add the current stem filter to the suggestion list, when it has length of X and
|
||||
* the user has pressed X keys.
|
||||
*/
|
||||
public void suggestStem() {
|
||||
if (stem.length() > 0 && stem.length() == digitSequence.length()) {
|
||||
suggestions.add(stem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* suggestMoreWords
|
||||
* Takes a list of words and appends them to the suggestion list, if they are missing.
|
||||
*/
|
||||
public void suggestMoreWords(ArrayList<String> newSuggestions) {
|
||||
for (String word : newSuggestions) {
|
||||
if (!suggestions.contains(word)) {
|
||||
suggestions.add(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* onAcceptSuggestion
|
||||
|
|
@ -316,28 +341,61 @@ public class ModePredictive extends InputMode {
|
|||
|
||||
|
||||
/**
|
||||
* getNextWordTextCase
|
||||
* Dynamically determine text case of words as the user types to reduce key presses.
|
||||
* adjustSuggestionTextCase
|
||||
* In addition to uppercase/lowercase, here we use the result from determineNextWordTextCase(),
|
||||
* to conveniently start sentences with capitals or whatnot.
|
||||
*
|
||||
* Also, by default we preserve any mixed case words in the dictionary,
|
||||
* for example: "dB", "Mb", proper names, German nouns, that always start with a capital,
|
||||
* or Dutch words such as: "'s-Hertogenbosch".
|
||||
*/
|
||||
protected String adjustSuggestionTextCase(String word, int newTextCase, Language language) {
|
||||
switch (newTextCase) {
|
||||
case CASE_UPPER:
|
||||
return word.toUpperCase(language.getLocale());
|
||||
case CASE_LOWER:
|
||||
return word.toLowerCase(language.getLocale());
|
||||
case CASE_CAPITALIZE:
|
||||
return language.isMixedCaseWord(word) ? word : language.capitalize(word);
|
||||
case CASE_DICTIONARY:
|
||||
return language.isMixedCaseWord(word) ? word : word.toLowerCase(language.getLocale());
|
||||
default:
|
||||
return word;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* determineNextWordTextCase
|
||||
* Dynamically determine text case of words as the user types, to reduce key presses.
|
||||
* For example, this function will return CASE_LOWER by default, but CASE_UPPER at the beginning
|
||||
* of a sentence.
|
||||
*/
|
||||
public int getNextWordTextCase(int currentTextCase, boolean isThereText, String textBeforeCursor) {
|
||||
public void determineNextWordTextCase(boolean isThereText, String textBeforeCursor) {
|
||||
// If the user wants to type in uppercase, this must be for a reason, so we better not override it.
|
||||
if (currentTextCase == CASE_UPPER) {
|
||||
return -1;
|
||||
if (textCase == CASE_UPPER) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start of text
|
||||
if (!isThereText) {
|
||||
return CASE_CAPITALIZE;
|
||||
textCase = CASE_CAPITALIZE;
|
||||
return;
|
||||
}
|
||||
|
||||
// start of sentence, excluding after "..."
|
||||
Matcher endOfSentenceMatch = Pattern.compile("(?<!\\.)[.?!]\\s*$").matcher(textBeforeCursor);
|
||||
if (endOfSentenceMatch.find()) {
|
||||
return CASE_CAPITALIZE;
|
||||
if (endOfSentence.matcher(textBeforeCursor).find()) {
|
||||
textCase = CASE_CAPITALIZE;
|
||||
return;
|
||||
}
|
||||
|
||||
return CASE_LOWER;
|
||||
textCase = CASE_DICTIONARY;
|
||||
}
|
||||
|
||||
|
||||
final public boolean isPredictive() { return true; }
|
||||
public int getSequenceLength() { return isEmoji ? 2 : digitSequence.length(); }
|
||||
public boolean shouldTrackUpDown() { return true; }
|
||||
public boolean shouldTrackLeftRight() { return true; }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,15 @@ public class Language {
|
|||
protected int id;
|
||||
protected String name;
|
||||
protected Locale locale;
|
||||
protected boolean isPunctuationPartOfWords; // see the getter for more info
|
||||
protected int icon;
|
||||
protected String dictionaryFile;
|
||||
protected int abcLowerCaseIcon;
|
||||
protected int abcUpperCaseIcon;
|
||||
protected ArrayList<ArrayList<String>> characterMap = new ArrayList<>();
|
||||
|
||||
// settings
|
||||
protected boolean isPunctuationPartOfWords; // see the getter for more info
|
||||
|
||||
final public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
@ -31,6 +33,15 @@ public class Language {
|
|||
return icon;
|
||||
}
|
||||
|
||||
final public String getDictionaryFile() {
|
||||
return dictionaryFile;
|
||||
}
|
||||
|
||||
final public int getAbcIcon(boolean lowerCase) {
|
||||
return lowerCase ? abcLowerCaseIcon : abcUpperCaseIcon;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* isPunctuationPartOfWords
|
||||
* This plays a role in Predictive mode only.
|
||||
|
|
@ -49,12 +60,15 @@ public class Language {
|
|||
*/
|
||||
final public boolean isPunctuationPartOfWords() { return isPunctuationPartOfWords; }
|
||||
|
||||
final public String getDictionaryFile() {
|
||||
return dictionaryFile;
|
||||
|
||||
/************* utility *************/
|
||||
|
||||
public String capitalize(String word) {
|
||||
return word != null ? word.substring(0, 1).toUpperCase(locale) + word.substring(1).toLowerCase(locale) : null;
|
||||
}
|
||||
|
||||
final public int getAbcIcon(boolean lowerCase) {
|
||||
return lowerCase ? abcLowerCaseIcon : abcUpperCaseIcon;
|
||||
public boolean isMixedCaseWord(String word) {
|
||||
return word != null && !word.toLowerCase(locale).equals(word) && !word.toUpperCase(locale).equals(word);
|
||||
}
|
||||
|
||||
public ArrayList<String> getKeyCharacters(int key) {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ public class Bulgarian extends Language {
|
|||
name = "български";
|
||||
locale = new Locale("bg","BG");
|
||||
dictionaryFile = "bg-utf8.txt";
|
||||
isPunctuationPartOfWords = false;
|
||||
icon = R.drawable.ime_lang_bg;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
|
||||
|
||||
isPunctuationPartOfWords = false;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ public class Dutch extends English {
|
|||
id = 8;
|
||||
name = "Nederlands";
|
||||
locale = new Locale("nl","NL");
|
||||
isPunctuationPartOfWords = true;
|
||||
dictionaryFile = "nl-utf8.txt";
|
||||
icon = R.drawable.ime_lang_nl;
|
||||
|
||||
isPunctuationPartOfWords = true;
|
||||
|
||||
characterMap.get(2).addAll(Arrays.asList("à", "ä", "ç"));
|
||||
characterMap.get(3).addAll(Arrays.asList("é", "è", "ê", "ë"));
|
||||
characterMap.get(4).addAll(Arrays.asList("î", "ï"));
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ public class English extends Language {
|
|||
name = "English";
|
||||
locale = Locale.ENGLISH;
|
||||
dictionaryFile = "en-utf8.txt";
|
||||
isPunctuationPartOfWords = true;
|
||||
icon = R.drawable.ime_lang_en;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_latin_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_latin_upper;
|
||||
|
||||
isPunctuationPartOfWords = true;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public class French extends English {
|
|||
locale = Locale.FRENCH;
|
||||
dictionaryFile = "fr-utf8.txt";
|
||||
icon = R.drawable.ime_lang_fr;
|
||||
|
||||
isPunctuationPartOfWords = false;
|
||||
|
||||
characterMap.get(2).addAll(Arrays.asList("à", "â", "æ", "ç"));
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public class German extends English {
|
|||
locale = Locale.GERMAN;
|
||||
dictionaryFile = "de-utf8.txt";
|
||||
icon = R.drawable.ime_lang_de;
|
||||
|
||||
isPunctuationPartOfWords = false;
|
||||
|
||||
characterMap.get(2).add("ä");
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public class Italian extends English {
|
|||
locale = Locale.ITALIAN;
|
||||
dictionaryFile = "it-utf8.txt";
|
||||
icon = R.drawable.ime_lang_it;
|
||||
|
||||
isPunctuationPartOfWords = false;
|
||||
|
||||
characterMap.get(2).add("à");
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ public class Russian extends Language {
|
|||
name = "русский";
|
||||
locale = new Locale("ru","RU");
|
||||
dictionaryFile = "ru-utf8.txt";
|
||||
isPunctuationPartOfWords = false;
|
||||
icon = R.drawable.ime_lang_ru;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
|
||||
|
||||
isPunctuationPartOfWords = false;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ public class Ukrainian extends Language {
|
|||
name = "українська";
|
||||
locale = new Locale("uk","UA");
|
||||
dictionaryFile = "uk-utf8.txt";
|
||||
isPunctuationPartOfWords = true;
|
||||
icon = R.drawable.ime_lang_uk;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
|
||||
|
||||
isPunctuationPartOfWords = true;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
|
|
|
|||
|
|
@ -10,14 +10,12 @@ import android.view.View;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||
|
||||
public class CandidateView extends View {
|
||||
|
||||
private List<String> mSuggestions;
|
||||
private List<String> mSuggestions = new ArrayList<>();
|
||||
protected int mSelectedIndex;
|
||||
|
||||
private Drawable mSelectionHighlight;
|
||||
|
|
@ -32,7 +30,7 @@ public class CandidateView extends View {
|
|||
|
||||
private static final int X_GAP = 10;
|
||||
|
||||
private static final List<String> EMPTY_LIST = new ArrayList<String>();
|
||||
private static final List<String> EMPTY_LIST = new ArrayList<>();
|
||||
|
||||
private int mColorNormal;
|
||||
private int mColorRecommended;
|
||||
|
|
@ -47,8 +45,6 @@ public class CandidateView extends View {
|
|||
|
||||
/**
|
||||
* Construct a CandidateView for showing suggested words for completion.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public CandidateView(Context context) {
|
||||
super(context);
|
||||
|
|
@ -204,23 +200,6 @@ public class CandidateView extends View {
|
|||
requestLayout();
|
||||
}
|
||||
|
||||
public void changeCase(int textCase, Locale locale) {
|
||||
ArrayList<String> newSuggestions = new ArrayList<>();
|
||||
|
||||
for (String s : mSuggestions) {
|
||||
if (textCase == InputMode.CASE_LOWER) {
|
||||
newSuggestions.add(s.toLowerCase(locale));
|
||||
} else if (textCase == InputMode.CASE_CAPITALIZE) {
|
||||
String cs = s.substring(0, 1).toUpperCase(locale) + s.substring(1).toLowerCase(locale);
|
||||
newSuggestions.add(cs);
|
||||
} else {
|
||||
newSuggestions.add(s.toUpperCase(locale));
|
||||
}
|
||||
}
|
||||
|
||||
setSuggestions(newSuggestions, mSelectedIndex);
|
||||
}
|
||||
|
||||
protected void clear() {
|
||||
mSuggestions = EMPTY_LIST;
|
||||
mSelectedIndex = -1;
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ public class UI {
|
|||
* Set the status icon that is appropriate in current mode (based on
|
||||
* openwmm-legacy)
|
||||
*/
|
||||
public static void updateStatusIcon(TraditionalT9 tt9, Language inputLanguage, InputMode inputMode, int textCase) {
|
||||
public static void updateStatusIcon(TraditionalT9 tt9, Language inputLanguage, InputMode inputMode) {
|
||||
if (inputMode.isABC()) {
|
||||
tt9.showStatusIcon(inputLanguage.getAbcIcon(textCase == InputMode.CASE_LOWER));
|
||||
tt9.showStatusIcon(inputLanguage.getAbcIcon(inputMode.getTextCase() == InputMode.CASE_LOWER));
|
||||
} else if (inputMode.isPredictive()) {
|
||||
tt9.showStatusIcon(inputLanguage.getIcon());
|
||||
} else if (inputMode.is123()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue