diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b4e4fb1c..ea39b483 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java index 56880199..416661f5 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java @@ -176,7 +176,7 @@ abstract public class CommandHandler extends TextEditingHandler { } } - suggestionOps.set(mInputMode.getSuggestions(), currentSuggestionIndex); + suggestionOps.set(mInputMode.getSuggestions(), currentSuggestionIndex, mInputMode.containsGeneratedSuggestions()); textField.setComposingText(suggestionOps.getCurrent()); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java index 74a6fd0d..582dce45 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java @@ -55,7 +55,7 @@ public class TraditionalT9 extends MainViewHandler { initTray(); setDarkTheme(); statusBar.setText(mInputMode); - suggestionOps.set(mInputMode.getSuggestions()); + suggestionOps.set(mInputMode.getSuggestions(), mInputMode.containsGeneratedSuggestions()); return mainView.getView(); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java index e8f620a6..4b1507e4 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java @@ -352,7 +352,7 @@ public abstract class TypingHandler extends KeyPadHandler { } // display the word suggestions - suggestionOps.set(mInputMode.getSuggestions()); + suggestionOps.set(mInputMode.getSuggestions(), mInputMode.containsGeneratedSuggestions()); // In case we are here, because the language was changed, and there were words for the old language, // but there are no words for the new language, we'll get only generated suggestions, consisting diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java index f7cd1e18..28d95057 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java @@ -49,14 +49,17 @@ public class SuggestionOps { textField.finishComposingText(); } - public void set(ArrayList suggestions) { - suggestionBar.setSuggestions(suggestions, 0); + suggestionBar.setSuggestions(suggestions, 0, false); + } + + public void set(ArrayList suggestions, boolean containsGenerated) { + suggestionBar.setSuggestions(suggestions, 0, containsGenerated); } - public void set(ArrayList suggestions, int selectIndex) { - suggestionBar.setSuggestions(suggestions, selectIndex); + public void set(ArrayList suggestions, int selectIndex, boolean containsGenerated) { + suggestionBar.setSuggestions(suggestions, selectIndex, containsGenerated); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java index 79533d39..fc94ff82 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java @@ -85,6 +85,7 @@ abstract public class InputMode { // Utility abstract public int getId(); + public boolean containsGeneratedSuggestions() { return false; } public int getSequenceLength() { return digitSequence.length(); } // The number of key presses for the current word. public int getAutoAcceptTimeout() { return autoAcceptTimeout; diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java index 6511c3fb..cc66be97 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java @@ -243,6 +243,11 @@ public class ModePredictive extends InputMode { } + @Override + public boolean containsGeneratedSuggestions() { + return predictions.containsGeneratedWords(); + } + /** * loadSuggestions * Loads the possible list of suggestions for the current digitSequence. "currentWord" is used diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java index 41017a4d..04a6dd11 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java @@ -20,6 +20,7 @@ public class Predictions { // data private boolean areThereDbWords = false; + private boolean containsGeneratedWords = false; private ArrayList words = new ArrayList<>(); @@ -53,6 +54,10 @@ public class Predictions { return this; } + public boolean containsGeneratedWords() { + return containsGeneratedWords; + } + public ArrayList getList() { return words; } @@ -93,6 +98,8 @@ public class Predictions { * sequence or loads the static ones. */ public void load() { + containsGeneratedWords = false; + if (digitSequence == null || digitSequence.isEmpty()) { words.clear(); onWordsChanged.run(); @@ -194,6 +201,7 @@ public class Predictions { generatedWords.add(baseWord + digitSequence.charAt(digitSequence.length() - 1)); } + containsGeneratedWords = true; return generatedWords; } @@ -235,6 +243,7 @@ public class Predictions { } } + containsGeneratedWords = true; return complementedWords; } @@ -273,6 +282,7 @@ public class Predictions { } } + containsGeneratedWords = !variations.isEmpty(); return variations; } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java index 74a3134e..6466784d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java @@ -25,6 +25,11 @@ import io.github.sspanak.tt9.ui.main.ResizableMainView; import io.github.sspanak.tt9.util.Characters; public class SuggestionsBar { + private final String STEM_SUFFIX = "… +"; + private final String STEM_VARIATION_PREFIX = "…"; + private final String STEM_PUNCTUATION_VARIATION_PREFIX = " "; + @NonNull private String stem = ""; + private double lastClickTime = 0; private final List suggestions = new ArrayList<>(); protected int selectedIndex = 0; @@ -116,25 +121,72 @@ public class SuggestionsBar { return ""; } + if (suggestions.get(id).endsWith(STEM_SUFFIX)) { + return stem; + } else if (suggestions.get(id).startsWith(STEM_VARIATION_PREFIX)) { + return stem + suggestions.get(id).substring(STEM_VARIATION_PREFIX.length()); + } else if (suggestions.get(id).startsWith(STEM_PUNCTUATION_VARIATION_PREFIX)) { + return stem + suggestions.get(id).substring(STEM_PUNCTUATION_VARIATION_PREFIX.length()); + } + return suggestions.get(id).equals(Characters.getNewLine()) ? "\n" : suggestions.get(id); } - public void setSuggestions(List newSuggestions, int initialSel) { + public void setSuggestions(List newSuggestions, int initialSel, boolean containsGenerated) { ecoSetBackground(newSuggestions); suggestions.clear(); - selectedIndex = 0; + selectedIndex = newSuggestions == null || newSuggestions.isEmpty() ? 0 : Math.max(initialSel, 0); - if (newSuggestions != null) { - for (String suggestion : newSuggestions) { - // make the new line better readable - suggestions.add(suggestion.equals("\n") ? Characters.getNewLine() : suggestion); - } - selectedIndex = Math.max(initialSel, 0); + setStem(newSuggestions, containsGenerated); + addAllSuggestions(newSuggestions); + setSuggestionsOnScreen(); + } + + + private void setStem(List newSuggestions, boolean containsGenerated) { + if (newSuggestions == null || newSuggestions.isEmpty()) { + stem = ""; + return; } - setSuggestionsOnScreen(); + stem = containsGenerated ? newSuggestions.get(0).substring(0, newSuggestions.get(0).length() - 1) : ""; + + // Do not modify single letter + punctuation, such as "j'" or "l'". They look better as they are. + stem = (stem.length() == 1 && newSuggestions.get(0).length() == 2 && !Character.isAlphabetic(newSuggestions.get(0).charAt(1))) ? "" : stem; + + if (!stem.isEmpty() && !newSuggestions.contains(stem)) { + suggestions.add(stem + STEM_SUFFIX); + selectedIndex++; + } + } + + + private void addAllSuggestions(List newSuggestions) { + if (newSuggestions != null) { + for (String suggestion : newSuggestions) { + addSuggestion(suggestion); + } + } + } + + + private void addSuggestion(@NonNull String suggestion) { + // shorten the stem variations + if (!stem.isEmpty() && suggestion.length() == stem.length() + 1 && suggestion.toLowerCase().startsWith(stem.toLowerCase())) { + String trimmedSuggestion = suggestion.substring(stem.length()); + trimmedSuggestion = Character.isAlphabetic(trimmedSuggestion.charAt(0)) ? STEM_VARIATION_PREFIX + trimmedSuggestion : STEM_PUNCTUATION_VARIATION_PREFIX + trimmedSuggestion; + suggestions.add(trimmedSuggestion); + } + // make the new line better readable + else if (suggestion.equals("\n")) { + suggestions.add(Characters.getNewLine()); + } + // or add any other suggestion as is + else { + suggestions.add(suggestion); + } } diff --git a/docs/user-manual.md b/docs/user-manual.md index 8814e0a1..4f6e5bc9 100644 --- a/docs/user-manual.md +++ b/docs/user-manual.md @@ -42,7 +42,7 @@ _Predictive mode only._ - **Single press**: Filter the suggestion list, leaving out only the ones that start with the current word. It doesn't matter if it is a complete word or not. For example, type "remin" and press Filter. It will leave out all words starting with "remin": "remin" itself, "remind", "reminds", "reminded", "reminding", and so on. - **Double press**: Expand the filter to the full suggestion. For example, type "remin" and press Filter twice. It will first filter by "remin", then expand the filter to "remind". You can keep expanding the filter until you get to the longest dictionary word. -Filtering can also be used to type unknown words. Let's say you want to type "Anakin", which is not in the dictionary. Start with "A", then press Filter to hide "B" and "C". Now press 6-key. Since the filter is on, in addition to the real dictionary words, it will provide all possible combinations for 6: "Am", "An", "Ao". Select "An" and press Filter to confirm your selection. Now pressing the 2-key, will provide "Ana", "Anb", and "Anc". Keep going, until you get "Anakin". +Filtering is also useful for typing unknown words. Let's say you want to type "Anakin", which is not in the dictionary. Start with "A", then press Filter to hide "B" and "C". Now press 6-key. Since the filter is on, in addition to the real dictionary words, it will provide all possible combinations for 1+6: "A..." + "m", "n", "o". Select "n" and press Filter to confirm your selection and produce "An". Now pressing the 2-key, will provide "An..." + "a", "b", and "c". Select "a", and keep going, until you get "Anakin". When filtering is enabled, the base text will become bold and italicized.