1
0
Fork 0

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:
Dimo Karaivanov 2022-11-02 09:58:33 +02:00 committed by GitHub
parent 10099f1c37
commit f152232bbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 223 additions and 155 deletions

View file

@ -230042,7 +230042,6 @@
домопритежателка домопритежателка
домостроителство домостроителство
домоуправителите домоуправителите
донякогадоникъде
дореволюционната дореволюционната
дореволюционните дореволюционните
дореволюционното дореволюционното

View file

@ -15658,6 +15658,7 @@ lester
lester's lester's
lestrade lestrade
lestrade's lestrade's
let's
leta leta
leta's leta's
letha letha
@ -93569,6 +93570,7 @@ isthmus's
isthmuses isthmuses
istle istle
it'd it'd
it's
it'll it'll
itacolumite itacolumite
italicization italicization

View file

@ -58,18 +58,12 @@ public class InputModeValidator {
return newMode; return newMode;
} }
public static int validateTextCase(T9Preferences prefs, int textCase, ArrayList<Integer> allowedTextCases) { public static void validateTextCase(T9Preferences prefs, InputMode inputMode, int newTextCase) {
if (allowedTextCases.size() > 0 && allowedTextCases.contains(textCase)) { if (!inputMode.setTextCase(newTextCase)) {
return textCase; 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(inputMode.getTextCase());
prefs.saveTextCase(newCase);
if (textCase != newCase) {
Logger.w("tt9/validateTextCase", "Invalid text case: " + textCase + " Enforcing: " + newCase);
}
return newCase;
} }
} }

View file

