Skip to content

Commit

Permalink
added support for languages with one text case
Browse files Browse the repository at this point in the history
  • Loading branch information
sspanak committed Mar 13, 2023
1 parent e82ae8e commit ca15ff2
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 91 deletions.
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ To support a new language one needs to:
- `name` is the native name of the language (e.g. "English", "Deutsch", "Українська").
- `locale` contains the language and the country codes (e.g. "en-US", "es-AR", "it-IT"). Refer to the list of [supported locales in Java](https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html#util-text).
- `dictionaryFile` is the name of the dictionary in `assets/` folder.
- `icon`, `abcLowerCaseIcon` and `abcUpperCaseIcon` are the respective status icons for Predictive mode, ABC (lowercase) and ABC (uppercase).
- `icon` is the status icon for Predictive mode.
- `abcLowerCaseIcon` and `abcUpperCaseIcon` are the respective status icons for ABC (non-predictive) modes. Note that, you must not set `abcUpperCaseIcon`, if your language has no uppercase and lowercase letters (like Arabic, Asian scripts and Hebrew).
- Set `isPunctuationPartOfWords` to `true`, if the dictionary contains words with apostrophes or dashes, such as: `it's`, `you'll`, `a'tje` or `п'ят`. This will allow using 1-key for typing them (they will appear as suggestions). `false` will enable faster typing when apostrophes or other punctuation are not part of the words (no such words will be suggested).
- `characterMap` contains the letters and punctuation marks associated with each key.
- Finally, add the new language to the list in `LanguageCollection.java`. You only need to add it in one place, in the constructor. Please, be nice and maintain the alphabetical order.
Expand Down
52 changes: 27 additions & 25 deletions src/io/github/sspanak/tt9/ime/TraditionalT9.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static Context getMainContext() {
private void loadSettings() {
mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage());
mEnabledLanguages = settings.getEnabledLanguageIds();
mInputMode = InputMode.getInstance(settings, settings.getInputMode());
mInputMode = InputMode.getInstance(settings, mLanguage, settings.getInputMode());
mInputMode.setTextCase(settings.getTextCase());
}

Expand Down Expand Up @@ -96,9 +96,10 @@ private void initTyping() {

// some input fields support only numbers or are not suited for predictions (e.g. password fields)
determineAllowedInputModes();
mInputMode = InputModeValidator.validateMode(settings, mInputMode, allowedInputModes);

int modeId = InputModeValidator.validateMode(settings, mInputMode, allowedInputModes);
mInputMode = InputMode.getInstance(settings, mLanguage, modeId);
mInputMode.setTextFieldCase(textField.determineTextCase(inputType));

// Some modes may want to change the default text case based on grammar rules.
determineNextTextCase();
InputModeValidator.validateTextCase(settings, mInputMode, settings.getTextCase());
Expand Down Expand Up @@ -197,7 +198,7 @@ public boolean onOK() {

String word = mSuggestionView.getCurrentSuggestion();

mInputMode.onAcceptSuggestion(mLanguage, word);
mInputMode.onAcceptSuggestion(word);
commitCurrentSuggestion();
autoCorrectSpace(word, true, -1, false, false);
resetKeyRepeat();
Expand All @@ -208,7 +209,7 @@ public boolean onOK() {

protected boolean onUp() {
if (previousSuggestion()) {
mInputMode.setWordStem(mLanguage, mSuggestionView.getCurrentSuggestion(), true);
mInputMode.setWordStem(mSuggestionView.getCurrentSuggestion(), true);
textField.setComposingTextWithHighlightedStem(mSuggestionView.getCurrentSuggestion(), mInputMode);
return true;
}
Expand All @@ -219,7 +220,7 @@ protected boolean onUp() {

protected boolean onDown() {
if (nextSuggestion()) {
mInputMode.setWordStem(mLanguage, mSuggestionView.getCurrentSuggestion(), true);
mInputMode.setWordStem(mSuggestionView.getCurrentSuggestion(), true);
textField.setComposingTextWithHighlightedStem(mSuggestionView.getCurrentSuggestion(), mInputMode);
return true;
}
Expand All @@ -230,7 +231,7 @@ protected boolean onDown() {

protected boolean onLeft() {
if (mInputMode.clearWordStem()) {
mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, getComposingText());
mInputMode.loadSuggestions(handleSuggestionsAsync, getComposingText());
} else {
jumpBeforeComposingText();
}
Expand All @@ -247,8 +248,8 @@ protected boolean onRight(boolean repeat) {
filter = getComposingText();
}

if (mInputMode.setWordStem(mLanguage, filter, repeat)) {
mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, filter);
if (mInputMode.setWordStem(filter, repeat)) {
mInputMode.loadSuggestions(handleSuggestionsAsync, filter);
} else if (filter.length() == 0) {
mInputMode.reset();
}
Expand All @@ -270,8 +271,8 @@ protected boolean onNumber(int key, boolean hold, int repeat) {

// Automatically accept the current word, when the next one is a space or whatnot,
// instead of requiring "OK" before that.
if (mInputMode.shouldAcceptCurrentSuggestion(mLanguage, key, hold, repeat > 0)) {
mInputMode.onAcceptSuggestion(mLanguage, currentWord);
if (mInputMode.shouldAcceptCurrentSuggestion(key, hold, repeat > 0)) {
mInputMode.onAcceptSuggestion(currentWord);
commitCurrentSuggestion(false);
autoCorrectSpace(currentWord, false, key, hold, repeat > 0);
currentWord = "";
Expand All @@ -283,7 +284,7 @@ protected boolean onNumber(int key, boolean hold, int repeat) {
determineNextTextCase();
}

if (!mInputMode.onNumber(mLanguage, key, hold, repeat)) {
if (!mInputMode.onNumber(key, hold, repeat)) {
return false;
}

Expand All @@ -295,7 +296,7 @@ protected boolean onNumber(int key, boolean hold, int repeat) {
if (mInputMode.getWord() != null) {
currentWord = mInputMode.getWord();

mInputMode.onAcceptSuggestion(mLanguage, currentWord);
mInputMode.onAcceptSuggestion(currentWord);
textField.setText(currentWord);
clearSuggestions();
autoCorrectSpace(currentWord, true, key, hold, repeat > 0);
Expand Down Expand Up @@ -333,6 +334,7 @@ protected boolean onKeyAddWord() {
protected boolean onKeyNextLanguage() {
if (nextLang()) {
commitCurrentSuggestion(false);
mInputMode.changeLanguage(mLanguage);
mInputMode.reset();
resetKeyRepeat();
clearSuggestions();
Expand Down Expand Up @@ -430,14 +432,14 @@ private void clearSuggestions() {


private void getSuggestions() {
if (!mInputMode.loadSuggestions(handleSuggestionsAsync, mLanguage, mSuggestionView.getCurrentSuggestion())) {
if (!mInputMode.loadSuggestions(handleSuggestionsAsync, mSuggestionView.getCurrentSuggestion())) {
handleSuggestions();
}
}


private void handleSuggestions() {
setSuggestions(mInputMode.getSuggestions(mLanguage));
setSuggestions(mInputMode.getSuggestions());

// Put the first suggestion in the text field,
// but cut it off to the length of the sequence (how many keys were pressed),
Expand Down Expand Up @@ -489,7 +491,7 @@ private void refreshComposingText() {

private void nextInputMode() {
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
mInputMode = !mInputMode.is123() ? InputMode.getInstance(settings, InputMode.MODE_123) : mInputMode;
mInputMode = !mInputMode.is123() ? InputMode.getInstance(settings, mLanguage, InputMode.MODE_123) : mInputMode;
}
// when typing a word or viewing scrolling the suggestions, only change the case
else if (!isSuggestionViewHidden()) {
Expand All @@ -498,9 +500,9 @@ else if (!isSuggestionViewHidden()) {
// When we are in AUTO mode and the dictionary word is in uppercase,
// the mode would switch to UPPERCASE, but visually, the word would not change.
// This is why we retry, until there is a visual change.
for (int retries = 0; retries < 2; retries++) {
for (int retries = 0; retries < 2 && mLanguage.hasUpperCase(); retries++) {
mInputMode.nextTextCase();
setSuggestions(mInputMode.getSuggestions(mLanguage), mSuggestionView.getCurrentIndex());
setSuggestions(mInputMode.getSuggestions(), mSuggestionView.getCurrentIndex());
refreshComposingText();

if (!currentSuggestionBefore.equals(getComposingText())) {
Expand All @@ -509,17 +511,17 @@ else if (!isSuggestionViewHidden()) {
}
}
// make "abc" and "ABC" separate modes from user perspective
else if (mInputMode.isABC() && mInputMode.getTextCase() == InputMode.CASE_LOWER) {
else if (mInputMode.isABC() && mInputMode.getTextCase() == InputMode.CASE_LOWER && mLanguage.hasUpperCase()) {
mInputMode.nextTextCase();
} else {
int modeIndex = (allowedInputModes.indexOf(mInputMode.getId()) + 1) % allowedInputModes.size();
mInputMode = InputMode.getInstance(settings, allowedInputModes.get(modeIndex));
mInputMode = InputMode.getInstance(settings, mLanguage, allowedInputModes.get(modeIndex));

mInputMode.defaultTextCase();
}

// save the settings for the next time
settings.saveInputMode(mInputMode);
settings.saveInputMode(mInputMode.getId());
settings.saveTextCase(mInputMode.getTextCase());

UI.updateStatusIcon(this, mLanguage, mInputMode);
Expand Down Expand Up @@ -558,7 +560,7 @@ private void jumpBeforeComposingText() {

textField.setComposingText(word, 0);
textField.finishComposingText();
mInputMode.onAcceptSuggestion(mLanguage, word);
mInputMode.onAcceptSuggestion(word);
mInputMode.reset();
setSuggestions(null);
}
Expand All @@ -569,11 +571,11 @@ private void determineAllowedInputModes() {

int lastInputModeId = settings.getInputMode();
if (allowedInputModes.contains(lastInputModeId)) {
mInputMode = InputMode.getInstance(settings, lastInputModeId);
mInputMode = InputMode.getInstance(settings, mLanguage, lastInputModeId);
} else if (allowedInputModes.contains(InputMode.MODE_ABC)) {
mInputMode = InputMode.getInstance(settings, InputMode.MODE_ABC);
mInputMode = InputMode.getInstance(settings, mLanguage, InputMode.MODE_ABC);
} else {
mInputMode = InputMode.getInstance(settings, allowedInputModes.get(0));
mInputMode = InputMode.getInstance(settings, mLanguage, allowedInputModes.get(0));
}

if (inputType.isDialer()) {
Expand Down
18 changes: 7 additions & 11 deletions src/io/github/sspanak/tt9/ime/helpers/InputModeValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,19 @@ public static Language validateLanguage(SettingsStore settings, Language languag
return validLanguage;
}

public static InputMode validateMode(SettingsStore settings, InputMode inputMode, ArrayList<Integer> allowedModes) {
public static int validateMode(SettingsStore settings, InputMode inputMode, ArrayList<Integer> allowedModes) {
if (allowedModes.size() > 0 && allowedModes.contains(inputMode.getId())) {
inputMode.reset();
return inputMode;
return inputMode.getId();
}

InputMode newMode = InputMode.getInstance(
settings,
allowedModes.size() > 0 ? allowedModes.get(0) : InputMode.MODE_123
);
settings.saveInputMode(newMode);
int newModeId = allowedModes.size() > 0 ? allowedModes.get(0) : InputMode.MODE_123;
settings.saveInputMode(newModeId);

if (newMode.getId() != inputMode.getId()) {
Logger.w("tt9/validateMode", "Invalid input mode: " + inputMode.getId() + " Enforcing: " + newMode.getId());
if (newModeId != inputMode.getId()) {
Logger.w("tt9/validateMode", "Invalid input mode: " + inputMode.getId() + " Enforcing: " + newModeId);
}

return newMode;
return newModeId;
}

public static void validateTextCase(SettingsStore settings, InputMode inputMode, int newTextCase) {
Expand Down
28 changes: 17 additions & 11 deletions src/io/github/sspanak/tt9/ime/modes/InputMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ abstract public class InputMode {
protected int textFieldTextCase = CASE_UNDEFINED;

// data
protected Language language;
protected ArrayList<String> suggestions = new ArrayList<>();
protected String word = null;


public static InputMode getInstance(SettingsStore settings, int mode) {
public static InputMode getInstance(SettingsStore settings, Language language, int mode) {
switch (mode) {
case MODE_PREDICTIVE:
return new ModePredictive(settings);
return new ModePredictive(settings, language);
case MODE_ABC:
return new ModeABC();
return new ModeABC(language);
default:
Logger.w("tt9/InputMode", "Defaulting to mode: " + Mode123.class.getName() + " for unknown InputMode: " + mode);
case MODE_123:
Expand All @@ -46,17 +47,17 @@ public static InputMode getInstance(SettingsStore settings, int mode) {

// Key handlers. Return "true" when handling the key or "false", when is nothing to do.
public boolean onBackspace() { return false; }
abstract public boolean onNumber(Language language, int key, boolean hold, int repeat);
abstract public boolean onNumber(int key, boolean hold, int repeat);

// Suggestions
public void onAcceptSuggestion(Language language, String suggestion) {}
public void onAcceptSuggestion(String suggestion) {}
protected void onSuggestionsUpdated(Handler handler) { handler.sendEmptyMessage(0); }
public boolean loadSuggestions(Handler handler, Language language, String currentWord) { return false; }
public boolean loadSuggestions(Handler handler, String currentWord) { return false; }

public ArrayList<String> getSuggestions(Language language) {
public ArrayList<String> getSuggestions() {
ArrayList<String> newSuggestions = new ArrayList<>();
for (String s : suggestions) {
newSuggestions.add(adjustSuggestionTextCase(s, textCase, language));
newSuggestions.add(adjustSuggestionTextCase(s, textCase));
}

return newSuggestions;
Expand All @@ -73,9 +74,14 @@ public ArrayList<String> getSuggestions(Language language) {
// Utility
abstract public int getId();
abstract public int getSequenceLength(); // The number of key presses for the current word.
public void changeLanguage(Language newLanguage) {
if (newLanguage != null) {
language = newLanguage;
}
}

// Interaction with the IME. Return "true" if it should perform the respective action.
public boolean shouldAcceptCurrentSuggestion(Language language, int key, boolean hold, boolean repeat) { return false; }
public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return false; }
public boolean shouldAddAutoSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int incomingKey, boolean hold, boolean repeat) { return false; }
public boolean shouldDeletePrecedingSpace(InputType inputType) { return false; }
public boolean shouldSelectNextSuggestion() { return false; }
Expand Down Expand Up @@ -116,12 +122,12 @@ public void nextTextCase() {
public void determineNextWordTextCase(SettingsStore settings, 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; }
protected String adjustSuggestionTextCase(String word, int newTextCase) { return word; }

// Stem filtering.
// Where applicable, return "true" if the mode supports it and the operation was possible.
public boolean clearWordStem() { return false; }
public boolean isStemFilterFuzzy() { return false; }
public String getWordStem() { return ""; }
public boolean setWordStem(Language language, String stem, boolean exact) { return false; }
public boolean setWordStem(String stem, boolean exact) { return false; }
}
4 changes: 1 addition & 3 deletions src/io/github/sspanak/tt9/ime/modes/Mode123.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import java.util.ArrayList;

import io.github.sspanak.tt9.languages.Language;

public class Mode123 extends InputMode {
public int getId() { return MODE_123; }

Expand All @@ -12,7 +10,7 @@ public class Mode123 extends InputMode {
}


public boolean onNumber(Language l, int key, boolean hold, int repeat) {
public boolean onNumber(int key, boolean hold, int repeat) {
if (key != 0) {
return false;
}
Expand Down
33 changes: 22 additions & 11 deletions src/io/github/sspanak/tt9/ime/modes/ModeABC.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ public class ModeABC extends InputMode {

private boolean shouldSelectNextLetter = false;

ModeABC() {
allowedTextCases.add(CASE_LOWER);
allowedTextCases.add(CASE_UPPER);
ModeABC(Language lang) {
changeLanguage(lang);
}


public boolean onNumber(Language language, int key, boolean hold, int repeat) {
@Override
public boolean onNumber(int key, boolean hold, int repeat) {
shouldSelectNextLetter = false;
suggestions = language.getKeyCharacters(key);
word = null;
Expand All @@ -31,18 +31,29 @@ public boolean onNumber(Language language, int key, boolean hold, int repeat) {
}


protected String adjustSuggestionTextCase(String word, int newTextCase, Language language) {
@Override
protected String adjustSuggestionTextCase(String word, int newTextCase) {
return newTextCase == CASE_UPPER ? word.toUpperCase(language.getLocale()) : word.toLowerCase(language.getLocale());
}

@Override
public void changeLanguage(Language language) {
super.changeLanguage(language);

allowedTextCases.clear();
allowedTextCases.add(CASE_LOWER);
if (language.hasUpperCase()) {
allowedTextCases.add(CASE_UPPER);
}
}

final public boolean isABC() { return true; }
public int getSequenceLength() { return 1; }
@Override final public boolean isABC() { return true; }
@Override public int getSequenceLength() { return 1; }

public boolean shouldAcceptCurrentSuggestion(Language l, int key, boolean hold, boolean repeat) { return hold || !repeat; }
public boolean shouldTrackUpDown() { return true; }
public boolean shouldTrackLeftRight() { return true; }
public boolean shouldSelectNextSuggestion() {
@Override public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return hold || !repeat; }
@Override public boolean shouldTrackUpDown() { return true; }
@Override public boolean shouldTrackLeftRight() { return true; }
@Override public boolean shouldSelectNextSuggestion() {
return shouldSelectNextLetter;
}
}
Loading

0 comments on commit ca15ff2

Please sign in to comment.