* fixed not updating the priority of uppercase words (#76)
* OK button now always accepts the entire current suggestion (as it should) * LEFT button accepts the current word as-is * in Predictive mode, when there are no more dictionary matches after the last key pressed, suggest all words ending with the letters for that key, instead of only the first one * OK button now performs the default action when supported by the application (e.g. submit a message, go to a web page, etc...) * smarter automatic text case selection in Predictive mode * suggestion stem filter in Predictive mode * all emoji are graphical * updated the docs
This commit is contained in:
parent
575293edb9
commit
acb48b7999
11 changed files with 399 additions and 157 deletions
|
|
@ -3,6 +3,9 @@ TT9 is an IME (Input Method Editor) for Android devices with hardware keypad. It
|
||||||
|
|
||||||
This is an updated version of the [original project](https://github.com/Clam-/TraditionalT9) by Clam-.
|
This is an updated version of the [original project](https://github.com/Clam-/TraditionalT9) by Clam-.
|
||||||
|
|
||||||
|
## Using Traditional T9
|
||||||
|
If you just wish to install and use TT9, see the [user manual](docs/user-manual.md). You don't need to read anything below this line.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
The recommended way of building is using Android Studio. As the of time of writing this, the current version is: Android Studio Dolphin | 2021.3.1.
|
The recommended way of building is using Android Studio. As the of time of writing this, the current version is: Android Studio Dolphin | 2021.3.1.
|
||||||
|
|
||||||
|
|
@ -53,9 +56,6 @@ To support a new language one needs to:
|
||||||
- Add new entries in `res/values/arrays.xml`.
|
- Add new entries in `res/values/arrays.xml`.
|
||||||
- Add translations in `res/values/strings-your-lang`. The Android Studio translation editor is very handy.
|
- Add translations in `res/values/strings-your-lang`. The Android Studio translation editor is very handy.
|
||||||
|
|
||||||
## Using the app
|
|
||||||
See the [user manual](docs/user-manual.md).
|
|
||||||
|
|
||||||
## Word Lists
|
## Word Lists
|
||||||
Here is detailed information and licenses about the word lists used:
|
Here is detailed information and licenses about the word lists used:
|
||||||
- [Bulgarian word list](docs/bgWordlistReadme.txt)
|
- [Bulgarian word list](docs/bgWordlistReadme.txt)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ _The actual menu names may vary depending on your Android version and phone._
|
||||||
### Enabling Predictive Mode
|
### Enabling Predictive Mode
|
||||||
With the default settings, it is only possible to type in 123 and ABC modes. In order to enable the Predictive mode, there are additional steps:
|
With the default settings, it is only possible to type in 123 and ABC modes. In order to enable the Predictive mode, there are additional steps:
|
||||||
|
|
||||||
- Open the [Configuration screen](#configuration-options).
|
- Open the [Settings screen](#settings-screen).
|
||||||
- Select the desired languages.
|
- Select the desired languages.
|
||||||
- Load the dictionaries.
|
- Load the dictionaries.
|
||||||
|
|
||||||
|
|
@ -23,19 +23,30 @@ _If you don't do the above, there will be no suggestions when typing in Predicti
|
||||||
## Hotkeys
|
## Hotkeys
|
||||||
|
|
||||||
#### D-pad Up (↑):
|
#### D-pad Up (↑):
|
||||||
Select previous word suggestion.
|
Select previous word/letter suggestion.
|
||||||
|
|
||||||
#### D-pad Down (↓):
|
#### D-pad Down (↓):
|
||||||
Select next word suggestion.
|
Select next word/letter suggestion.
|
||||||
|
|
||||||
|
#### D-pad Right (→):
|
||||||
|
_Predictive mode only._
|
||||||
|
|
||||||
|
Filter the suggestion list leaving out only words similar to the current suggestion. For example, "6463" results in: "nine", "mine", "mind", and so on. But selecting "mind" and pressing Right will leave out only the similar ones: "mind", "minds", "mindy", "minded"...
|
||||||
|
|
||||||
|
#### D-pad Left (←):
|
||||||
|
_Predictive mode only._
|
||||||
|
|
||||||
|
- Clear the suggestion filter, if applied.
|
||||||
|
- When no filter is applied, accept the current word as-is, even if it does not fully match a suggestion, then jump before it.
|
||||||
|
|
||||||
#### 0 key
|
#### 0 key
|
||||||
- In 123 mode: type "0" or hold it to type "+".
|
- **In 123 mode:** type "0" or hold it to type "+".
|
||||||
- In ABC mode: type secondary punctuation or hold to type "0".
|
- **In ABC mode:** type secondary punctuation or hold to type "0".
|
||||||
- In Predictive mode: type space or hold to type "0".
|
- **In Predictive mode:** type space or hold to type "0".
|
||||||
|
|
||||||
#### 1 to 9 keys
|
#### 1 to 9 keys
|
||||||
- In 123 mode: type the respective number.
|
- **In 123 mode:** type the respective number.
|
||||||
- In ABC and Predictive mode: type a letter/punctuation character or hold to type the respective number.
|
- **In ABC and Predictive mode:** type a letter/punctuation character or hold to type the respective number.
|
||||||
|
|
||||||
#### Text Mode Key (Hash/Pound/#):
|
#### Text Mode Key (Hash/Pound/#):
|
||||||
- **Short press:** Cycle input modes (abc → ABC → Predictive → 123)
|
- **Short press:** Cycle input modes (abc → ABC → Predictive → 123)
|
||||||
|
|
@ -59,12 +70,12 @@ Select next word suggestion.
|
||||||
All functionality is available using the keypad, but for convenience, on touchscreen phones or the ones with customizable function keys, you could also use the on-screen soft keys.
|
All functionality is available using the keypad, but for convenience, on touchscreen phones or the ones with customizable function keys, you could also use the on-screen soft keys.
|
||||||
|
|
||||||
#### Left Soft Key:
|
#### Left Soft Key:
|
||||||
Open the [Configuration screen](#configuration-options).
|
Open the [Settings screen](#settings-screen).
|
||||||
|
|
||||||
#### Right Soft Key:
|
#### Right Soft Key:
|
||||||
Backspace.
|
Backspace.
|
||||||
|
|
||||||
## Configuration Options
|
## Settings Screen
|
||||||
On the Configuration screen, you can choose your preferred languages, load a dictionary for Predictive mode or view this manual.
|
On the Configuration screen, you can choose your preferred languages, load a dictionary for Predictive mode or view this manual.
|
||||||
|
|
||||||
To access it:
|
To access it:
|
||||||
|
|
|
||||||
|
|
@ -72,15 +72,6 @@ public class DictionaryDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void sendSuggestions(Handler handler, ArrayList<String> data) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putStringArrayList("suggestions", data);
|
|
||||||
Message msg = new Message();
|
|
||||||
msg.setData(bundle);
|
|
||||||
handler.sendMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void truncateWords(Handler handler) {
|
public static void truncateWords(Handler handler) {
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -134,6 +125,8 @@ public class DictionaryDb {
|
||||||
|
|
||||||
|
|
||||||
public static void incrementWordFrequency(Language language, String word, String sequence) throws Exception {
|
public static void incrementWordFrequency(Language language, String word, String sequence) throws Exception {
|
||||||
|
Logger.d("incrementWordFrequency", "Incrementing priority of Word: " + word +" | Sequence: " + sequence);
|
||||||
|
|
||||||
if (language == null) {
|
if (language == null) {
|
||||||
throw new InvalidLanguageException();
|
throw new InvalidLanguageException();
|
||||||
}
|
}
|
||||||
|
|
@ -146,18 +139,19 @@ public class DictionaryDb {
|
||||||
// If one of them is empty, then this is an invalid operation,
|
// If one of them is empty, then this is an invalid operation,
|
||||||
// because a digit sequence exist for every word.
|
// because a digit sequence exist for every word.
|
||||||
if (word == null || word.length() == 0 || sequence == null || sequence.length() == 0) {
|
if (word == null || word.length() == 0 || sequence == null || sequence.length() == 0) {
|
||||||
throw new Exception("Cannot increment word frequency. Word: '" + word + "', Sequence: '" + sequence + "'");
|
throw new Exception("Cannot increment word frequency. Word: " + word + " | Sequence: " + sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
getInstance().wordsDao().incrementFrequency(language.getId(), word, sequence);
|
int affectedRows = getInstance().wordsDao().incrementFrequency(language.getId(), word.toLowerCase(language.getLocale()), sequence);
|
||||||
|
Logger.d("incrementWordFrequency", "Affected rows: " + affectedRows);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.e(
|
Logger.e(
|
||||||
DictionaryDb.class.getName(),
|
DictionaryDb.class.getName(),
|
||||||
"Failed incrementing word frequency. Word: '" + word + "', Sequence: '" + sequence + "'. " + e.getMessage()
|
"Failed incrementing word frequency. Word: " + word + " | Sequence: " + sequence + ". " + e.getMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,46 +159,93 @@ public class DictionaryDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void getSuggestions(Handler handler, Language language, String sequence, int minimumWords, int maximumWords) {
|
private static ArrayList<String> getSuggestionsExact(Language language, String sequence, String word, int maximumWords) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
List<Word> exactMatches = getInstance().wordsDao().getMany(
|
||||||
|
language.getId(),
|
||||||
|
maximumWords,
|
||||||
|
sequence,
|
||||||
|
word == null || word.equals("") ? null : word
|
||||||
|
);
|
||||||
|
Logger.d(
|
||||||
|
"db.getSuggestionsExact",
|
||||||
|
"Exact matches: " + exactMatches.size() + ". Time: " + (System.currentTimeMillis() - start) + " ms"
|
||||||
|
);
|
||||||
|
|
||||||
|
ArrayList<String> suggestions = new ArrayList<>();
|
||||||
|
for (Word w : exactMatches) {
|
||||||
|
Logger.d("db.getSuggestions", "exact match: " + w.word + " | priority: " + w.frequency);
|
||||||
|
suggestions.add(w.word);
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static ArrayList<String> getSuggestionsFuzzy(Language language, String sequence, String word, int maximumWords) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
List<Word> extraWords = getInstance().wordsDao().getFuzzy(
|
||||||
|
language.getId(),
|
||||||
|
maximumWords,
|
||||||
|
sequence,
|
||||||
|
word == null || word.equals("") ? null : word
|
||||||
|
);
|
||||||
|
Logger.d(
|
||||||
|
"db.getSuggestionsFuzzy",
|
||||||
|
"Fuzzy matches: " + extraWords.size() + ". Time: " + (System.currentTimeMillis() - start) + " ms"
|
||||||
|
);
|
||||||
|
|
||||||
|
ArrayList<String> suggestions = new ArrayList<>();
|
||||||
|
for (Word w : extraWords) {
|
||||||
|
Logger.d(
|
||||||
|
"db.getSuggestions",
|
||||||
|
"fuzzy match: " + w.word + " | sequence: " + w.sequence + " | priority: " + w.frequency
|
||||||
|
);
|
||||||
|
suggestions.add(w.word);
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void sendSuggestions(Handler handler, ArrayList<String> data) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putStringArrayList("suggestions", data);
|
||||||
|
Message msg = new Message();
|
||||||
|
msg.setData(bundle);
|
||||||
|
handler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void getSuggestions(Handler handler, Language language, String sequence, String word, int minimumWords, int maximumWords) {
|
||||||
final int minWords = Math.max(minimumWords, 0);
|
final int minWords = Math.max(minimumWords, 0);
|
||||||
final int maxWords = Math.max(maximumWords, minimumWords);
|
final int maxWords = Math.max(maximumWords, minimumWords);
|
||||||
|
|
||||||
|
if (sequence == null || sequence.length() == 0) {
|
||||||
|
Logger.w("tt9/db.getSuggestions", "Attempting to get suggestions for an empty sequence.");
|
||||||
|
sendSuggestions(handler, new ArrayList<>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (language == null) {
|
||||||
|
Logger.w("tt9/db.getSuggestions", "Attempting to get suggestions for NULL language.");
|
||||||
|
sendSuggestions(handler, new ArrayList<>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (sequence == null || sequence.length() == 0) {
|
|
||||||
Logger.w("tt9/db.getSuggestions", "Attempting to get suggestions for an empty sequence.");
|
|
||||||
sendSuggestions(handler, new ArrayList<>());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (language == null) {
|
|
||||||
Logger.w("tt9/db.getSuggestions", "Attempting to get suggestions for NULL language.");
|
|
||||||
sendSuggestions(handler, new ArrayList<>());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get exact sequence matches, for example: "9422" -> "what"
|
// get exact sequence matches, for example: "9422" -> "what"
|
||||||
List<Word> exactMatches = getInstance().wordsDao().getMany(language.getId(), sequence, maxWords);
|
ArrayList<String> suggestions = getSuggestionsExact(language, sequence, word, maxWords);
|
||||||
Logger.d("db.getSuggestions", "Exact matches: " + exactMatches.size());
|
|
||||||
|
|
||||||
ArrayList<String> suggestions = new ArrayList<>();
|
|
||||||
for (Word word : exactMatches) {
|
|
||||||
Logger.d("db.getSuggestions", "exact match: " + word.word + ", priority: " + word.frequency);
|
|
||||||
suggestions.add(word.word);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the exact matches are too few, add some more words that start with the same characters,
|
// if the exact matches are too few, add some more words that start with the same characters,
|
||||||
// for example: "rol" => "roll", "roller", "rolling", ...
|
// for example: "rol" -> "roll", "roller", "rolling", ...
|
||||||
if (exactMatches.size() < minWords && sequence.length() >= 2) {
|
if (suggestions.size() < minWords && sequence.length() >= 2) {
|
||||||
int extraWordsNeeded = minWords - exactMatches.size();
|
suggestions.addAll(
|
||||||
List<Word> extraWords = getInstance().wordsDao().getFuzzy(language.getId(), sequence, extraWordsNeeded);
|
getSuggestionsFuzzy(language, sequence, word, minWords - suggestions.size())
|
||||||
Logger.d("db.getSuggestions", "Fuzzy matches: " + extraWords.size());
|
);
|
||||||
|
|
||||||
for (Word word : extraWords) {
|
|
||||||
Logger.d("db.getSuggestions", "fuzzy match: " + word.word + ", sequence: " + word.sequence);
|
|
||||||
suggestions.add(word.word);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suggestions.size() == 0) {
|
if (suggestions.size() == 0) {
|
||||||
|
|
@ -215,6 +256,5 @@ public class DictionaryDb {
|
||||||
sendSuggestions(handler, suggestions);
|
sendSuggestions(handler, suggestions);
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,26 @@ interface WordsDao {
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT * " +
|
"SELECT * " +
|
||||||
"FROM words " +
|
"FROM words " +
|
||||||
"WHERE lang = :langId AND seq = :sequence " +
|
"WHERE " +
|
||||||
|
"lang = :langId " +
|
||||||
|
"AND seq = :sequence " +
|
||||||
|
"AND (:word IS NULL OR word LIKE :word || '%') " +
|
||||||
"ORDER BY freq DESC " +
|
"ORDER BY freq DESC " +
|
||||||
"LIMIT :limit"
|
"LIMIT :limit"
|
||||||
)
|
)
|
||||||
List<Word> getMany(int langId, String sequence, int limit);
|
List<Word> getMany(int langId, int limit, String sequence, String word);
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT * " +
|
"SELECT * " +
|
||||||
"FROM words " +
|
"FROM words " +
|
||||||
"WHERE lang = :langId AND seq > :sequence AND seq <= :sequence || '99' " +
|
"WHERE " +
|
||||||
"ORDER BY freq DESC, seq ASC " +
|
"lang = :langId " +
|
||||||
|
"AND seq > :sequence AND seq <= :sequence || '99' " +
|
||||||
|
"AND (:word IS NULL OR word LIKE :word || '%') " +
|
||||||
|
"ORDER BY freq DESC, LENGTH(seq) ASC, seq ASC " +
|
||||||
"LIMIT :limit"
|
"LIMIT :limit"
|
||||||
)
|
)
|
||||||
List<Word> getFuzzy(int langId, String sequence, int limit);
|
List<Word> getFuzzy(int langId, int limit, String sequence, String word);
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
void insert(Word word);
|
void insert(Word word);
|
||||||
|
|
@ -36,7 +42,7 @@ interface WordsDao {
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE words " +
|
"UPDATE words " +
|
||||||
"SET freq = (SELECT IFNULL(MAX(freq), 0) FROM words WHERE lang = :langId AND seq = :sequence AND word <> :word) + 1 " +
|
"SET freq = (SELECT IFNULL(MAX(freq), 0) FROM words WHERE lang = :langId AND seq = :sequence AND word <> :word) + 1 " +
|
||||||
"WHERE lang = :langId AND word = :word "
|
"WHERE lang = :langId AND word = :word AND seq = :sequence"
|
||||||
)
|
)
|
||||||
void incrementFrequency(int langId, String word, String sequence);
|
int incrementFrequency(int langId, String word, String sequence);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,8 @@ abstract class KeyPadHandler extends InputMethodService {
|
||||||
|| keyCode == KeyEvent.KEYCODE_STAR
|
|| keyCode == KeyEvent.KEYCODE_STAR
|
||||||
|| keyCode == KeyEvent.KEYCODE_POUND
|
|| keyCode == KeyEvent.KEYCODE_POUND
|
||||||
|| (isNumber(keyCode) && shouldTrackNumPress())
|
|| (isNumber(keyCode) && shouldTrackNumPress())
|
||||||
|| ((keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && shouldTrackArrows())
|
|| ((keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && shouldTrackUpDown())
|
||||||
|
|| ((keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) && shouldTrackLeftRight())
|
||||||
|| (mEditing != EDITING_NOSHOW && keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
|
|| (mEditing != EDITING_NOSHOW && keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -296,6 +297,8 @@ abstract class KeyPadHandler extends InputMethodService {
|
||||||
case KeyEvent.KEYCODE_DPAD_CENTER: return onOK();
|
case KeyEvent.KEYCODE_DPAD_CENTER: return onOK();
|
||||||
case KeyEvent.KEYCODE_DPAD_UP: return onUp();
|
case KeyEvent.KEYCODE_DPAD_UP: return onUp();
|
||||||
case KeyEvent.KEYCODE_DPAD_DOWN: return onDown();
|
case KeyEvent.KEYCODE_DPAD_DOWN: return onDown();
|
||||||
|
case KeyEvent.KEYCODE_DPAD_LEFT: return onLeft();
|
||||||
|
case KeyEvent.KEYCODE_DPAD_RIGHT: return onRight();
|
||||||
case KeyEvent.KEYCODE_1:
|
case KeyEvent.KEYCODE_1:
|
||||||
case KeyEvent.KEYCODE_2:
|
case KeyEvent.KEYCODE_2:
|
||||||
case KeyEvent.KEYCODE_3:
|
case KeyEvent.KEYCODE_3:
|
||||||
|
|
@ -370,7 +373,8 @@ abstract class KeyPadHandler extends InputMethodService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggle handlers
|
// toggle handlers
|
||||||
abstract protected boolean shouldTrackArrows();
|
abstract protected boolean shouldTrackUpDown();
|
||||||
|
abstract protected boolean shouldTrackLeftRight();
|
||||||
abstract protected boolean shouldTrackNumPress();
|
abstract protected boolean shouldTrackNumPress();
|
||||||
|
|
||||||
// default hardware key handlers
|
// default hardware key handlers
|
||||||
|
|
@ -378,6 +382,8 @@ abstract class KeyPadHandler extends InputMethodService {
|
||||||
abstract public boolean onOK();
|
abstract public boolean onOK();
|
||||||
abstract protected boolean onUp();
|
abstract protected boolean onUp();
|
||||||
abstract protected boolean onDown();
|
abstract protected boolean onDown();
|
||||||
|
abstract protected boolean onLeft();
|
||||||
|
abstract protected boolean onRight();
|
||||||
abstract protected boolean onNumber(int key, boolean hold, boolean repeat);
|
abstract protected boolean onNumber(int key, boolean hold, boolean repeat);
|
||||||
abstract protected boolean onStar();
|
abstract protected boolean onStar();
|
||||||
abstract protected boolean onPound();
|
abstract protected boolean onPound();
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
private SoftKeyHandler softKeyHandler = null;
|
private SoftKeyHandler softKeyHandler = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static Context getMainContext() {
|
public static Context getMainContext() {
|
||||||
return self.getApplicationContext();
|
return self.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +74,8 @@ 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);
|
determineAllowedInputModes(inputField);
|
||||||
determineAllowedTextCases();
|
determineAllowedTextCases();
|
||||||
|
|
@ -120,7 +121,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
if (mInputMode.onBackspace()) {
|
if (mInputMode.onBackspace()) {
|
||||||
getSuggestions();
|
getSuggestions();
|
||||||
} else {
|
} else {
|
||||||
commitCurrentSuggestion();
|
commitCurrentSuggestion(false);
|
||||||
super.sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
|
super.sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,25 +131,49 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
|
|
||||||
|
|
||||||
public boolean onOK() {
|
public boolean onOK() {
|
||||||
Logger.d("onOK", "enter handler");
|
if (isSuggestionViewHidden() && currentInputConnection != null) {
|
||||||
|
return sendDefaultEditorAction(false);
|
||||||
|
}
|
||||||
|
|
||||||
acceptCurrentSuggestion();
|
mInputMode.onAcceptSuggestion(mLanguage, mSuggestionView.getCurrentSuggestion());
|
||||||
|
commitCurrentSuggestion();
|
||||||
|
determineNextTextCase();
|
||||||
resetKeyRepeat();
|
resetKeyRepeat();
|
||||||
mInputMode.reset();
|
|
||||||
|
|
||||||
return !isSuggestionViewHidden();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean onUp() {
|
protected boolean onUp() {
|
||||||
return previousSuggestion();
|
return previousSuggestion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean onDown() {
|
protected boolean onDown() {
|
||||||
return nextSuggestion();
|
return nextSuggestion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean onLeft() {
|
||||||
|
if (mInputMode.isStemFilterOn()) {
|
||||||
|
mInputMode.clearStemFilter();
|
||||||
|
mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, getComposingText());
|
||||||
|
} else {
|
||||||
|
jumpBeforeComposingText();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean onRight() {
|
||||||
|
String filter = mSuggestionView.getCurrentSuggestion();
|
||||||
|
|
||||||
|
if (mInputMode.setStemFilter(mLanguage, filter)) {
|
||||||
|
mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, filter);
|
||||||
|
} else if (filter.length() == 0) {
|
||||||
|
mInputMode.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onNumber
|
* onNumber
|
||||||
*
|
*
|
||||||
|
|
@ -159,7 +184,13 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
*/
|
*/
|
||||||
protected boolean onNumber(int key, boolean hold, boolean repeat) {
|
protected boolean onNumber(int key, boolean hold, boolean repeat) {
|
||||||
if (mInputMode.shouldAcceptCurrentSuggestion(key, hold, repeat)) {
|
if (mInputMode.shouldAcceptCurrentSuggestion(key, hold, repeat)) {
|
||||||
acceptCurrentSuggestion();
|
mInputMode.onAcceptSuggestion(mLanguage, getComposingText());
|
||||||
|
commitCurrentSuggestion(false);
|
||||||
|
determineNextTextCase();
|
||||||
|
} else if (!InputFieldHelper.isThereText(currentInputConnection)) {
|
||||||
|
// it would have been nice to determine the text case on every key press,
|
||||||
|
// but it is somewhat resource-intensive
|
||||||
|
determineNextTextCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mInputMode.onNumber(mLanguage, key, hold, repeat)) {
|
if (!mInputMode.onNumber(mLanguage, key, hold, repeat)) {
|
||||||
|
|
@ -172,7 +203,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mInputMode.getWord() != null) {
|
if (mInputMode.getWord() != null) {
|
||||||
setText(mInputMode.getWord());
|
commitText(mInputMode.getWord());
|
||||||
} else {
|
} else {
|
||||||
getSuggestions();
|
getSuggestions();
|
||||||
}
|
}
|
||||||
|
|
@ -182,13 +213,13 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
|
|
||||||
|
|
||||||
protected boolean onPound() {
|
protected boolean onPound() {
|
||||||
setText("#");
|
commitText("#");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean onStar() {
|
protected boolean onStar() {
|
||||||
setText("*");
|
commitText("*");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,8 +259,12 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean shouldTrackArrows() {
|
protected boolean shouldTrackUpDown() {
|
||||||
return mEditing != EDITING_NOSHOW && !isSuggestionViewHidden();
|
return mEditing != EDITING_NOSHOW && !isSuggestionViewHidden() && mInputMode.shouldTrackUpDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean shouldTrackLeftRight() {
|
||||||
|
return mEditing != EDITING_NOSHOW && !isSuggestionViewHidden() && mInputMode.shouldTrackLeftRight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -261,44 +296,17 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSuggestions(ArrayList<String> suggestions, int maxWordLength) {
|
|
||||||
setSuggestions(suggestions);
|
|
||||||
|
|
||||||
// Put the first suggestion in the text field,
|
|
||||||
// but cut it off to the length of the sequence (how many keys were pressed),
|
|
||||||
// for a more intuitive experience.
|
|
||||||
String word = mSuggestionView.getCurrentSuggestion();
|
|
||||||
word = word.substring(0, Math.min(maxWordLength, word.length()));
|
|
||||||
setComposingText(word);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Handler handleSuggestionsAsync = new Handler(Looper.getMainLooper()) {
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
handleSuggestions(
|
|
||||||
msg.getData().getStringArrayList("suggestions"),
|
|
||||||
msg.getData().getInt("maxWordLength", 1000)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private void acceptCurrentSuggestion() {
|
|
||||||
mInputMode.onAcceptSuggestion(mLanguage, mSuggestionView.getCurrentSuggestion());
|
|
||||||
commitCurrentSuggestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void commitCurrentSuggestion() {
|
private void commitCurrentSuggestion() {
|
||||||
// commit the current suggestion to the input field
|
commitCurrentSuggestion(true);
|
||||||
if (!isSuggestionViewHidden()) {
|
}
|
||||||
if (mSuggestionView.getCurrentSuggestion().equals(" ")) {
|
|
||||||
// finishComposingText() seems to ignore a single space,
|
private void commitCurrentSuggestion(boolean entireSuggestion) {
|
||||||
// so we have to force commit it.
|
if (!isSuggestionViewHidden() && currentInputConnection != null) {
|
||||||
setText(" ");
|
if (entireSuggestion) {
|
||||||
} else {
|
setComposingTextFromCurrentSuggestion();
|
||||||
currentInputConnection.finishComposingText();
|
|
||||||
}
|
}
|
||||||
|
currentInputConnection.finishComposingText();
|
||||||
}
|
}
|
||||||
|
|
||||||
setSuggestions(null);
|
setSuggestions(null);
|
||||||
|
|
@ -309,17 +317,39 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
setSuggestions(null);
|
setSuggestions(null);
|
||||||
|
|
||||||
if (currentInputConnection != null) {
|
if (currentInputConnection != null) {
|
||||||
setComposingTextFromCurrentSuggestion();
|
setComposingText("");
|
||||||
currentInputConnection.finishComposingText();
|
currentInputConnection.finishComposingText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void getSuggestions() {
|
private void getSuggestions() {
|
||||||
if (!mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, mSuggestionView.getCurrentSuggestion())) {
|
if (!mInputMode.getSuggestionsAsync(handleSuggestionsAsync, mLanguage, mSuggestionView.getCurrentSuggestion())) {
|
||||||
handleSuggestions(mInputMode.getSuggestions(), 1);
|
handleSuggestions(mInputMode.getSuggestions());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void handleSuggestions(ArrayList<String> suggestions) {
|
||||||
|
setSuggestions(suggestions);
|
||||||
|
|
||||||
|
// Put the first suggestion in the text field,
|
||||||
|
// but cut it off to the length of the sequence (how many keys were pressed),
|
||||||
|
// for a more intuitive experience.
|
||||||
|
String word = mSuggestionView.getCurrentSuggestion();
|
||||||
|
word = word.substring(0, Math.min(mInputMode.getSequenceLength(), word.length()));
|
||||||
|
setComposingText(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final Handler handleSuggestionsAsync = new Handler(Looper.getMainLooper()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
handleSuggestions(msg.getData().getStringArrayList("suggestions"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
private void setSuggestions(List<String> suggestions) {
|
private void setSuggestions(List<String> suggestions) {
|
||||||
if (mSuggestionView == null) {
|
if (mSuggestionView == null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -332,14 +362,35 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
setCandidatesViewShown(show);
|
setCandidatesViewShown(show);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setText(String text) {
|
|
||||||
if (text != null) {
|
private void commitText(String text) {
|
||||||
currentInputConnection.commitText(text, text.length());
|
if (text != null && currentInputConnection != null) {
|
||||||
|
currentInputConnection.commitText(text, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getComposingText() {
|
||||||
|
String text = mSuggestionView.getCurrentSuggestion();
|
||||||
|
if (text.length() > 0 && text.length() > mInputMode.getSequenceLength()) {
|
||||||
|
text = text.substring(0, mInputMode.getSequenceLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void setComposingText(String text) {
|
private void setComposingText(String text) {
|
||||||
currentInputConnection.setComposingText(text, 1);
|
if (text != null && currentInputConnection != null) {
|
||||||
|
currentInputConnection.setComposingText(text, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setComposingTextFromCurrentSuggestion() {
|
||||||
|
if (!isSuggestionViewHidden()) {
|
||||||
|
setComposingText(mSuggestionView.getCurrentSuggestion());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -356,7 +407,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
mTextCase = allowedTextCases.get(modeIndex);
|
mTextCase = allowedTextCases.get(modeIndex);
|
||||||
|
|
||||||
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
|
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
|
||||||
setComposingTextFromCurrentSuggestion();
|
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() && mTextCase == InputMode.CASE_LOWER) {
|
||||||
|
|
@ -375,12 +426,6 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setComposingTextFromCurrentSuggestion() {
|
|
||||||
if (!isSuggestionViewHidden()) {
|
|
||||||
setComposingText(mSuggestionView.getCurrentSuggestion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void nextLang() {
|
private void nextLang() {
|
||||||
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
|
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
|
||||||
|
|
@ -403,6 +448,17 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void jumpBeforeComposingText() {
|
||||||
|
if (currentInputConnection != null) {
|
||||||
|
currentInputConnection.setComposingText(getComposingText(), 0);
|
||||||
|
currentInputConnection.finishComposingText();
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuggestions(null);
|
||||||
|
mInputMode.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void determineAllowedInputModes(EditorInfo inputField) {
|
private void determineAllowedInputModes(EditorInfo inputField) {
|
||||||
allowedInputModes = InputFieldHelper.determineInputModes(inputField);
|
allowedInputModes = InputFieldHelper.determineInputModes(inputField);
|
||||||
|
|
||||||
|
|
@ -431,8 +487,19 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void determineNextTextCase() {
|
||||||
|
int nextTextCase = mInputMode.getNextWordTextCase(
|
||||||
|
mTextCase,
|
||||||
|
InputFieldHelper.isThereText(currentInputConnection),
|
||||||
|
(String) currentInputConnection.getTextBeforeCursor(50, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
mTextCase = nextTextCase != -1 ? nextTextCase : mTextCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void showAddWord() {
|
private void showAddWord() {
|
||||||
acceptCurrentSuggestion();
|
currentInputConnection.finishComposingText();
|
||||||
clearSuggestions();
|
clearSuggestions();
|
||||||
|
|
||||||
UI.showAddWordDialog(this, mLanguage.getId(), InputFieldHelper.getSurroundingWord(currentInputConnection));
|
UI.showAddWordDialog(this, mLanguage.getId(), InputFieldHelper.getSurroundingWord(currentInputConnection));
|
||||||
|
|
@ -453,7 +520,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Logger.d("restoreAddedWordIfAny", "Restoring word: '" + word + "'...");
|
Logger.d("restoreAddedWordIfAny", "Restoring word: '" + word + "'...");
|
||||||
setText(word);
|
commitText(word);
|
||||||
mInputMode.reset();
|
mInputMode.reset();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.w("tt9/restoreLastWord", "Could not restore the last added word. " + e.getMessage());
|
Logger.w("tt9/restoreLastWord", "Could not restore the last added word. " + e.getMessage());
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ abstract public class InputMode {
|
||||||
public static final int MODE_123 = 2;
|
public static final int MODE_123 = 2;
|
||||||
|
|
||||||
// text case
|
// text case
|
||||||
public static final int CASE_LOWER = 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_UPPER = 2;
|
public static final int CASE_LOWER = 2;
|
||||||
protected ArrayList<Integer> allowedTextCases = new ArrayList<>();
|
protected ArrayList<Integer> allowedTextCases = new ArrayList<>();
|
||||||
|
|
||||||
// data
|
// data
|
||||||
|
|
@ -47,10 +47,9 @@ abstract public class InputMode {
|
||||||
public void onAcceptSuggestion(Language language, String suggestion) {}
|
public void onAcceptSuggestion(Language language, String suggestion) {}
|
||||||
public ArrayList<String> getSuggestions() { return suggestions; }
|
public ArrayList<String> getSuggestions() { return suggestions; }
|
||||||
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) { return false; }
|
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) { return false; }
|
||||||
protected void sendSuggestions(Handler handler, ArrayList<String> suggestions, int maxWordLength) {
|
protected void sendSuggestions(Handler handler, ArrayList<String> suggestions) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putStringArrayList("suggestions", suggestions);
|
bundle.putStringArrayList("suggestions", suggestions);
|
||||||
bundle.putInt("maxWordLength", maxWordLength);
|
|
||||||
Message msg = new Message();
|
Message msg = new Message();
|
||||||
msg.setData(bundle);
|
msg.setData(bundle);
|
||||||
handler.sendMessage(msg);
|
handler.sendMessage(msg);
|
||||||
|
|
@ -67,11 +66,23 @@ abstract public class InputMode {
|
||||||
// Utility
|
// Utility
|
||||||
abstract public int getId();
|
abstract public int getId();
|
||||||
public ArrayList<Integer> getAllowedTextCases() { return allowedTextCases; }
|
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() {
|
public void reset() {
|
||||||
suggestions = new ArrayList<>();
|
suggestions = new ArrayList<>();
|
||||||
word = null;
|
word = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stem filtering.
|
||||||
|
// Where applicable, return "true" if the mode supports it and the operation was possible.
|
||||||
|
public boolean isStemFilterOn() { return false; }
|
||||||
|
public void clearStemFilter() {}
|
||||||
|
public boolean setStemFilter(Language language, String stem) { return false; }
|
||||||
|
|
||||||
public boolean shouldTrackNumPress() { return true; }
|
public boolean shouldTrackNumPress() { return true; }
|
||||||
|
public boolean shouldTrackUpDown() { return false; }
|
||||||
|
public boolean shouldTrackLeftRight() { return false; }
|
||||||
public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return false; }
|
public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return false; }
|
||||||
public boolean shouldSelectNextSuggestion() { return false; }
|
public boolean shouldSelectNextSuggestion() { return false; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,5 +24,6 @@ public class Mode123 extends InputMode {
|
||||||
|
|
||||||
|
|
||||||
final public boolean is123() { return true; }
|
final public boolean is123() { return true; }
|
||||||
|
public int getSequenceLength() { return 0; }
|
||||||
public boolean shouldTrackNumPress() { return false; }
|
public boolean shouldTrackNumPress() { return false; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,11 @@ public class ModeABC extends InputMode {
|
||||||
|
|
||||||
|
|
||||||
final public boolean isABC() { return true; }
|
final public boolean isABC() { return true; }
|
||||||
|
public int getSequenceLength() { return 1; }
|
||||||
|
|
||||||
public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return hold || !repeat; }
|
public boolean shouldAcceptCurrentSuggestion(int key, boolean hold, boolean repeat) { return hold || !repeat; }
|
||||||
|
public boolean shouldTrackUpDown() { return true; }
|
||||||
|
public boolean shouldTrackLeftRight() { return true; }
|
||||||
public boolean shouldSelectNextSuggestion() {
|
public boolean shouldSelectNextSuggestion() {
|
||||||
return shouldSelectNextLetter;
|
return shouldSelectNextLetter;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.Logger;
|
import io.github.sspanak.tt9.Logger;
|
||||||
import io.github.sspanak.tt9.db.DictionaryDb;
|
import io.github.sspanak.tt9.db.DictionaryDb;
|
||||||
|
|
@ -16,33 +17,47 @@ import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||||
public class ModePredictive extends InputMode {
|
public class ModePredictive extends InputMode {
|
||||||
public int getId() { return MODE_PREDICTIVE; }
|
public int getId() { return MODE_PREDICTIVE; }
|
||||||
|
|
||||||
private Language currentLanguage = null;
|
private boolean isEmoji = false;
|
||||||
private String digitSequence = "";
|
private String digitSequence = "";
|
||||||
private boolean isEmoticon = false;
|
|
||||||
|
// stem filter
|
||||||
|
private String stemFilter = "";
|
||||||
|
private final int STEM_FILTER_MIN_LENGTH = 2;
|
||||||
|
|
||||||
|
// async suggestion handling
|
||||||
|
private Language currentLanguage = null;
|
||||||
private String lastInputFieldWord = "";
|
private String lastInputFieldWord = "";
|
||||||
private static Handler handleSuggestionsExternal;
|
private static Handler handleSuggestionsExternal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ModePredictive() {
|
ModePredictive() {
|
||||||
|
allowedTextCases.add(CASE_UPPER);
|
||||||
allowedTextCases.add(CASE_CAPITALIZE);
|
allowedTextCases.add(CASE_CAPITALIZE);
|
||||||
allowedTextCases.add(CASE_LOWER);
|
allowedTextCases.add(CASE_LOWER);
|
||||||
allowedTextCases.add(CASE_UPPER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean onBackspace() {
|
public boolean onBackspace() {
|
||||||
|
if (stemFilter.length() < STEM_FILTER_MIN_LENGTH) {
|
||||||
|
stemFilter = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (digitSequence.length() < 1) {
|
if (digitSequence.length() < 1) {
|
||||||
|
stemFilter = "";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
digitSequence = digitSequence.substring(0, digitSequence.length() - 1);
|
digitSequence = digitSequence.substring(0, digitSequence.length() - 1);
|
||||||
|
if (stemFilter.length() > digitSequence.length()) {
|
||||||
|
stemFilter = stemFilter.substring(0, digitSequence.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean onNumber(Language l, int key, boolean hold, boolean repeat) {
|
public boolean onNumber(Language l, int key, boolean hold, boolean repeat) {
|
||||||
isEmoticon = false;
|
isEmoji = false;
|
||||||
|
|
||||||
if (hold) {
|
if (hold) {
|
||||||
// hold to type any digit
|
// hold to type any digit
|
||||||
|
|
@ -55,8 +70,8 @@ public class ModePredictive extends InputMode {
|
||||||
} else if (key == 1 && repeat) {
|
} else if (key == 1 && repeat) {
|
||||||
// emoticons
|
// emoticons
|
||||||
reset();
|
reset();
|
||||||
isEmoticon = true;
|
isEmoji = true;
|
||||||
suggestions = Punctuation.Emoticons;
|
suggestions = Punctuation.Emoji;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// words
|
// words
|
||||||
|
|
@ -71,6 +86,7 @@ public class ModePredictive extends InputMode {
|
||||||
public void reset() {
|
public void reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
digitSequence = "";
|
digitSequence = "";
|
||||||
|
stemFilter = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -78,6 +94,11 @@ public class ModePredictive extends InputMode {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSequenceLength() { return isEmoji ? 2 : digitSequence.length(); }
|
||||||
|
|
||||||
|
public boolean shouldTrackUpDown() { return true; }
|
||||||
|
public boolean shouldTrackLeftRight() { return true; }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shouldAcceptCurrentSuggestion
|
* shouldAcceptCurrentSuggestion
|
||||||
|
|
@ -97,6 +118,47 @@ public class ModePredictive extends InputMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* isStemFilterOn
|
||||||
|
* Returns "true" if a filter was applied using "setStem()".
|
||||||
|
*/
|
||||||
|
public boolean isStemFilterOn() {
|
||||||
|
return stemFilter.length() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clearStemFilter
|
||||||
|
* Do not filter the suggestions by the word set using "setStem()", use only the digit sequence.
|
||||||
|
*/
|
||||||
|
public void clearStemFilter() {
|
||||||
|
stemFilter = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setStemFilter
|
||||||
|
* Filter the possible suggestions by the given stem. The stem must have
|
||||||
|
* a minimum length of STEM_FILTER_MIN_LENGTH.
|
||||||
|
*
|
||||||
|
* Note that you need to manually get the suggestions again to obtain a filtered list.
|
||||||
|
*/
|
||||||
|
public boolean setStemFilter(Language language, String stem) {
|
||||||
|
if (language == null || stem == null || stem.length() < STEM_FILTER_MIN_LENGTH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
digitSequence = language.getDigitSequenceForWord(stem);
|
||||||
|
stemFilter = stem;
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.w("tt9/setStemFilter", "Ignoring invalid stem filter: " + stem + ". " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getSuggestionsAsync
|
* getSuggestionsAsync
|
||||||
* 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
|
||||||
|
|
@ -106,8 +168,8 @@ public class ModePredictive extends InputMode {
|
||||||
* See: generateSuggestionWhenNone()
|
* See: generateSuggestionWhenNone()
|
||||||
*/
|
*/
|
||||||
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) {
|
public boolean getSuggestionsAsync(Handler handler, Language language, String lastWord) {
|
||||||
if (isEmoticon) {
|
if (isEmoji) {
|
||||||
super.sendSuggestions(handler, suggestions, 2);
|
super.sendSuggestions(handler, suggestions);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,6 +186,7 @@ public class ModePredictive extends InputMode {
|
||||||
handleSuggestions,
|
handleSuggestions,
|
||||||
language,
|
language,
|
||||||
digitSequence,
|
digitSequence,
|
||||||
|
stemFilter,
|
||||||
T9Preferences.getInstance().getSuggestionsMin(),
|
T9Preferences.getInstance().getSuggestionsMin(),
|
||||||
T9Preferences.getInstance().getSuggestionsMax()
|
T9Preferences.getInstance().getSuggestionsMax()
|
||||||
);
|
);
|
||||||
|
|
@ -143,7 +206,7 @@ public class ModePredictive extends InputMode {
|
||||||
ArrayList<String> suggestions = msg.getData().getStringArrayList("suggestions");
|
ArrayList<String> suggestions = msg.getData().getStringArrayList("suggestions");
|
||||||
suggestions = generateSuggestionWhenNone(suggestions, currentLanguage, lastInputFieldWord);
|
suggestions = generateSuggestionWhenNone(suggestions, currentLanguage, lastInputFieldWord);
|
||||||
|
|
||||||
ModePredictive.super.sendSuggestions(handleSuggestionsExternal, suggestions, digitSequence.length());
|
ModePredictive.super.sendSuggestions(handleSuggestionsExternal, suggestions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -152,9 +215,9 @@ public class ModePredictive extends InputMode {
|
||||||
* When there are no matching suggestions after the last key press, generate a list of possible
|
* When there are no matching suggestions after the last key press, generate a list of possible
|
||||||
* ones, so that the user can complete the missing word.
|
* ones, so that the user can complete the missing word.
|
||||||
*/
|
*/
|
||||||
private ArrayList<String> generateSuggestionWhenNone(ArrayList<String> suggestions, Language language, String lastWord) {
|
private ArrayList<String> generateSuggestionWhenNone(ArrayList<String> suggestions, Language language, String word) {
|
||||||
if (
|
if (
|
||||||
(lastWord == null || lastWord.length() == 0) ||
|
(word == null || word.length() == 0) ||
|
||||||
(suggestions != null && suggestions.size() > 0) ||
|
(suggestions != null && suggestions.size() > 0) ||
|
||||||
digitSequence.length() == 0 ||
|
digitSequence.length() == 0 ||
|
||||||
digitSequence.charAt(0) == '1'
|
digitSequence.charAt(0) == '1'
|
||||||
|
|
@ -162,15 +225,23 @@ public class ModePredictive extends InputMode {
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastWord = lastWord.substring(0, Math.min(digitSequence.length() - 1, lastWord.length()));
|
// append all letters for the last key
|
||||||
try {
|
word = word.substring(0, Math.min(digitSequence.length() - 1, word.length()));
|
||||||
int lastDigit = digitSequence.charAt(digitSequence.length() - 1) - '0';
|
ArrayList<String> generatedSuggestions = new ArrayList<>();
|
||||||
lastWord += language.getKeyCharacters(lastDigit).get(0);
|
int lastSequenceDigit = digitSequence.charAt(digitSequence.length() - 1) - '0';
|
||||||
} catch (Exception e) {
|
|
||||||
lastWord += digitSequence.charAt(digitSequence.length() - 1);
|
for (String keyLetter : language.getKeyCharacters(lastSequenceDigit)) {
|
||||||
|
if (keyLetter.charAt(0) - '0' > '9') { // append only letters, not numbers
|
||||||
|
generatedSuggestions.add(word + keyLetter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ArrayList<>(Collections.singletonList(lastWord));
|
// if there are no letters for this key, just append the number
|
||||||
|
if (generatedSuggestions.size() == 0) {
|
||||||
|
generatedSuggestions.add(word + digitSequence.charAt(digitSequence.length() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return generatedSuggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -179,7 +250,7 @@ public class ModePredictive extends InputMode {
|
||||||
* Bring this word up in the suggestions list next time.
|
* Bring this word up in the suggestions list next time.
|
||||||
*/
|
*/
|
||||||
public void onAcceptSuggestion(Language language, String currentWord) {
|
public void onAcceptSuggestion(Language language, String currentWord) {
|
||||||
digitSequence = "";
|
reset();
|
||||||
|
|
||||||
if (currentWord.length() == 0) {
|
if (currentWord.length() == 0) {
|
||||||
Logger.i("acceptCurrentSuggestion", "Current word is empty. Nothing to accept.");
|
Logger.i("acceptCurrentSuggestion", "Current word is empty. Nothing to accept.");
|
||||||
|
|
@ -193,4 +264,30 @@ public class ModePredictive extends InputMode {
|
||||||
Logger.e("tt9/ModePredictive", "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage());
|
Logger.e("tt9/ModePredictive", "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getNextWordTextCase
|
||||||
|
* 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) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start of text
|
||||||
|
if (!isThereText) {
|
||||||
|
return CASE_CAPITALIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start of sentence, excluding after "..."
|
||||||
|
Matcher endOfSentenceMatch = Pattern.compile("(?<!\\.)[.?!]\\s*$").matcher(textBeforeCursor);
|
||||||
|
if (endOfSentenceMatch.find()) {
|
||||||
|
return CASE_CAPITALIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CASE_LOWER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ public class Punctuation {
|
||||||
" ", "+", "\n"
|
" ", "+", "\n"
|
||||||
));
|
));
|
||||||
|
|
||||||
final public static ArrayList<String> Emoticons = new ArrayList<>(Arrays.asList(
|
final public static ArrayList<String> Emoji = new ArrayList<>(Arrays.asList(
|
||||||
"👍", ":)", ":D", ";)", ":(", ":P"
|
"👍", "🙂", "😀", "😉", "🙁", "😢", "😛", "😬"
|
||||||
));
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue