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 c4e95731..4aead923 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 @@ -225,7 +225,11 @@ public abstract class TypingHandler extends KeyPadHandler { textField.deletePrecedingSpace(currentWord); } - if (mInputMode.shouldAddAutoSpace(inputType, textField, isWordAcceptedManually, nextKey)) { + if (mInputMode.shouldAddPrecedingSpace(textField)) { + textField.addPrecedingSpace(currentWord); + } + + if (mInputMode.shouldAddTrailingSpace(inputType, textField, isWordAcceptedManually, nextKey)) { textField.setText(" "); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java index 486ed553..cb6c5338 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/TextField.java @@ -229,6 +229,29 @@ public class TextField extends InputField { } + /** + * Adds a space before the given word if the word is before the cursor. No action is taken if + * there is no such word before the cursor. + */ + public void addPrecedingSpace(String word) { + if (connection == null) { + return; + } + + connection.beginBatchEdit(); + CharSequence beforeText = connection.getTextBeforeCursor(word.length(), 0); + if (beforeText == null || !beforeText.equals(word)) { + connection.endBatchEdit(); + return; + } + + connection.deleteSurroundingText(word.length(), 0); + connection.commitText(" " + word, 1); + + connection.endBatchEdit(); + } + + /** * Erases the previous N characters and sets the given "text" as composing text. N is the length of * the given "text". Returns "true" if the operation was successful, "false" otherwise. 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 97883f62..ff2feab3 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 @@ -106,7 +106,8 @@ abstract public class InputMode { // Interaction with the IME. Return "true" if it should perform the respective action. public boolean shouldAcceptPreviousSuggestion() { return false; } public boolean shouldAcceptPreviousSuggestion(int nextKey) { return false; } - public boolean shouldAddAutoSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) { return false; } + public boolean shouldAddTrailingSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) { return false; } + public boolean shouldAddPrecedingSpace(TextField textField) { return false; } public boolean shouldDeletePrecedingSpace(InputType inputType) { return false; } public boolean shouldIgnoreText(String text) { return text == null || text.isEmpty(); } public boolean shouldSelectNextSuggestion() { return false; } 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 e9d5db48..a5596f1d 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 @@ -46,7 +46,7 @@ public class ModePredictive extends InputMode { ModePredictive(SettingsStore settings, InputType inputType, TextField textField, Language lang) { - autoSpace = new AutoSpace(settings, lang).setLanguage(lang); + autoSpace = new AutoSpace(settings).setLanguage(lang); autoTextCase = new AutoTextCase(settings); digitSequence = ""; predictions = new Predictions(settings, textField); @@ -464,13 +464,21 @@ public class ModePredictive extends InputMode { @Override - public boolean shouldAddAutoSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) { + public boolean shouldAddTrailingSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) { return autoSpace .setLastWord(lastAcceptedWord) .setLastSequence() .setInputType(inputType) .setTextField(textField) - .shouldAddAutoSpace(isWordAcceptedManually, nextKey); + .shouldAddTrailingSpace(isWordAcceptedManually, nextKey); + } + + + @Override + public boolean shouldAddPrecedingSpace(TextField textField) { + return autoSpace + .setTextField(textField) + .shouldAddBeforePunctuation(); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java index 25919982..2227b0d9 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java @@ -3,6 +3,7 @@ package io.github.sspanak.tt9.ime.modes.helpers; import io.github.sspanak.tt9.hacks.InputType; import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Characters; import io.github.sspanak.tt9.util.Text; @@ -13,29 +14,38 @@ public class AutoSpace { private InputType inputType; private TextField textField; private String lastWord; + + private boolean isLanguageFrench; private boolean isLanguageWithSpaceBetweenWords; - public AutoSpace(SettingsStore settingsStore, Language language) { + + public AutoSpace(SettingsStore settingsStore) { settings = settingsStore; + isLanguageFrench = false; isLanguageWithSpaceBetweenWords = true; } + public AutoSpace setInputType(InputType inputType) { this.inputType = inputType; return this; } + public AutoSpace setTextField(TextField textField) { this.textField = textField; return this; } + public AutoSpace setLastWord(String lastWord) { this.lastWord = lastWord; return this; } + public AutoSpace setLanguage(Language language) { + isLanguageFrench = LanguageKind.isFrench(language); isLanguageWithSpaceBetweenWords = language != null && language.hasSpaceBetweenWords(); return this; } @@ -44,13 +54,13 @@ public class AutoSpace { return this; } + /** - * shouldAddAutoSpace - * When the "auto-space" settings is enabled, this determines whether to automatically add a space - * at the end of a sentence or after accepting a suggestion. This allows faster typing, without - * pressing space. See the helper functions for the list of rules. + * Determines whether to automatically add a space at the end of a sentence or after accepting a + * suggestion. This allows faster typing, without pressing space. See the helper functions for + * the list of rules. */ - public boolean shouldAddAutoSpace(boolean isWordAcceptedManually, int nextKey) { + public boolean shouldAddTrailingSpace(boolean isWordAcceptedManually, int nextKey) { if (!isLanguageWithSpaceBetweenWords) { return false; } @@ -71,7 +81,32 @@ public class AutoSpace { /** - * shouldAddAfterPunctuation + * For languages that require a space before punctuation (currently only French), this determines + * whether to transform: "word?" to: "word ?". + */ + public boolean shouldAddBeforePunctuation() { + String previousChars = textField.getStringBeforeCursor(2); + char penultimateChar = previousChars.length() < 2 ? 0 : previousChars.charAt(previousChars.length() - 2); + char previousChar = previousChars.isEmpty() ? 0 : previousChars.charAt(previousChars.length() - 1); + + return + isLanguageWithSpaceBetweenWords + && isLanguageFrench + && settings.getAutoSpace() + && !inputType.isSpecialized() + && Character.isAlphabetic(penultimateChar) + && ( + previousChar == ';' + || previousChar == ':' + || previousChar == '!' + || previousChar == '?' + || previousChar == ')' + || previousChar == '»' + ); + } + + + /** * Determines whether to automatically adding a space after certain punctuation signs makes sense. * The rules are similar to the ones in the standard Android keyboard (with some exceptions, * because we are not using a QWERTY keyboard here). @@ -94,6 +129,7 @@ public class AutoSpace { || previousChar == ')' || previousChar == ']' || previousChar == '%' + || (isLanguageFrench && previousChar == '«') || previousChar == '»' || previousChar == '؟' || previousChar == '“' @@ -105,7 +141,6 @@ public class AutoSpace { /** - * shouldAddAfterWord * Similar to "shouldAddAfterPunctuation()", but determines whether to add a space after words. */ private boolean shouldAddAfterWord(boolean isWordAcceptedManually, String previousChars, Text nextChars, int nextKey) { @@ -118,9 +153,7 @@ public class AutoSpace { /** - * shouldDeletePrecedingSpace - * When the "auto-space" settings is enabled, determine whether to delete spaces before punctuation. - * This allows automatic conversion from: "words ." to: "words." + * Determines whether to transform: "word ." to: "word." */ public boolean shouldDeletePrecedingSpace() { return @@ -129,12 +162,12 @@ public class AutoSpace { && ( lastWord.equals(".") || lastWord.equals(",") - || lastWord.equals(";") - || lastWord.equals(":") - || lastWord.equals("!") - || lastWord.equals("?") + || (!isLanguageFrench && lastWord.equals(";")) + || (!isLanguageFrench && lastWord.equals(":")) + || (!isLanguageFrench && lastWord.equals("!")) + || (!isLanguageFrench && lastWord.equals("?")) || lastWord.equals("؟") - || lastWord.equals(")") + || (!isLanguageFrench && lastWord.equals(")")) || lastWord.equals("]") || lastWord.equals("'") || lastWord.equals("@") diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java index 9fb98f85..e66a5324 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java @@ -4,8 +4,9 @@ public class LanguageKind { public static boolean isArabic(Language language) { return language != null && language.getId() == 502337; } public static boolean isBulgarian(Language language) { return language != null && language.getId() == 231650; } public static boolean isCyrillic(Language language) { return language != null && language.getKeyCharacters(2).contains("а"); } - public static boolean isHebrew(Language language) { return language != null && (language.getId() == 305450 || language.getId() == 403177); } + public static boolean isFrench(Language language) { return language != null && language.getId() == 596550; } public static boolean isGreek(Language language) { return language != null && language.getId() == 597381; } + public static boolean isHebrew(Language language) { return language != null && (language.getId() == 305450 || language.getId() == 403177); } public static boolean isLatinBased(Language language) { return language != null && language.getKeyCharacters(2).contains("a"); } public static boolean isRTL(Language language) { return isArabic(language) || isHebrew(language); } public static boolean isUkrainian(Language language) { return language != null && language.getId() == 54645; }