@ -25,10 +25,6 @@ public class TraditionalT9 extends KeyPadHandler {
private ArrayList<Integer> allowedInputModes = new ArrayList<>(); private ArrayList<Integer> allowedInputModes = new ArrayList<>();
private InputMode mInputMode; private InputMode mInputMode;
// text case
private ArrayList<Integer> allowedTextCases = new ArrayList<>();
private int mTextCase = InputMode.CASE_LOWER;
// language // language
protected ArrayList<Integer> mEnabledLanguages; protected ArrayList<Integer> mEnabledLanguages;
protected Language mLanguage; protected Language mLanguage;
@ -45,21 +41,20 @@ public class TraditionalT9 extends KeyPadHandler {
private void loadPreferences() { private void loadPreferences() {
mLanguage = LanguageCollection.getLanguage(prefs.getInputLanguage()); mLanguage = LanguageCollection.getLanguage(prefs.getInputLanguage());
mEnabledLanguages = prefs.getEnabledLanguages(); mEnabledLanguages = prefs.getEnabledLanguages();
validateLanguages();
mInputMode = InputMode.getInstance(prefs.getInputMode()); mInputMode = InputMode.getInstance(prefs.getInputMode());
mTextCase = prefs.getTextCase(); mInputMode = InputModeValidator.validateMode(prefs, mInputMode, allowedInputModes);
InputModeValidator.validateTextCase(prefs, mInputMode, prefs.getTextCase());
} }
private void validateLanguages() { private void validateLanguages() {
mEnabledLanguages = InputModeValidator.validateEnabledLanguages(prefs, mEnabledLanguages); mEnabledLanguages = InputModeValidator.validateEnabledLanguages(prefs, mEnabledLanguages);
mLanguage = InputModeValidator.validateLanguage(prefs, mLanguage, 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() { protected void onInit() {
self = this; self = this;
@ -74,19 +69,14 @@ public class TraditionalT9 extends KeyPadHandler {
protected void onRestart(EditorInfo inputField) { protected void onRestart(EditorInfo inputField) {
determineNextTextCase();
// determine the valid state for the current input field and preferences // 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 mEnabledLanguages = prefs.getEnabledLanguages(); // in case we are back from Preferences screen, update the language list
determineAllowedInputModes(inputField);
// enforce a valid initial state determineNextTextCase(); // Only in some modes. If they support it, let's overwrite the default.
validatePreferences();
clearSuggestions();
// build the UI // build the UI
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase); clearSuggestions();
UI.updateStatusIcon(this, mLanguage, mInputMode);
softKeyHandler.show(); softKeyHandler.show();
if (!isInputViewShown()) { if (!isInputViewShown()) {
showWindow(true); showWindow(true);
@ -163,7 +153,7 @@ public class TraditionalT9 extends KeyPadHandler {
protected boolean onLeft() { protected boolean onLeft() {
if (mInputMode.clearWordStem()) { if (mInputMode.clearWordStem()) {
mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, getComposingText()); mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, getComposingText());
} else { } else {
jumpBeforeComposingText(); jumpBeforeComposingText();
} }
@ -175,7 +165,7 @@ public class TraditionalT9 extends KeyPadHandler {
String filter = repeat ? mSuggestionView.getSuggestion(1) : getComposingText(); String filter = repeat ? mSuggestionView.getSuggestion(1) : getComposingText();
if (mInputMode.setWordStem(mLanguage, filter, repeat)) { if (mInputMode.setWordStem(mLanguage, filter, repeat)) {
mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, filter); mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, filter);
} else if (filter.length() == 0) { } else if (filter.length() == 0) {
mInputMode.reset(); mInputMode.reset();
} }
@ -333,14 +323,14 @@ public class TraditionalT9 extends KeyPadHandler {
private void getSuggestions() { private void getSuggestions() {
if (!mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, mSuggestionView.getCurrentSuggestion())) { if (!mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, mSuggestionView.getCurrentSuggestion())) {
handleSuggestions(mInputMode.getSuggestions()); handleSuggestions();
} }
} }
private void handleSuggestions(ArrayList<String> suggestions) { private void handleSuggestions() {
setSuggestions(suggestions); setSuggestions(mInputMode.getSuggestions(mLanguage));
// Put the first suggestion in the text field, // Put the first suggestion in the text field,
// but cut it off to the length of the sequence (how many keys were pressed), // 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()) { private final Handler handleSuggestionsAsync = new Handler(Looper.getMainLooper()) {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message m) {
handleSuggestions(msg.getData().getStringArrayList("suggestions")); handleSuggestions();
} }
}; };
private void setSuggestions(List<String> suggestions) { private void setSuggestions(List<String> suggestions) {
setSuggestions(suggestions, 0);
}
private void setSuggestions(List<String> suggestions, int selectedIndex) {
if (mSuggestionView == null) { if (mSuggestionView == null) {
return; return;
} }
boolean show = suggestions != null && suggestions.size() > 0; boolean show = suggestions != null && suggestions.size() > 0;
mSuggestionView.setSuggestions(suggestions, 0); mSuggestionView.setSuggestions(suggestions, selectedIndex);
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
setCandidatesViewShown(show); setCandidatesViewShown(show);
} }
@ -410,29 +403,27 @@ public class TraditionalT9 extends KeyPadHandler {
} }
// when typing a word or viewing scrolling the suggestions, only change the case // when typing a word or viewing scrolling the suggestions, only change the case
else if (!isSuggestionViewHidden()) { else if (!isSuggestionViewHidden()) {
determineAllowedTextCases(); mInputMode.nextTextCase();
ArrayList<String> switchedSuggestions = mInputMode.getSuggestions(mLanguage);
int modeIndex = (allowedTextCases.indexOf(mTextCase) + 1) % allowedTextCases.size(); setSuggestions(switchedSuggestions, mSuggestionView.getCurrentIndex());
mTextCase = allowedTextCases.get(modeIndex);
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
setComposingText(getComposingText()); // no mistake, this forces the new text case setComposingText(getComposingText()); // no mistake, this forces the new text case
} }
// make "abc" and "ABC" separate modes from user perspective // make "abc" and "ABC" separate modes from user perspective
else if (mInputMode.isABC() && mTextCase == InputMode.CASE_LOWER) { else if (mInputMode.isABC() && mInputMode.getTextCase() == InputMode.CASE_LOWER) {
mTextCase = InputMode.CASE_UPPER; mInputMode.setTextCase(InputMode.CASE_UPPER);
} else { } else {
int modeIndex = (allowedInputModes.indexOf(mInputMode.getId()) + 1) % allowedInputModes.size(); int modeIndex = (allowedInputModes.indexOf(mInputMode.getId()) + 1) % allowedInputModes.size();
mInputMode = InputMode.getInstance(allowedInputModes.get(modeIndex)); mInputMode = InputMode.getInstance(allowedInputModes.get(modeIndex));
mTextCase = mInputMode.isPredictive() ? InputMode.CASE_CAPITALIZE : InputMode.CASE_LOWER; mInputMode.defaultTextCase();
} }
// save the settings for the next time // save the settings for the next time
prefs.saveInputMode(mInputMode); 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 // save it for the next time
prefs.saveInputLanguage(mLanguage.getId()); 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() { private void determineNextTextCase() {
int nextTextCase = mInputMode.getNextWordTextCase( mInputMode.determineNextWordTextCase(
mTextCase,
InputFieldHelper.isThereText(currentInputConnection), InputFieldHelper.isThereText(currentInputConnection),
(String) currentInputConnection.getTextBeforeCursor(50, 0) (String) currentInputConnection.getTextBeforeCursor(50, 0)
); );
mTextCase = nextTextCase != -1 ? nextTextCase : mTextCase;
} }

View file

@ -1,8 +1,6 @@
package io.github.sspanak.tt9.ime.modes; package io.github.sspanak.tt9.ime.modes;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message;
import java.util.ArrayList; import java.util.ArrayList;
@ -19,7 +17,9 @@ abstract public class InputMode {
public static final int CASE_UPPER = 0; public static final int CASE_UPPER = 0;
public static final int CASE_CAPITALIZE = 1; public static final int CASE_CAPITALIZE = 1;
public static final int CASE_LOWER = 2; 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 ArrayList<Integer> allowedTextCases = new ArrayList<>();
protected int textCase = CASE_LOWER;
// data // data
protected ArrayList<String> suggestions = new ArrayList<>(); protected ArrayList<String> suggestions = new ArrayList<>();
@ -45,14 +45,16 @@ abstract public class InputMode {
// Suggestions // Suggestions
public void onAcceptSuggestion(Language language, String suggestion) {} public void onAcceptSuggestion(Language language, String suggestion) {}
public ArrayList<String> getSuggestions() { return suggestions; } protected void onSuggestionsUpdated(Handler handler) { handler.sendEmptyMessage(0); }
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) { return false; } public boolean loadSuggestions(Handler handler, Language language, String lastWord) { return false; }
protected void sendSuggestions(Handler handler, ArrayList<String> suggestions) {
Bundle bundle = new Bundle(); public ArrayList<String> getSuggestions(Language language) {
bundle.putStringArrayList("suggestions", suggestions); ArrayList<String> newSuggestions = new ArrayList<>();
Message msg = new Message(); for (String s : suggestions) {
msg.setData(bundle); newSuggestions.add(adjustSuggestionTextCase(s, textCase, language));
handler.sendMessage(msg); }
return newSuggestions;
} }
// Word // Word
@ -65,15 +67,38 @@ abstract public class InputMode {
// Utility // Utility
abstract public int getId(); 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. abstract public int getSequenceLength(); // The number of key presses for the current word.
public void reset() { public void reset() {
suggestions = new ArrayList<>(); suggestions = new ArrayList<>();
word = null; 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. // Stem filtering.
// Where applicable, return "true" if the mode supports it and the operation was possible. // Where applicable, return "true" if the mode supports it and the operation was possible.
public boolean clearWordStem() { return false; } public boolean clearWordStem() { return false; }

View file

@ -1,5 +1,7 @@
package io.github.sspanak.tt9.ime.modes; package io.github.sspanak.tt9.ime.modes;
import java.util.ArrayList;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
public class ModeABC extends InputMode { public class ModeABC extends InputMode {
@ -19,10 +21,10 @@ public class ModeABC extends InputMode {
word = null; word = null;
if (hold) { if (hold) {
suggestions = null; suggestions = new ArrayList<>();
word = String.valueOf(key); word = String.valueOf(key);
} else if (repeat) { } else if (repeat) {
suggestions = null; suggestions = new ArrayList<>();
shouldSelectNextLetter = true; 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; } final public boolean isABC() { return true; }
public int getSequenceLength() { return 1; } public int getSequenceLength() { return 1; }

View file

@ -5,7 +5,6 @@ import android.os.Looper;
import android.os.Message; import android.os.Message;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.Logger;
@ -29,11 +28,14 @@ public class ModePredictive extends InputMode {
private String lastInputFieldWord = ""; private String lastInputFieldWord = "";
private static Handler handleSuggestionsExternal; private static Handler handleSuggestionsExternal;
// auto text case selection
private final Pattern endOfSentence = Pattern.compile("(?<!\\.)[.?!]\\s*$");
ModePredictive() { ModePredictive() {
allowedTextCases.add(CASE_UPPER);
allowedTextCases.add(CASE_CAPITALIZE);
allowedTextCases.add(CASE_LOWER); 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 * shouldAcceptCurrentSuggestion
* In this mode, In addition to confirming the suggestion in the input field, * 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 * Queries the dictionary database for a list of suggestions matching the current language and
* sequence. Returns "false" when there is nothing to do. * sequence. Returns "false" when there is nothing to do.
* *
* "lastWord" is used for generating suggestions when there are no results. * "lastWord" is used for generating suggestions when there are no results.
* See: generatePossibleCompletions() * See: generatePossibleCompletions()
*/ */
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) { public boolean loadSuggestions(Handler handler, Language language, String lastWord) {
if (isEmoji) { if (isEmoji) {
super.sendSuggestions(handler, suggestions); super.onSuggestionsUpdated(handler);
return true; return true;
} }
if (digitSequence.length() == 0) { if (digitSequence.length() == 0) {
suggestions.clear();
return false; return false;
} }
@ -209,17 +202,19 @@ public class ModePredictive extends InputMode {
private final Handler handleSuggestions = new Handler(Looper.getMainLooper()) { private final Handler handleSuggestions = new Handler(Looper.getMainLooper()) {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
ArrayList<String> suggestions = msg.getData().getStringArrayList("suggestions"); ArrayList<String> dbSuggestions = msg.getData().getStringArrayList("suggestions");
suggestions = suggestions == null ? new ArrayList<>() : suggestions; dbSuggestions = dbSuggestions == null ? new ArrayList<>() : dbSuggestions;
if (suggestions.size() == 0 && digitSequence.length() > 0) { if (dbSuggestions.size() == 0 && digitSequence.length() > 0) {
suggestions = generatePossibleCompletions(currentLanguage, lastInputFieldWord); dbSuggestions = generatePossibleCompletions(currentLanguage, lastInputFieldWord);
} }
ArrayList<String> stemVariations = generatePossibleStemVariations(currentLanguage, suggestions); suggestions.clear();
stemVariations.addAll(suggestions); 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", * 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 * 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 * generate: "extrb" and "extrc". This is useful for typing an unknown word, that is similar to
* in the dictionary. * 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<>(); ArrayList<String> variations = new ArrayList<>();
if (stem.length() == 0) { if (stem.length() == 0) {
return variations; return variations;
} }
if (stem.length() == digitSequence.length() && !currentSuggestions.contains(stem)) {
variations.add(stem);
}
if (isStemFuzzy && stem.length() == digitSequence.length() - 1) { if (isStemFuzzy && stem.length() == digitSequence.length() - 1) {
for (String word : generatePossibleCompletions(language, stem)) { ArrayList<String> allPossibleVariations = generatePossibleCompletions(language, stem);
if (!currentSuggestions.contains(word)) {
// 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); variations.add(word);
} }
} }
@ -293,6 +294,30 @@ public class ModePredictive extends InputMode {
return variations; 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 * onAcceptSuggestion
@ -316,28 +341,61 @@ public class ModePredictive extends InputMode {
/** /**
* getNextWordTextCase * adjustSuggestionTextCase
* Dynamically determine text case of words as the user types to reduce key presses. * 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 * For example, this function will return CASE_LOWER by default, but CASE_UPPER at the beginning
* of a sentence. * 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 the user wants to type in uppercase, this must be for a reason, so we better not override it.
if (currentTextCase == CASE_UPPER) { if (textCase == CASE_UPPER) {
return -1; return;
} }
// start of text // start of text
if (!isThereText) { if (!isThereText) {
return CASE_CAPITALIZE; textCase = CASE_CAPITALIZE;
return;
} }
// start of sentence, excluding after "..." // start of sentence, excluding after "..."
Matcher endOfSentenceMatch = Pattern.compile("(?<!\\.)[.?!]\\s*$").matcher(textBeforeCursor); if (endOfSentence.matcher(textBeforeCursor).find()) {
if (endOfSentenceMatch.find()) { textCase = CASE_CAPITALIZE;
return 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; }
} }

View file

@ -8,13 +8,15 @@ public class Language {
protected int id; protected int id;
protected String name; protected String name;
protected Locale locale; protected Locale locale;
protected boolean isPunctuationPartOfWords; // see the getter for more info
protected int icon; protected int icon;
protected String dictionaryFile; protected String dictionaryFile;
protected int abcLowerCaseIcon; protected int abcLowerCaseIcon;
protected int abcUpperCaseIcon; protected int abcUpperCaseIcon;
protected ArrayList<ArrayList<String>> characterMap = new ArrayList<>(); protected ArrayList<ArrayList<String>> characterMap = new ArrayList<>();
// settings
protected boolean isPunctuationPartOfWords; // see the getter for more info
final public int getId() { final public int getId() {
return id; return id;
} }
@ -31,6 +33,15 @@ public class Language {
return icon; return icon;
} }
final public String getDictionaryFile() {
return dictionaryFile;
}
final public int getAbcIcon(boolean lowerCase) {
return lowerCase ? abcLowerCaseIcon : abcUpperCaseIcon;
}
/** /**
* isPunctuationPartOfWords * isPunctuationPartOfWords
* This plays a role in Predictive mode only. * This plays a role in Predictive mode only.
@ -49,12 +60,15 @@ public class Language {
*/ */
final public boolean isPunctuationPartOfWords() { return isPunctuationPartOfWords; } 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) { public boolean isMixedCaseWord(String word) {
return lowerCase ? abcLowerCaseIcon : abcUpperCaseIcon; return word != null && !word.toLowerCase(locale).equals(word) && !word.toUpperCase(locale).equals(word);
} }
public ArrayList<String> getKeyCharacters(int key) { public ArrayList<String> getKeyCharacters(int key) {

View file

@ -14,11 +14,12 @@ public class Bulgarian extends Language {
name = "български"; name = "български";
locale = new Locale("bg","BG"); locale = new Locale("bg","BG");
dictionaryFile = "bg-utf8.txt"; dictionaryFile = "bg-utf8.txt";
isPunctuationPartOfWords = false;
icon = R.drawable.ime_lang_bg; icon = R.drawable.ime_lang_bg;
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower; abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper; abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
isPunctuationPartOfWords = false;
characterMap = new ArrayList<>(Arrays.asList( characterMap = new ArrayList<>(Arrays.asList(
Punctuation.Secondary, // 0 Punctuation.Secondary, // 0
Punctuation.Main, // 1 Punctuation.Main, // 1

View file

@ -12,10 +12,11 @@ public class Dutch extends English {
id = 8; id = 8;
name = "Nederlands"; name = "Nederlands";
locale = new Locale("nl","NL"); locale = new Locale("nl","NL");
isPunctuationPartOfWords = true;
dictionaryFile = "nl-utf8.txt"; dictionaryFile = "nl-utf8.txt";
icon = R.drawable.ime_lang_nl; icon = R.drawable.ime_lang_nl;
isPunctuationPartOfWords = true;
characterMap.get(2).addAll(Arrays.asList("à", "ä", "ç")); characterMap.get(2).addAll(Arrays.asList("à", "ä", "ç"));
characterMap.get(3).addAll(Arrays.asList("é", "è", "ê", "ë")); characterMap.get(3).addAll(Arrays.asList("é", "è", "ê", "ë"));
characterMap.get(4).addAll(Arrays.asList("î", "ï")); characterMap.get(4).addAll(Arrays.asList("î", "ï"));

View file

@ -14,11 +14,12 @@ public class English extends Language {
name = "English"; name = "English";
locale = Locale.ENGLISH; locale = Locale.ENGLISH;
dictionaryFile = "en-utf8.txt"; dictionaryFile = "en-utf8.txt";
isPunctuationPartOfWords = true;
icon = R.drawable.ime_lang_en; icon = R.drawable.ime_lang_en;
abcLowerCaseIcon = R.drawable.ime_lang_latin_lower; abcLowerCaseIcon = R.drawable.ime_lang_latin_lower;
abcUpperCaseIcon = R.drawable.ime_lang_latin_upper; abcUpperCaseIcon = R.drawable.ime_lang_latin_upper;
isPunctuationPartOfWords = true;
characterMap = new ArrayList<>(Arrays.asList( characterMap = new ArrayList<>(Arrays.asList(
Punctuation.Secondary, // 0 Punctuation.Secondary, // 0
Punctuation.Main, // 1 Punctuation.Main, // 1

View file

@ -14,6 +14,7 @@ public class French extends English {
locale = Locale.FRENCH; locale = Locale.FRENCH;
dictionaryFile = "fr-utf8.txt"; dictionaryFile = "fr-utf8.txt";
icon = R.drawable.ime_lang_fr; icon = R.drawable.ime_lang_fr;
isPunctuationPartOfWords = false; isPunctuationPartOfWords = false;
characterMap.get(2).addAll(Arrays.asList("à", "â", "æ", "ç")); characterMap.get(2).addAll(Arrays.asList("à", "â", "æ", "ç"));

View file

@ -13,6 +13,7 @@ public class German extends English {
locale = Locale.GERMAN; locale = Locale.GERMAN;
dictionaryFile = "de-utf8.txt"; dictionaryFile = "de-utf8.txt";
icon = R.drawable.ime_lang_de; icon = R.drawable.ime_lang_de;
isPunctuationPartOfWords = false; isPunctuationPartOfWords = false;
characterMap.get(2).add("ä"); characterMap.get(2).add("ä");

View file

@ -14,6 +14,7 @@ public class Italian extends English {
locale = Locale.ITALIAN; locale = Locale.ITALIAN;
dictionaryFile = "it-utf8.txt"; dictionaryFile = "it-utf8.txt";
icon = R.drawable.ime_lang_it; icon = R.drawable.ime_lang_it;
isPunctuationPartOfWords = false; isPunctuationPartOfWords = false;
characterMap.get(2).add("à"); characterMap.get(2).add("à");

View file

@ -14,11 +14,12 @@ public class Russian extends Language {
name = "русский"; name = "русский";
locale = new Locale("ru","RU"); locale = new Locale("ru","RU");
dictionaryFile = "ru-utf8.txt"; dictionaryFile = "ru-utf8.txt";
isPunctuationPartOfWords = false;
icon = R.drawable.ime_lang_ru; icon = R.drawable.ime_lang_ru;
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower; abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper; abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
isPunctuationPartOfWords = false;
characterMap = new ArrayList<>(Arrays.asList( characterMap = new ArrayList<>(Arrays.asList(
Punctuation.Secondary, // 0 Punctuation.Secondary, // 0
Punctuation.Main, // 1 Punctuation.Main, // 1

View file

@ -14,11 +14,12 @@ public class Ukrainian extends Language {
name = "українська"; name = "українська";
locale = new Locale("uk","UA"); locale = new Locale("uk","UA");
dictionaryFile = "uk-utf8.txt"; dictionaryFile = "uk-utf8.txt";
isPunctuationPartOfWords = true;
icon = R.drawable.ime_lang_uk; icon = R.drawable.ime_lang_uk;
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower; abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper; abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
isPunctuationPartOfWords = true;
characterMap = new ArrayList<>(Arrays.asList( characterMap = new ArrayList<>(Arrays.asList(
Punctuation.Secondary, // 0 Punctuation.Secondary, // 0
Punctuation.Main, // 1 Punctuation.Main, // 1

View file

@ -10,14 +10,12 @@ import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ime.modes.InputMode;
public class CandidateView extends View { public class CandidateView extends View {
private List<String> mSuggestions; private List<String> mSuggestions = new ArrayList<>();
protected int mSelectedIndex; protected int mSelectedIndex;
private Drawable mSelectionHighlight; private Drawable mSelectionHighlight;
@ -32,7 +30,7 @@ public class CandidateView extends View {
private static final int X_GAP = 10; 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 mColorNormal;
private int mColorRecommended; private int mColorRecommended;
@ -47,8 +45,6 @@ public class CandidateView extends View {
/** /**
* Construct a CandidateView for showing suggested words for completion. * Construct a CandidateView for showing suggested words for completion.
*
* @param context
*/ */
public CandidateView(Context context) { public CandidateView(Context context) {
super(context); super(context);
@ -204,23 +200,6 @@ public class CandidateView extends View {
requestLayout(); 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() { protected void clear() {
mSuggestions = EMPTY_LIST; mSuggestions = EMPTY_LIST;
mSelectedIndex = -1; mSelectedIndex = -1;

View file

@ -35,9 +35,9 @@ public class UI {
* Set the status icon that is appropriate in current mode (based on * Set the status icon that is appropriate in current mode (based on
* openwmm-legacy) * 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()) { if (inputMode.isABC()) {
tt9.showStatusIcon(inputLanguage.getAbcIcon(textCase == InputMode.CASE_LOWER)); tt9.showStatusIcon(inputLanguage.getAbcIcon(inputMode.getTextCase() == InputMode.CASE_LOWER));
} else if (inputMode.isPredictive()) { } else if (inputMode.isPredictive()) {
tt9.showStatusIcon(inputLanguage.getIcon()); tt9.showStatusIcon(inputLanguage.getIcon());
} else if (inputMode.is123()) { } else if (inputMode.is123()) {