diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/Combiner.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/Combiner.kt similarity index 51% rename from app/src/main/java/org/dslul/openboard/inputmethod/event/Combiner.java rename to app/src/main/java/org/dslul/openboard/inputmethod/event/Combiner.kt index ee14a59e6..c1290dfe0 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/Combiner.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/Combiner.kt @@ -1,30 +1,12 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +package org.dslul.openboard.inputmethod.event -package org.dslul.openboard.inputmethod.event; - -import java.util.ArrayList; - -import javax.annotation.Nonnull; +import java.util.* /** * A generic interface for combiners. Combiners are objects that transform chains of input events * into committable strings and manage feedback to show to the user on the combining state. */ -public interface Combiner { +interface Combiner { /** * Process an event, possibly combining it with the existing state and return the new event. * @@ -35,17 +17,16 @@ public interface Combiner { * @param event the event to combine with the existing state. * @return the resulting event. */ - @Nonnull - Event processEvent(ArrayList previousEvents, Event event); + fun processEvent(previousEvents: ArrayList?, event: Event?): Event? /** * Get the feedback that should be shown to the user for the current state of this combiner. * @return A CharSequence representing the feedback to show users. It may include styles. */ - CharSequence getCombiningStateFeedback(); + val combiningStateFeedback: CharSequence /** * Reset the state of this combiner, for example when the cursor was moved. */ - void reset(); -} + fun reset() +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/CombinerChain.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/CombinerChain.java deleted file mode 100644 index f7e4f9eba..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/CombinerChain.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dslul.openboard.inputmethod.event; - -import android.text.SpannableStringBuilder; -import android.text.TextUtils; - -import org.dslul.openboard.inputmethod.latin.common.Constants; - -import java.util.ArrayList; - -import javax.annotation.Nonnull; - -/** - * This class implements the logic chain between receiving events and generating code points. - * - * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard, - * or any exotic input source. - * This class will orchestrate the composing chain that starts with an event as its input. Each - * composer will be given turns one after the other. - * The output is composed of two sequences of code points: the first, representing the already - * finished combining part, will be shown normally as the composing string, while the second is - * feedback on the composing state and will typically be shown with different styling such as - * a colored background. - */ -public class CombinerChain { - // The already combined text, as described above - private StringBuilder mCombinedText; - // The feedback on the composing state, as described above - private SpannableStringBuilder mStateFeedback; - private final ArrayList mCombiners; - - /** - * Create an combiner chain. - * - * The combiner chain takes events as inputs and outputs code points and combining state. - * For example, if the input language is Japanese, the combining chain will typically perform - * kana conversion. This takes a string for initial text, taken to be present before the - * cursor: we'll start after this. - * - * @param initialText The text that has already been combined so far. - */ - public CombinerChain(final String initialText) { - mCombiners = new ArrayList<>(); - // The dead key combiner is always active, and always first - mCombiners.add(new DeadKeyCombiner()); - mCombinedText = new StringBuilder(initialText); - mStateFeedback = new SpannableStringBuilder(); - } - - public void reset() { - mCombinedText.setLength(0); - mStateFeedback.clear(); - for (final Combiner c : mCombiners) { - c.reset(); - } - } - - private void updateStateFeedback() { - mStateFeedback.clear(); - for (int i = mCombiners.size() - 1; i >= 0; --i) { - mStateFeedback.append(mCombiners.get(i).getCombiningStateFeedback()); - } - } - - /** - * Process an event through the combining chain, and return a processed event to apply. - * @param previousEvents the list of previous events in this composition - * @param newEvent the new event to process - * @return the processed event. It may be the same event, or a consumed event, or a completely - * new event. However it may never be null. - */ - @Nonnull - public Event processEvent(final ArrayList previousEvents, - @Nonnull final Event newEvent) { - final ArrayList modifiablePreviousEvents = new ArrayList<>(previousEvents); - Event event = newEvent; - for (final Combiner combiner : mCombiners) { - // A combiner can never return more than one event; it can return several - // code points, but they should be encapsulated within one event. - event = combiner.processEvent(modifiablePreviousEvents, event); - if (event.isConsumed()) { - // If the event is consumed, then we don't pass it to subsequent combiners: - // they should not see it at all. - break; - } - } - updateStateFeedback(); - return event; - } - - /** - * Apply a processed event. - * @param event the event to be applied - */ - public void applyProcessedEvent(final Event event) { - if (null != event) { - // TODO: figure out the generic way of doing this - if (Constants.CODE_DELETE == event.mKeyCode) { - final int length = mCombinedText.length(); - if (length > 0) { - final int lastCodePoint = mCombinedText.codePointBefore(length); - mCombinedText.delete(length - Character.charCount(lastCodePoint), length); - } - } else { - final CharSequence textToCommit = event.getTextToCommit(); - if (!TextUtils.isEmpty(textToCommit)) { - mCombinedText.append(textToCommit); - } - } - } - updateStateFeedback(); - } - - /** - * Get the char sequence that should be displayed as the composing word. It may include - * styling spans. - */ - public CharSequence getComposingWordWithCombiningFeedback() { - final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText); - return s.append(mStateFeedback); - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/CombinerChain.kt b/app/src/main/java/org/dslul/openboard/inputmethod/event/CombinerChain.kt new file mode 100644 index 000000000..0b38ea072 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/CombinerChain.kt @@ -0,0 +1,113 @@ +package org.dslul.openboard.inputmethod.event + +import android.text.SpannableStringBuilder +import android.text.TextUtils +import org.dslul.openboard.inputmethod.latin.common.Constants +import java.util.* + +/** + * This class implements the logic chain between receiving events and generating code points. + * + * Event sources are multiple. It may be a hardware keyboard, a D-PAD, a software keyboard, + * or any exotic input source. + * This class will orchestrate the composing chain that starts with an event as its input. Each + * composer will be given turns one after the other. + * The output is composed of two sequences of code points: the first, representing the already + * finished combining part, will be shown normally as the composing string, while the second is + * feedback on the composing state and will typically be shown with different styling such as + * a colored background. + */ +class CombinerChain(initialText: String?) { + // The already combined text, as described above + private val mCombinedText: StringBuilder + // The feedback on the composing state, as described above + private val mStateFeedback: SpannableStringBuilder + private val mCombiners: ArrayList + fun reset() { + mCombinedText.setLength(0) + mStateFeedback.clear() + for (c in mCombiners) { + c.reset() + } + } + + private fun updateStateFeedback() { + mStateFeedback.clear() + for (i in mCombiners.indices.reversed()) { + mStateFeedback.append(mCombiners[i].combiningStateFeedback) + } + } + + /** + * Process an event through the combining chain, and return a processed event to apply. + * @param previousEvents the list of previous events in this composition + * @param newEvent the new event to process + * @return the processed event. It may be the same event, or a consumed event, or a completely + * new event. However it may never be null. + */ + fun processEvent(previousEvents: ArrayList?, + newEvent: Event?): Event? { + val modifiablePreviousEvents = ArrayList(previousEvents!!) + var event = newEvent + for (combiner in mCombiners) { // A combiner can never return more than one event; it can return several +// code points, but they should be encapsulated within one event. + event = combiner.processEvent(modifiablePreviousEvents, event) + if (event!!.isConsumed) { // If the event is consumed, then we don't pass it to subsequent combiners: +// they should not see it at all. + break + } + } + updateStateFeedback() + return event + } + + /** + * Apply a processed event. + * @param event the event to be applied + */ + fun applyProcessedEvent(event: Event?) { + if (null != event) { // TODO: figure out the generic way of doing this + if (Constants.CODE_DELETE == event.mKeyCode) { + val length = mCombinedText.length + if (length > 0) { + val lastCodePoint = mCombinedText.codePointBefore(length) + mCombinedText.delete(length - Character.charCount(lastCodePoint), length) + } + } else { + val textToCommit = event.textToCommit + if (!TextUtils.isEmpty(textToCommit)) { + mCombinedText.append(textToCommit) + } + } + } + updateStateFeedback() + } + + /** + * Get the char sequence that should be displayed as the composing word. It may include + * styling spans. + */ + val composingWordWithCombiningFeedback: CharSequence + get() { + val s = SpannableStringBuilder(mCombinedText) + return s.append(mStateFeedback) + } + + /** + * Create an combiner chain. + * + * The combiner chain takes events as inputs and outputs code points and combining state. + * For example, if the input language is Japanese, the combining chain will typically perform + * kana conversion. This takes a string for initial text, taken to be present before the + * cursor: we'll start after this. + * + * @param initialText The text that has already been combined so far. + */ + init { + mCombiners = ArrayList() + // The dead key combiner is always active, and always first + mCombiners.add(DeadKeyCombiner()) + mCombinedText = StringBuilder(initialText!!) + mStateFeedback = SpannableStringBuilder() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/DeadKeyCombiner.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/DeadKeyCombiner.java deleted file mode 100644 index 7843e0668..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/DeadKeyCombiner.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dslul.openboard.inputmethod.event; - -import android.text.TextUtils; -import android.util.SparseIntArray; - -import org.dslul.openboard.inputmethod.latin.common.Constants; - -import java.text.Normalizer; -import java.util.ArrayList; - -import javax.annotation.Nonnull; - -/** - * A combiner that handles dead keys. - */ -public class DeadKeyCombiner implements Combiner { - - private static class Data { - // This class data taken from KeyCharacterMap.java. - - /* Characters used to display placeholders for dead keys. */ - private static final int ACCENT_ACUTE = '\u00B4'; - private static final int ACCENT_BREVE = '\u02D8'; - private static final int ACCENT_CARON = '\u02C7'; - private static final int ACCENT_CEDILLA = '\u00B8'; - private static final int ACCENT_CIRCUMFLEX = '\u02C6'; - private static final int ACCENT_COMMA_ABOVE = '\u1FBD'; - private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC'; - private static final int ACCENT_DOT_ABOVE = '\u02D9'; - private static final int ACCENT_DOT_BELOW = Constants.CODE_PERIOD; // approximate - private static final int ACCENT_DOUBLE_ACUTE = '\u02DD'; - private static final int ACCENT_GRAVE = '\u02CB'; - private static final int ACCENT_HOOK_ABOVE = '\u02C0'; - private static final int ACCENT_HORN = Constants.CODE_SINGLE_QUOTE; // approximate - private static final int ACCENT_MACRON = '\u00AF'; - private static final int ACCENT_MACRON_BELOW = '\u02CD'; - private static final int ACCENT_OGONEK = '\u02DB'; - private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD'; - private static final int ACCENT_RING_ABOVE = '\u02DA'; - private static final int ACCENT_STROKE = Constants.CODE_DASH; // approximate - private static final int ACCENT_TILDE = '\u02DC'; - private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB'; - private static final int ACCENT_UMLAUT = '\u00A8'; - private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8'; - private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC'; - - /* Legacy dead key display characters used in previous versions of the API (before L) - * We still support these characters by mapping them to their non-legacy version. */ - private static final int ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT; - private static final int ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT; - private static final int ACCENT_TILDE_LEGACY = Constants.CODE_TILDE; - - /** - * Maps Unicode combining diacritical to display-form dead key. - */ - static final SparseIntArray sCombiningToAccent = new SparseIntArray(); - static final SparseIntArray sAccentToCombining = new SparseIntArray(); - static { - // U+0300: COMBINING GRAVE ACCENT - addCombining('\u0300', ACCENT_GRAVE); - // U+0301: COMBINING ACUTE ACCENT - addCombining('\u0301', ACCENT_ACUTE); - // U+0302: COMBINING CIRCUMFLEX ACCENT - addCombining('\u0302', ACCENT_CIRCUMFLEX); - // U+0303: COMBINING TILDE - addCombining('\u0303', ACCENT_TILDE); - // U+0304: COMBINING MACRON - addCombining('\u0304', ACCENT_MACRON); - // U+0306: COMBINING BREVE - addCombining('\u0306', ACCENT_BREVE); - // U+0307: COMBINING DOT ABOVE - addCombining('\u0307', ACCENT_DOT_ABOVE); - // U+0308: COMBINING DIAERESIS - addCombining('\u0308', ACCENT_UMLAUT); - // U+0309: COMBINING HOOK ABOVE - addCombining('\u0309', ACCENT_HOOK_ABOVE); - // U+030A: COMBINING RING ABOVE - addCombining('\u030A', ACCENT_RING_ABOVE); - // U+030B: COMBINING DOUBLE ACUTE ACCENT - addCombining('\u030B', ACCENT_DOUBLE_ACUTE); - // U+030C: COMBINING CARON - addCombining('\u030C', ACCENT_CARON); - // U+030D: COMBINING VERTICAL LINE ABOVE - addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE); - // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE - //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE); - // U+030F: COMBINING DOUBLE GRAVE ACCENT - //addCombining('\u030F', ACCENT_DOUBLE_GRAVE); - // U+0310: COMBINING CANDRABINDU - //addCombining('\u0310', ACCENT_CANDRABINDU); - // U+0311: COMBINING INVERTED BREVE - //addCombining('\u0311', ACCENT_INVERTED_BREVE); - // U+0312: COMBINING TURNED COMMA ABOVE - addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE); - // U+0313: COMBINING COMMA ABOVE - addCombining('\u0313', ACCENT_COMMA_ABOVE); - // U+0314: COMBINING REVERSED COMMA ABOVE - addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE); - // U+0315: COMBINING COMMA ABOVE RIGHT - addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT); - // U+031B: COMBINING HORN - addCombining('\u031B', ACCENT_HORN); - // U+0323: COMBINING DOT BELOW - addCombining('\u0323', ACCENT_DOT_BELOW); - // U+0326: COMBINING COMMA BELOW - //addCombining('\u0326', ACCENT_COMMA_BELOW); - // U+0327: COMBINING CEDILLA - addCombining('\u0327', ACCENT_CEDILLA); - // U+0328: COMBINING OGONEK - addCombining('\u0328', ACCENT_OGONEK); - // U+0329: COMBINING VERTICAL LINE BELOW - addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW); - // U+0331: COMBINING MACRON BELOW - addCombining('\u0331', ACCENT_MACRON_BELOW); - // U+0335: COMBINING SHORT STROKE OVERLAY - addCombining('\u0335', ACCENT_STROKE); - // U+0342: COMBINING GREEK PERISPOMENI - //addCombining('\u0342', ACCENT_PERISPOMENI); - // U+0344: COMBINING GREEK DIALYTIKA TONOS - //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS); - // U+0345: COMBINING GREEK YPOGEGRAMMENI - //addCombining('\u0345', ACCENT_YPOGEGRAMMENI); - - // One-way mappings to equivalent preferred accents. - // U+0340: COMBINING GRAVE TONE MARK - sCombiningToAccent.append('\u0340', ACCENT_GRAVE); - // U+0341: COMBINING ACUTE TONE MARK - sCombiningToAccent.append('\u0341', ACCENT_ACUTE); - // U+0343: COMBINING GREEK KORONIS - sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE); - - // One-way legacy mappings to preserve compatibility with older applications. - // U+0300: COMBINING GRAVE ACCENT - sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300'); - // U+0302: COMBINING CIRCUMFLEX ACCENT - sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302'); - // U+0303: COMBINING TILDE - sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303'); - } - - private static void addCombining(int combining, int accent) { - sCombiningToAccent.append(combining, accent); - sAccentToCombining.append(accent, combining); - } - - // Caution! This may only contain chars, not supplementary code points. It's unlikely - // it will ever need to, but if it does we'll have to change this - private static final SparseIntArray sNonstandardDeadCombinations = new SparseIntArray(); - static { - // Non-standard decompositions. - // Stroke modifier for Finnish multilingual keyboard and others. - // U+0110: LATIN CAPITAL LETTER D WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'D', '\u0110'); - // U+01E4: LATIN CAPITAL LETTER G WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'G', '\u01e4'); - // U+0126: LATIN CAPITAL LETTER H WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'H', '\u0126'); - // U+0197: LATIN CAPITAL LETTER I WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'I', '\u0197'); - // U+0141: LATIN CAPITAL LETTER L WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'L', '\u0141'); - // U+00D8: LATIN CAPITAL LETTER O WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'O', '\u00d8'); - // U+0166: LATIN CAPITAL LETTER T WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'T', '\u0166'); - // U+0111: LATIN SMALL LETTER D WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'd', '\u0111'); - // U+01E5: LATIN SMALL LETTER G WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'g', '\u01e5'); - // U+0127: LATIN SMALL LETTER H WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'h', '\u0127'); - // U+0268: LATIN SMALL LETTER I WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'i', '\u0268'); - // U+0142: LATIN SMALL LETTER L WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'l', '\u0142'); - // U+00F8: LATIN SMALL LETTER O WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 'o', '\u00f8'); - // U+0167: LATIN SMALL LETTER T WITH STROKE - addNonStandardDeadCombination(ACCENT_STROKE, 't', '\u0167'); - } - - private static void addNonStandardDeadCombination(final int deadCodePoint, - final int spacingCodePoint, final int result) { - final int combination = (deadCodePoint << 16) | spacingCodePoint; - sNonstandardDeadCombinations.put(combination, result); - } - - public static final int NOT_A_CHAR = 0; - public static final int BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16; - // Get a non-standard combination - public static char getNonstandardCombination(final int deadCodePoint, - final int spacingCodePoint) { - final int combination = spacingCodePoint | - (deadCodePoint << BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION); - return (char)sNonstandardDeadCombinations.get(combination, NOT_A_CHAR); - } - } - - // TODO: make this a list of events instead - final StringBuilder mDeadSequence = new StringBuilder(); - - @Nonnull - private static Event createEventChainFromSequence(final @Nonnull CharSequence text, - @Nonnull final Event originalEvent) { - int index = text.length(); - if (index <= 0) { - return originalEvent; - } - Event lastEvent = null; - do { - final int codePoint = Character.codePointBefore(text, index); - lastEvent = Event.createHardwareKeypressEvent(codePoint, - originalEvent.mKeyCode, lastEvent, false /* isKeyRepeat */); - index -= Character.charCount(codePoint); - } while (index > 0); - return lastEvent; - } - - @Override - @Nonnull - public Event processEvent(final ArrayList previousEvents, final Event event) { - if (TextUtils.isEmpty(mDeadSequence)) { - // No dead char is currently being tracked: this is the most common case. - if (event.isDead()) { - // The event was a dead key. Start tracking it. - mDeadSequence.appendCodePoint(event.mCodePoint); - return Event.createConsumedEvent(event); - } - // Regular keystroke when not keeping track of a dead key. Simply said, there are - // no dead keys at all in the current input, so this combiner has nothing to do and - // simply returns the event as is. The majority of events will go through this path. - return event; - } - if (Character.isWhitespace(event.mCodePoint) - || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length())) { - // When whitespace or twice the same dead key, we should output the dead sequence as is. - final Event resultEvent = createEventChainFromSequence(mDeadSequence.toString(), - event); - mDeadSequence.setLength(0); - return resultEvent; - } - if (event.isFunctionalKeyEvent()) { - if (Constants.CODE_DELETE == event.mKeyCode) { - // Remove the last code point - final int trimIndex = mDeadSequence.length() - Character.charCount( - mDeadSequence.codePointBefore(mDeadSequence.length())); - mDeadSequence.setLength(trimIndex); - return Event.createConsumedEvent(event); - } - return event; - } - if (event.isDead()) { - mDeadSequence.appendCodePoint(event.mCodePoint); - return Event.createConsumedEvent(event); - } - // Combine normally. - final StringBuilder sb = new StringBuilder(); - sb.appendCodePoint(event.mCodePoint); - int codePointIndex = 0; - while (codePointIndex < mDeadSequence.length()) { - final int deadCodePoint = mDeadSequence.codePointAt(codePointIndex); - final char replacementSpacingChar = - Data.getNonstandardCombination(deadCodePoint, event.mCodePoint); - if (Data.NOT_A_CHAR != replacementSpacingChar) { - sb.setCharAt(0, replacementSpacingChar); - } else { - final int combining = Data.sAccentToCombining.get(deadCodePoint); - sb.appendCodePoint(0 == combining ? deadCodePoint : combining); - } - codePointIndex += Character.isSupplementaryCodePoint(deadCodePoint) ? 2 : 1; - } - final String normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC); - final Event resultEvent = createEventChainFromSequence(normalizedString, event); - mDeadSequence.setLength(0); - return resultEvent; - } - - @Override - public void reset() { - mDeadSequence.setLength(0); - } - - @Override - public CharSequence getCombiningStateFeedback() { - return mDeadSequence; - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/DeadKeyCombiner.kt b/app/src/main/java/org/dslul/openboard/inputmethod/event/DeadKeyCombiner.kt new file mode 100644 index 000000000..9b3ad3821 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/DeadKeyCombiner.kt @@ -0,0 +1,267 @@ +package org.dslul.openboard.inputmethod.event + +import android.text.TextUtils +import android.util.SparseIntArray +import org.dslul.openboard.inputmethod.latin.common.Constants +import java.text.Normalizer +import java.util.* + +/** + * A combiner that handles dead keys. + */ +class DeadKeyCombiner : Combiner { + private object Data { + // This class data taken from KeyCharacterMap.java. +/* Characters used to display placeholders for dead keys. */ + private const val ACCENT_ACUTE = '\u00B4'.toInt() + private const val ACCENT_BREVE = '\u02D8'.toInt() + private const val ACCENT_CARON = '\u02C7'.toInt() + private const val ACCENT_CEDILLA = '\u00B8'.toInt() + private const val ACCENT_CIRCUMFLEX = '\u02C6'.toInt() + private const val ACCENT_COMMA_ABOVE = '\u1FBD'.toInt() + private const val ACCENT_COMMA_ABOVE_RIGHT = '\u02BC'.toInt() + private const val ACCENT_DOT_ABOVE = '\u02D9'.toInt() + private const val ACCENT_DOT_BELOW = Constants.CODE_PERIOD // approximate + private const val ACCENT_DOUBLE_ACUTE = '\u02DD'.toInt() + private const val ACCENT_GRAVE = '\u02CB'.toInt() + private const val ACCENT_HOOK_ABOVE = '\u02C0'.toInt() + private const val ACCENT_HORN = Constants.CODE_SINGLE_QUOTE // approximate + private const val ACCENT_MACRON = '\u00AF'.toInt() + private const val ACCENT_MACRON_BELOW = '\u02CD'.toInt() + private const val ACCENT_OGONEK = '\u02DB'.toInt() + private const val ACCENT_REVERSED_COMMA_ABOVE = '\u02BD'.toInt() + private const val ACCENT_RING_ABOVE = '\u02DA'.toInt() + private const val ACCENT_STROKE = Constants.CODE_DASH // approximate + private const val ACCENT_TILDE = '\u02DC'.toInt() + private const val ACCENT_TURNED_COMMA_ABOVE = '\u02BB'.toInt() + private const val ACCENT_UMLAUT = '\u00A8'.toInt() + private const val ACCENT_VERTICAL_LINE_ABOVE = '\u02C8'.toInt() + private const val ACCENT_VERTICAL_LINE_BELOW = '\u02CC'.toInt() + /* Legacy dead key display characters used in previous versions of the API (before L) + * We still support these characters by mapping them to their non-legacy version. */ + private const val ACCENT_GRAVE_LEGACY = Constants.CODE_GRAVE_ACCENT + private const val ACCENT_CIRCUMFLEX_LEGACY = Constants.CODE_CIRCUMFLEX_ACCENT + private const val ACCENT_TILDE_LEGACY = Constants.CODE_TILDE + /** + * Maps Unicode combining diacritical to display-form dead key. + */ + val sCombiningToAccent = SparseIntArray() + val sAccentToCombining = SparseIntArray() + private fun addCombining(combining: Int, accent: Int) { + sCombiningToAccent.append(combining, accent) + sAccentToCombining.append(accent, combining) + } + + // Caution! This may only contain chars, not supplementary code points. It's unlikely +// it will ever need to, but if it does we'll have to change this + private val sNonstandardDeadCombinations = SparseIntArray() + + private fun addNonStandardDeadCombination(deadCodePoint: Int, + spacingCodePoint: Int, result: Int) { + val combination = deadCodePoint shl 16 or spacingCodePoint + sNonstandardDeadCombinations.put(combination, result) + } + + const val NOT_A_CHAR = 0 + const val BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION = 16 + // Get a non-standard combination + fun getNonstandardCombination(deadCodePoint: Int, + spacingCodePoint: Int): Char { + val combination = spacingCodePoint or + (deadCodePoint shl BITS_TO_SHIFT_DEAD_CODE_POINT_FOR_NON_STANDARD_COMBINATION) + return sNonstandardDeadCombinations[combination, NOT_A_CHAR].toChar() + } + + init { // U+0300: COMBINING GRAVE ACCENT + addCombining('\u0300'.toInt(), ACCENT_GRAVE) + // U+0301: COMBINING ACUTE ACCENT + addCombining('\u0301'.toInt(), ACCENT_ACUTE) + // U+0302: COMBINING CIRCUMFLEX ACCENT + addCombining('\u0302'.toInt(), ACCENT_CIRCUMFLEX) + // U+0303: COMBINING TILDE + addCombining('\u0303'.toInt(), ACCENT_TILDE) + // U+0304: COMBINING MACRON + addCombining('\u0304'.toInt(), ACCENT_MACRON) + // U+0306: COMBINING BREVE + addCombining('\u0306'.toInt(), ACCENT_BREVE) + // U+0307: COMBINING DOT ABOVE + addCombining('\u0307'.toInt(), ACCENT_DOT_ABOVE) + // U+0308: COMBINING DIAERESIS + addCombining('\u0308'.toInt(), ACCENT_UMLAUT) + // U+0309: COMBINING HOOK ABOVE + addCombining('\u0309'.toInt(), ACCENT_HOOK_ABOVE) + // U+030A: COMBINING RING ABOVE + addCombining('\u030A'.toInt(), ACCENT_RING_ABOVE) + // U+030B: COMBINING DOUBLE ACUTE ACCENT + addCombining('\u030B'.toInt(), ACCENT_DOUBLE_ACUTE) + // U+030C: COMBINING CARON + addCombining('\u030C'.toInt(), ACCENT_CARON) + // U+030D: COMBINING VERTICAL LINE ABOVE + addCombining('\u030D'.toInt(), ACCENT_VERTICAL_LINE_ABOVE) + // U+030E: COMBINING DOUBLE VERTICAL LINE ABOVE +//addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE); +// U+030F: COMBINING DOUBLE GRAVE ACCENT +//addCombining('\u030F', ACCENT_DOUBLE_GRAVE); +// U+0310: COMBINING CANDRABINDU +//addCombining('\u0310', ACCENT_CANDRABINDU); +// U+0311: COMBINING INVERTED BREVE +//addCombining('\u0311', ACCENT_INVERTED_BREVE); +// U+0312: COMBINING TURNED COMMA ABOVE + addCombining('\u0312'.toInt(), ACCENT_TURNED_COMMA_ABOVE) + // U+0313: COMBINING COMMA ABOVE + addCombining('\u0313'.toInt(), ACCENT_COMMA_ABOVE) + // U+0314: COMBINING REVERSED COMMA ABOVE + addCombining('\u0314'.toInt(), ACCENT_REVERSED_COMMA_ABOVE) + // U+0315: COMBINING COMMA ABOVE RIGHT + addCombining('\u0315'.toInt(), ACCENT_COMMA_ABOVE_RIGHT) + // U+031B: COMBINING HORN + addCombining('\u031B'.toInt(), ACCENT_HORN) + // U+0323: COMBINING DOT BELOW + addCombining('\u0323'.toInt(), ACCENT_DOT_BELOW) + // U+0326: COMBINING COMMA BELOW +//addCombining('\u0326', ACCENT_COMMA_BELOW); +// U+0327: COMBINING CEDILLA + addCombining('\u0327'.toInt(), ACCENT_CEDILLA) + // U+0328: COMBINING OGONEK + addCombining('\u0328'.toInt(), ACCENT_OGONEK) + // U+0329: COMBINING VERTICAL LINE BELOW + addCombining('\u0329'.toInt(), ACCENT_VERTICAL_LINE_BELOW) + // U+0331: COMBINING MACRON BELOW + addCombining('\u0331'.toInt(), ACCENT_MACRON_BELOW) + // U+0335: COMBINING SHORT STROKE OVERLAY + addCombining('\u0335'.toInt(), ACCENT_STROKE) + // U+0342: COMBINING GREEK PERISPOMENI +//addCombining('\u0342', ACCENT_PERISPOMENI); +// U+0344: COMBINING GREEK DIALYTIKA TONOS +//addCombining('\u0344', ACCENT_DIALYTIKA_TONOS); +// U+0345: COMBINING GREEK YPOGEGRAMMENI +//addCombining('\u0345', ACCENT_YPOGEGRAMMENI); +// One-way mappings to equivalent preferred accents. +// U+0340: COMBINING GRAVE TONE MARK + sCombiningToAccent.append('\u0340'.toInt(), ACCENT_GRAVE) + // U+0341: COMBINING ACUTE TONE MARK + sCombiningToAccent.append('\u0341'.toInt(), ACCENT_ACUTE) + // U+0343: COMBINING GREEK KORONIS + sCombiningToAccent.append('\u0343'.toInt(), ACCENT_COMMA_ABOVE) + // One-way legacy mappings to preserve compatibility with older applications. +// U+0300: COMBINING GRAVE ACCENT + sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300'.toInt()) + // U+0302: COMBINING CIRCUMFLEX ACCENT + sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302'.toInt()) + // U+0303: COMBINING TILDE + sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303'.toInt()) + } + + init { // Non-standard decompositions. +// Stroke modifier for Finnish multilingual keyboard and others. +// U+0110: LATIN CAPITAL LETTER D WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'D'.toInt(), '\u0110'.toInt()) + // U+01E4: LATIN CAPITAL LETTER G WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'G'.toInt(), '\u01e4'.toInt()) + // U+0126: LATIN CAPITAL LETTER H WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'H'.toInt(), '\u0126'.toInt()) + // U+0197: LATIN CAPITAL LETTER I WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'I'.toInt(), '\u0197'.toInt()) + // U+0141: LATIN CAPITAL LETTER L WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'L'.toInt(), '\u0141'.toInt()) + // U+00D8: LATIN CAPITAL LETTER O WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'O'.toInt(), '\u00d8'.toInt()) + // U+0166: LATIN CAPITAL LETTER T WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'T'.toInt(), '\u0166'.toInt()) + // U+0111: LATIN SMALL LETTER D WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'd'.toInt(), '\u0111'.toInt()) + // U+01E5: LATIN SMALL LETTER G WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'g'.toInt(), '\u01e5'.toInt()) + // U+0127: LATIN SMALL LETTER H WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'h'.toInt(), '\u0127'.toInt()) + // U+0268: LATIN SMALL LETTER I WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'i'.toInt(), '\u0268'.toInt()) + // U+0142: LATIN SMALL LETTER L WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'l'.toInt(), '\u0142'.toInt()) + // U+00F8: LATIN SMALL LETTER O WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 'o'.toInt(), '\u00f8'.toInt()) + // U+0167: LATIN SMALL LETTER T WITH STROKE + addNonStandardDeadCombination(ACCENT_STROKE, 't'.toInt(), '\u0167'.toInt()) + } + } + + // TODO: make this a list of events instead + val mDeadSequence = StringBuilder() + + override fun processEvent(previousEvents: ArrayList?, event: Event?): Event? { + if (TextUtils.isEmpty(mDeadSequence)) { // No dead char is currently being tracked: this is the most common case. + if (event!!.isDead) { // The event was a dead key. Start tracking it. + mDeadSequence.appendCodePoint(event.mCodePoint) + return Event.Companion.createConsumedEvent(event) + } + // Regular keystroke when not keeping track of a dead key. Simply said, there are +// no dead keys at all in the current input, so this combiner has nothing to do and +// simply returns the event as is. The majority of events will go through this path. + return event + } + if (Character.isWhitespace(event!!.mCodePoint) + || event.mCodePoint == mDeadSequence.codePointBefore(mDeadSequence.length)) { // When whitespace or twice the same dead key, we should output the dead sequence as is. + val resultEvent = createEventChainFromSequence(mDeadSequence.toString(), + event) + mDeadSequence.setLength(0) + return resultEvent + } + if (event.isFunctionalKeyEvent) { + if (Constants.CODE_DELETE == event.mKeyCode) { // Remove the last code point + val trimIndex = mDeadSequence.length - Character.charCount( + mDeadSequence.codePointBefore(mDeadSequence.length)) + mDeadSequence.setLength(trimIndex) + return Event.Companion.createConsumedEvent(event) + } + return event + } + if (event.isDead) { + mDeadSequence.appendCodePoint(event.mCodePoint) + return Event.Companion.createConsumedEvent(event) + } + // Combine normally. + val sb = StringBuilder() + sb.appendCodePoint(event.mCodePoint) + var codePointIndex = 0 + while (codePointIndex < mDeadSequence.length) { + val deadCodePoint = mDeadSequence.codePointAt(codePointIndex) + val replacementSpacingChar = Data.getNonstandardCombination(deadCodePoint, event.mCodePoint) + if (Data.NOT_A_CHAR != replacementSpacingChar.toInt()) { + sb.setCharAt(0, replacementSpacingChar) + } else { + val combining = Data.sAccentToCombining[deadCodePoint] + sb.appendCodePoint(if (0 == combining) deadCodePoint else combining) + } + codePointIndex += if (Character.isSupplementaryCodePoint(deadCodePoint)) 2 else 1 + } + val normalizedString = Normalizer.normalize(sb, Normalizer.Form.NFC) + val resultEvent = createEventChainFromSequence(normalizedString, event) + mDeadSequence.setLength(0) + return resultEvent + } + + override fun reset() { + mDeadSequence.setLength(0) + } + + override val combiningStateFeedback: CharSequence + get() = mDeadSequence + + companion object { + private fun createEventChainFromSequence(text: CharSequence, + originalEvent: Event?): Event? { + var index = text.length + if (index <= 0) { + return originalEvent + } + var lastEvent: Event? = null + do { + val codePoint = Character.codePointBefore(text, index) + lastEvent = Event.Companion.createHardwareKeypressEvent(codePoint, + originalEvent!!.mKeyCode, lastEvent, false /* isKeyRepeat */) + index -= Character.charCount(codePoint) + } while (index > 0) + return lastEvent + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/Event.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/Event.java deleted file mode 100644 index 4cfcd23bf..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/Event.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dslul.openboard.inputmethod.event; - -import org.dslul.openboard.inputmethod.annotations.ExternallyReferenced; -import org.dslul.openboard.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import org.dslul.openboard.inputmethod.latin.common.Constants; -import org.dslul.openboard.inputmethod.latin.common.StringUtils; - -import javax.annotation.Nonnull; - -/** - * Class representing a generic input event as handled by Latin IME. - * - * This contains information about the origin of the event, but it is generalized and should - * represent a software keypress, hardware keypress, or d-pad move alike. - * Very importantly, this does not necessarily result in inputting one character, or even anything - * at all - it may be a dead key, it may be a partial input, it may be a special key on the - * keyboard, it may be a cancellation of a keypress (e.g. in a soft keyboard the finger of the - * user has slid out of the key), etc. It may also be a batch input from a gesture or handwriting - * for example. - * The combiner should figure out what to do with this. - */ -public class Event { - // Should the types below be represented by separate classes instead? It would be cleaner - // but probably a bit too much - // An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard. - final public static int EVENT_TYPE_NOT_HANDLED = 0; - // A key press that is part of input, for example pressing an alphabetic character on a - // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later - // through combination. - final public static int EVENT_TYPE_INPUT_KEYPRESS = 1; - // A toggle event is triggered by a key that affects the previous character. An example would - // be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with - // repeated presses. - final public static int EVENT_TYPE_TOGGLE = 2; - // A mode event instructs the combiner to change modes. The canonical example would be the - // hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard - // if handled at the combiner level. - final public static int EVENT_TYPE_MODE_KEY = 3; - // An event corresponding to a gesture. - final public static int EVENT_TYPE_GESTURE = 4; - // An event corresponding to the manual pick of a suggestion. - final public static int EVENT_TYPE_SUGGESTION_PICKED = 5; - // An event corresponding to a string generated by some software process. - final public static int EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6; - // An event corresponding to a cursor move - final public static int EVENT_TYPE_CURSOR_MOVE = 7; - - // 0 is a valid code point, so we use -1 here. - final public static int NOT_A_CODE_POINT = -1; - // -1 is a valid key code, so we use 0 here. - final public static int NOT_A_KEY_CODE = 0; - - final private static int FLAG_NONE = 0; - // This event is a dead character, usually input by a dead key. Examples include dead-acute - // or dead-abovering. - final private static int FLAG_DEAD = 0x1; - // This event is coming from a key repeat, software or hardware. - final private static int FLAG_REPEAT = 0x2; - // This event has already been consumed. - final private static int FLAG_CONSUMED = 0x4; - - final private int mEventType; // The type of event - one of the constants above - // The code point associated with the event, if relevant. This is a unicode code point, and - // has nothing to do with other representations of the key. It is only relevant if this event - // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point - // associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when - // it's not relevant. - final public int mCodePoint; - - // If applicable, this contains the string that should be input. - final public CharSequence mText; - - // The key code associated with the event, if relevant. This is relevant whenever this event - // has been triggered by a key press, but not for a gesture for example. This has conceptually - // no link to the code point, although keys that enter a straight code point may often set - // this to be equal to mCodePoint for convenience. If this is not a key, this must contain - // NOT_A_KEY_CODE. - final public int mKeyCode; - - // Coordinates of the touch event, if relevant. If useful, we may want to replace this with - // a MotionEvent or something in the future. This is only relevant when the keypress is from - // a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the - // future or some other awesome sauce. - final public int mX; - final public int mY; - - // Some flags that can't go into the key code. It's a bit field of FLAG_* - final private int mFlags; - - // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in - // other cases). - final public SuggestedWordInfo mSuggestedWordInfo; - - // The next event, if any. Null if there is no next event yet. - final public Event mNextEvent; - - // This method is private - to create a new event, use one of the create* utility methods. - private Event(final int type, final CharSequence text, final int codePoint, final int keyCode, - final int x, final int y, final SuggestedWordInfo suggestedWordInfo, final int flags, - final Event next) { - mEventType = type; - mText = text; - mCodePoint = codePoint; - mKeyCode = keyCode; - mX = x; - mY = y; - mSuggestedWordInfo = suggestedWordInfo; - mFlags = flags; - mNextEvent = next; - // Sanity checks - // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED - if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) { - if (null == mSuggestedWordInfo) { - throw new RuntimeException("Wrong event: SUGGESTION_PICKED event must have a " - + "non-null SuggestedWordInfo"); - } - } else { - if (null != mSuggestedWordInfo) { - throw new RuntimeException("Wrong event: only SUGGESTION_PICKED events may have " + - "a non-null SuggestedWordInfo"); - } - } - } - - @Nonnull - public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode, - final int x, final int y, final boolean isKeyRepeat) { - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y, - null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, null); - } - - @Nonnull - public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode, - final Event next, final boolean isKeyRepeat) { - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, - Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, - null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, next); - } - - // This creates an input event for a dead character. @see {@link #FLAG_DEAD} - @ExternallyReferenced - @Nonnull - public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) { - // TODO: add an argument or something if we ever create a software layout with dead keys. - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, - Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, - null /* suggestedWordInfo */, FLAG_DEAD, next); - } - - /** - * Create an input event with nothing but a code point. This is the most basic possible input - * event; it contains no information on many things the IME requires to function correctly, - * so avoid using it unless really nothing is known about this input. - * @param codePoint the code point. - * @return an event for this code point. - */ - @Nonnull - public static Event createEventForCodePointFromUnknownSource(final int codePoint) { - // TODO: should we have a different type of event for this? After all, it's not a key press. - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, - null /* suggestedWordInfo */, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event with a code point and x, y coordinates. This is typically used when - * resuming a previously-typed word, when the coordinates are still known. - * @param codePoint the code point to input. - * @param x the X coordinate. - * @param y the Y coordinate. - * @return an event for this code point and coordinates. - */ - @Nonnull - public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint, - final int x, final int y) { - // TODO: should we have a different type of event for this? After all, it's not a key press. - return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, - x, y, null /* suggestedWordInfo */, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event representing the manual pick of a suggestion. - * @return an event for this suggestion pick. - */ - @Nonnull - public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) { - return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, - NOT_A_CODE_POINT, NOT_A_KEY_CODE, - Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, - suggestedWordInfo, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event with a CharSequence. This is used by some software processes whose - * output is a string, possibly with styling. Examples include press on a multi-character key, - * or combination that outputs a string. - * @param text the CharSequence associated with this event. - * @param keyCode the key code, or NOT_A_KEYCODE if not applicable. - * @return an event for this text. - */ - @Nonnull - public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) { - return new Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, - null /* suggestedWordInfo */, FLAG_NONE, null /* next */); - } - - /** - * Creates an input event representing the manual pick of a punctuation suggestion. - * @return an event for this suggestion pick. - */ - @Nonnull - public static Event createPunctuationSuggestionPickedEvent( - final SuggestedWordInfo suggestedWordInfo) { - final int primaryCode = suggestedWordInfo.mWord.charAt(0); - return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode, - NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE, - Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE, - null /* next */); - } - - /** - * Creates an input event representing moving the cursor. The relative move amount is stored - * in mX. - * @param moveAmount the relative move amount. - * @return an event for this cursor move. - */ - @Nonnull - public static Event createCursorMovedEvent(final int moveAmount) { - return new Event(EVENT_TYPE_CURSOR_MOVE, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, - moveAmount, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null); - } - - /** - * Creates an event identical to the passed event, but that has already been consumed. - * @param source the event to copy the properties of. - * @return an identical event marked as consumed. - */ - @Nonnull - public static Event createConsumedEvent(final Event source) { - // A consumed event should not input any text at all, so we pass the empty string as text. - return new Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode, - source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags | FLAG_CONSUMED, - source.mNextEvent); - } - - @Nonnull - public static Event createNotHandledEvent() { - return new Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, - null /* suggestedWordInfo */, FLAG_NONE, null); - } - - // Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys - // that result in input like letters or space. - public boolean isFunctionalKeyEvent() { - // This logic may need to be refined in the future - return NOT_A_CODE_POINT == mCodePoint; - } - - // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD} - public boolean isDead() { - return 0 != (FLAG_DEAD & mFlags); - } - - public boolean isKeyRepeat() { - return 0 != (FLAG_REPEAT & mFlags); - } - - public boolean isConsumed() { return 0 != (FLAG_CONSUMED & mFlags); } - - public boolean isGesture() { return EVENT_TYPE_GESTURE == mEventType; } - - // Returns whether this is a fake key press from the suggestion strip. This happens with - // punctuation signs selected from the suggestion strip. - public boolean isSuggestionStripPress() { - return EVENT_TYPE_SUGGESTION_PICKED == mEventType; - } - - public boolean isHandled() { - return EVENT_TYPE_NOT_HANDLED != mEventType; - } - - public CharSequence getTextToCommit() { - if (isConsumed()) { - return ""; // A consumed event should input no text. - } - switch (mEventType) { - case EVENT_TYPE_MODE_KEY: - case EVENT_TYPE_NOT_HANDLED: - case EVENT_TYPE_TOGGLE: - case EVENT_TYPE_CURSOR_MOVE: - return ""; - case EVENT_TYPE_INPUT_KEYPRESS: - return StringUtils.newSingleCodePointString(mCodePoint); - case EVENT_TYPE_GESTURE: - case EVENT_TYPE_SOFTWARE_GENERATED_STRING: - case EVENT_TYPE_SUGGESTION_PICKED: - return mText; - } - throw new RuntimeException("Unknown event type: " + mEventType); - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/Event.kt b/app/src/main/java/org/dslul/openboard/inputmethod/event/Event.kt new file mode 100644 index 000000000..ba0563794 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/Event.kt @@ -0,0 +1,264 @@ +package org.dslul.openboard.inputmethod.event + +import org.dslul.openboard.inputmethod.annotations.ExternallyReferenced +import org.dslul.openboard.inputmethod.latin.SuggestedWords.SuggestedWordInfo +import org.dslul.openboard.inputmethod.latin.common.Constants +import org.dslul.openboard.inputmethod.latin.common.StringUtils + +/** + * Class representing a generic input event as handled by Latin IME. + * + * This contains information about the origin of the event, but it is generalized and should + * represent a software keypress, hardware keypress, or d-pad move alike. + * Very importantly, this does not necessarily result in inputting one character, or even anything + * at all - it may be a dead key, it may be a partial input, it may be a special key on the + * keyboard, it may be a cancellation of a keypress (e.g. in a soft keyboard the finger of the + * user has slid out of the key), etc. It may also be a batch input from a gesture or handwriting + * for example. + * The combiner should figure out what to do with this. + */ +class Event private constructor(// The type of event - one of the constants above + private val mEventType: Int, // If applicable, this contains the string that should be input. + val mText: CharSequence?, // The code point associated with the event, if relevant. This is a unicode code point, and +// has nothing to do with other representations of the key. It is only relevant if this event +// is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point +// associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when +// it's not relevant. + val mCodePoint: Int, // The key code associated with the event, if relevant. This is relevant whenever this event +// has been triggered by a key press, but not for a gesture for example. This has conceptually +// no link to the code point, although keys that enter a straight code point may often set +// this to be equal to mCodePoint for convenience. If this is not a key, this must contain +// NOT_A_KEY_CODE. + val mKeyCode: Int, + // Coordinates of the touch event, if relevant. If useful, we may want to replace this with +// a MotionEvent or something in the future. This is only relevant when the keypress is from +// a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the +// future or some other awesome sauce. + val mX: Int, val mY: Int, + // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in +// other cases). + val mSuggestedWordInfo: SuggestedWordInfo?, + // Some flags that can't go into the key code. It's a bit field of FLAG_* + private val mFlags: Int, + // The next event, if any. Null if there is no next event yet. + val mNextEvent: Event?// This logic may need to be refined in the future +) { + + // Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys +// that result in input like letters or space. + val isFunctionalKeyEvent: Boolean + get() =// This logic may need to be refined in the future + NOT_A_CODE_POINT == mCodePoint + + // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD} + val isDead: Boolean + get() = 0 != FLAG_DEAD and mFlags + + val isKeyRepeat: Boolean + get() = 0 != FLAG_REPEAT and mFlags + + val isConsumed: Boolean + get() = 0 != FLAG_CONSUMED and mFlags + + val isGesture: Boolean + get() = EVENT_TYPE_GESTURE == mEventType + + // Returns whether this is a fake key press from the suggestion strip. This happens with +// punctuation signs selected from the suggestion strip. + val isSuggestionStripPress: Boolean + get() = EVENT_TYPE_SUGGESTION_PICKED == mEventType + + val isHandled: Boolean + get() = EVENT_TYPE_NOT_HANDLED != mEventType + + // A consumed event should input no text. + val textToCommit: CharSequence? + get() { + if (isConsumed) { + return "" // A consumed event should input no text. + } + when (mEventType) { + EVENT_TYPE_MODE_KEY, EVENT_TYPE_NOT_HANDLED, EVENT_TYPE_TOGGLE, EVENT_TYPE_CURSOR_MOVE -> return "" + EVENT_TYPE_INPUT_KEYPRESS -> return StringUtils.newSingleCodePointString(mCodePoint) + EVENT_TYPE_GESTURE, EVENT_TYPE_SOFTWARE_GENERATED_STRING, EVENT_TYPE_SUGGESTION_PICKED -> return mText + } + throw RuntimeException("Unknown event type: $mEventType") + } + + companion object { + // Should the types below be represented by separate classes instead? It would be cleaner +// but probably a bit too much +// An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard. + const val EVENT_TYPE_NOT_HANDLED = 0 + // A key press that is part of input, for example pressing an alphabetic character on a +// hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later +// through combination. + const val EVENT_TYPE_INPUT_KEYPRESS = 1 + // A toggle event is triggered by a key that affects the previous character. An example would +// be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with +// repeated presses. + const val EVENT_TYPE_TOGGLE = 2 + // A mode event instructs the combiner to change modes. The canonical example would be the +// hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard +// if handled at the combiner level. + const val EVENT_TYPE_MODE_KEY = 3 + // An event corresponding to a gesture. + const val EVENT_TYPE_GESTURE = 4 + // An event corresponding to the manual pick of a suggestion. + const val EVENT_TYPE_SUGGESTION_PICKED = 5 + // An event corresponding to a string generated by some software process. + const val EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6 + // An event corresponding to a cursor move + const val EVENT_TYPE_CURSOR_MOVE = 7 + // 0 is a valid code point, so we use -1 here. + const val NOT_A_CODE_POINT = -1 + // -1 is a valid key code, so we use 0 here. + const val NOT_A_KEY_CODE = 0 + private const val FLAG_NONE = 0 + // This event is a dead character, usually input by a dead key. Examples include dead-acute +// or dead-abovering. + private const val FLAG_DEAD = 0x1 + // This event is coming from a key repeat, software or hardware. + private const val FLAG_REPEAT = 0x2 + // This event has already been consumed. + private const val FLAG_CONSUMED = 0x4 + + @JvmStatic + fun createSoftwareKeypressEvent(codePoint: Int, keyCode: Int, + x: Int, y: Int, isKeyRepeat: Boolean): Event { + return Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y, + null /* suggestedWordInfo */, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, null) + } + + fun createHardwareKeypressEvent(codePoint: Int, keyCode: Int, + next: Event?, isKeyRepeat: Boolean): Event { + return Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, + Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, + null /* suggestedWordInfo */, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, next) + } + + // This creates an input event for a dead character. @see {@link #FLAG_DEAD} + @ExternallyReferenced + fun createDeadEvent(codePoint: Int, keyCode: Int, next: Event?): Event { // TODO: add an argument or something if we ever create a software layout with dead keys. + return Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, + Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, + null /* suggestedWordInfo */, FLAG_DEAD, next) + } + + /** + * Create an input event with nothing but a code point. This is the most basic possible input + * event; it contains no information on many things the IME requires to function correctly, + * so avoid using it unless really nothing is known about this input. + * @param codePoint the code point. + * @return an event for this code point. + */ + @JvmStatic + fun createEventForCodePointFromUnknownSource(codePoint: Int): Event { // TODO: should we have a different type of event for this? After all, it's not a key press. + return Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, + null /* suggestedWordInfo */, FLAG_NONE, null /* next */) + } + + /** + * Creates an input event with a code point and x, y coordinates. This is typically used when + * resuming a previously-typed word, when the coordinates are still known. + * @param codePoint the code point to input. + * @param x the X coordinate. + * @param y the Y coordinate. + * @return an event for this code point and coordinates. + */ + @JvmStatic + fun createEventForCodePointFromAlreadyTypedText(codePoint: Int, + x: Int, y: Int): Event { // TODO: should we have a different type of event for this? After all, it's not a key press. + return Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, + x, y, null /* suggestedWordInfo */, FLAG_NONE, null /* next */) + } + + /** + * Creates an input event representing the manual pick of a suggestion. + * @return an event for this suggestion pick. + */ + @JvmStatic + fun createSuggestionPickedEvent(suggestedWordInfo: SuggestedWordInfo): Event { + return Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, + NOT_A_CODE_POINT, NOT_A_KEY_CODE, + Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, + suggestedWordInfo, FLAG_NONE, null /* next */) + } + + /** + * Creates an input event with a CharSequence. This is used by some software processes whose + * output is a string, possibly with styling. Examples include press on a multi-character key, + * or combination that outputs a string. + * @param text the CharSequence associated with this event. + * @param keyCode the key code, or NOT_A_KEYCODE if not applicable. + * @return an event for this text. + */ + @JvmStatic + fun createSoftwareTextEvent(text: CharSequence?, keyCode: Int): Event { + return Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, + null /* suggestedWordInfo */, FLAG_NONE, null /* next */) + } + + /** + * Creates an input event representing the manual pick of a punctuation suggestion. + * @return an event for this suggestion pick. + */ + @JvmStatic + fun createPunctuationSuggestionPickedEvent( + suggestedWordInfo: SuggestedWordInfo): Event { + val primaryCode = suggestedWordInfo.mWord[0].toInt() + return Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode, + NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE, + Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE, + null /* next */) + } + + /** + * Creates an input event representing moving the cursor. The relative move amount is stored + * in mX. + * @param moveAmount the relative move amount. + * @return an event for this cursor move. + */ + @JvmStatic + fun createCursorMovedEvent(moveAmount: Int): Event { + return Event(EVENT_TYPE_CURSOR_MOVE, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, + moveAmount, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null) + } + + /** + * Creates an event identical to the passed event, but that has already been consumed. + * @param source the event to copy the properties of. + * @return an identical event marked as consumed. + */ + fun createConsumedEvent(source: Event?): Event { // A consumed event should not input any text at all, so we pass the empty string as text. + return Event(source!!.mEventType, source.mText, source.mCodePoint, source.mKeyCode, + source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_CONSUMED, + source.mNextEvent) + } + + fun createNotHandledEvent(): Event { + return Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, + null /* suggestedWordInfo */, FLAG_NONE, null) + } + } + + // This method is private - to create a new event, use one of the create* utility methods. + init { + // Sanity checks +// mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED + if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) { + if (null == mSuggestedWordInfo) { + throw RuntimeException("Wrong event: SUGGESTION_PICKED event must have a " + + "non-null SuggestedWordInfo") + } + } else { + if (null != mSuggestedWordInfo) { + throw RuntimeException("Wrong event: only SUGGESTION_PICKED events may have " + + "a non-null SuggestedWordInfo") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/EventDecoder.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/EventDecoder.kt similarity index 89% rename from app/src/main/java/org/dslul/openboard/inputmethod/event/EventDecoder.java rename to app/src/main/java/org/dslul/openboard/inputmethod/event/EventDecoder.kt index d5c6ae1aa..45f383527 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/EventDecoder.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/EventDecoder.kt @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.dslul.openboard.inputmethod.event; +package org.dslul.openboard.inputmethod.event /** * A generic interface for event decoders. */ -public interface EventDecoder { - -} +interface EventDecoder \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareEventDecoder.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareEventDecoder.kt similarity index 76% rename from app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareEventDecoder.java rename to app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareEventDecoder.kt index 2d8c8db0e..9e5ff958d 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareEventDecoder.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareEventDecoder.kt @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.dslul.openboard.inputmethod.event -package org.dslul.openboard.inputmethod.event; - -import android.view.KeyEvent; +import android.view.KeyEvent /** * An event decoder for hardware events. */ -public interface HardwareEventDecoder extends EventDecoder { - public Event decodeHardwareKey(final KeyEvent keyEvent); -} +interface HardwareEventDecoder : EventDecoder { + fun decodeHardwareKey(keyEvent: KeyEvent): Event +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareKeyboardEventDecoder.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareKeyboardEventDecoder.java deleted file mode 100644 index 4bba8d935..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareKeyboardEventDecoder.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dslul.openboard.inputmethod.event; - -import android.view.KeyCharacterMap; -import android.view.KeyEvent; - -import org.dslul.openboard.inputmethod.latin.common.Constants; - -/** - * A hardware event decoder for a hardware qwerty-ish keyboard. - * - * The events are always hardware keypresses, but they can be key down or key up events, they - * can be dead keys, they can be meta keys like shift or ctrl... This does not deal with - * 10-key like keyboards; a different decoder is used for this. - */ -public class HardwareKeyboardEventDecoder implements HardwareEventDecoder { - final int mDeviceId; - - public HardwareKeyboardEventDecoder(final int deviceId) { - mDeviceId = deviceId; - // TODO: get the layout for this hardware keyboard - } - - @Override - public Event decodeHardwareKey(final KeyEvent keyEvent) { - // KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value - // that includes both the unicode char in the lower 21 bits and flags in the upper bits, - // hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info. - final int codePointAndFlags = keyEvent.getUnicodeChar(); - // The keyCode is the abstraction used by the KeyEvent to represent different keys that - // do not necessarily map to a unicode character. This represents a physical key, like - // the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock. - final int keyCode = keyEvent.getKeyCode(); - final boolean isKeyRepeat = (0 != keyEvent.getRepeatCount()); - if (KeyEvent.KEYCODE_DEL == keyCode) { - return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, Constants.CODE_DELETE, - null /* next */, isKeyRepeat); - } - if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode - || KeyEvent.KEYCODE_ENTER == keyCode) { - if (0 != (codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT)) { - // A dead key. - return Event.createDeadEvent( - codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, keyCode, - null /* next */); - } - if (KeyEvent.KEYCODE_ENTER == keyCode) { - // The Enter key. If the Shift key is not being pressed, this should send a - // CODE_ENTER to trigger the action if any, or a carriage return otherwise. If the - // Shift key is being pressed, this should send a CODE_SHIFT_ENTER and let - // Latin IME decide what to do with it. - if (keyEvent.isShiftPressed()) { - return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, - Constants.CODE_SHIFT_ENTER, null /* next */, isKeyRepeat); - } - return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode, - null /* next */, isKeyRepeat); - } - // If not Enter, then this is just a regular keypress event for a normal character - // that can be committed right away, taking into account the current state. - return Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, null /* next */, - isKeyRepeat); - } - return Event.createNotHandledEvent(); - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareKeyboardEventDecoder.kt b/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareKeyboardEventDecoder.kt new file mode 100644 index 000000000..91991e8d9 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/HardwareKeyboardEventDecoder.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dslul.openboard.inputmethod.event + +import android.view.KeyCharacterMap +import android.view.KeyEvent +import org.dslul.openboard.inputmethod.latin.common.Constants + +/** + * A hardware event decoder for a hardware qwerty-ish keyboard. + * + * The events are always hardware keypresses, but they can be key down or key up events, they + * can be dead keys, they can be meta keys like shift or ctrl... This does not deal with + * 10-key like keyboards; a different decoder is used for this. + */ +class HardwareKeyboardEventDecoder // TODO: get the layout for this hardware keyboard +(val mDeviceId: Int) : HardwareEventDecoder { + override fun decodeHardwareKey(keyEvent: KeyEvent): Event { // KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value +// that includes both the unicode char in the lower 21 bits and flags in the upper bits, +// hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info. + val codePointAndFlags = keyEvent.unicodeChar + // The keyCode is the abstraction used by the KeyEvent to represent different keys that +// do not necessarily map to a unicode character. This represents a physical key, like +// the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock. + val keyCode = keyEvent.keyCode + val isKeyRepeat = 0 != keyEvent.repeatCount + if (KeyEvent.KEYCODE_DEL == keyCode) { + return Event.Companion.createHardwareKeypressEvent(Event.Companion.NOT_A_CODE_POINT, Constants.CODE_DELETE, + null /* next */, isKeyRepeat) + } + if (keyEvent.isPrintingKey || KeyEvent.KEYCODE_SPACE == keyCode || KeyEvent.KEYCODE_ENTER == keyCode) { + if (0 != codePointAndFlags and KeyCharacterMap.COMBINING_ACCENT) { // A dead key. + return Event.Companion.createDeadEvent( + codePointAndFlags and KeyCharacterMap.COMBINING_ACCENT_MASK, keyCode, + null /* next */) + } + return if (KeyEvent.KEYCODE_ENTER == keyCode) { // The Enter key. If the Shift key is not being pressed, this should send a +// CODE_ENTER to trigger the action if any, or a carriage return otherwise. If the +// Shift key is being pressed, this should send a CODE_SHIFT_ENTER and let +// Latin IME decide what to do with it. + if (keyEvent.isShiftPressed) { + Event.Companion.createHardwareKeypressEvent(Event.Companion.NOT_A_CODE_POINT, + Constants.CODE_SHIFT_ENTER, null /* next */, isKeyRepeat) + } else Event.Companion.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode, + null /* next */, isKeyRepeat) + } else Event.Companion.createHardwareKeypressEvent(codePointAndFlags, keyCode, null /* next */, + isKeyRepeat) + // If not Enter, then this is just a regular keypress event for a normal character +// that can be committed right away, taking into account the current state. + } + return Event.Companion.createNotHandledEvent() + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/InputTransaction.java b/app/src/main/java/org/dslul/openboard/inputmethod/event/InputTransaction.java deleted file mode 100644 index 38af82120..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/event/InputTransaction.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dslul.openboard.inputmethod.event; - -import org.dslul.openboard.inputmethod.latin.settings.SettingsValues; - -/** - * An object encapsulating a single transaction for input. - */ -public class InputTransaction { - // UPDATE_LATER is stronger than UPDATE_NOW. The reason for this is, if we have to update later, - // it's because something will change that we can't evaluate now, which means that even if we - // re-evaluate now we'll have to do it again later. The only case where that wouldn't apply - // would be if we needed to update now to find out the new state right away, but then we - // can't do it with this deferred mechanism anyway. - public static final int SHIFT_NO_UPDATE = 0; - public static final int SHIFT_UPDATE_NOW = 1; - public static final int SHIFT_UPDATE_LATER = 2; - - // Initial conditions - public final SettingsValues mSettingsValues; - public final Event mEvent; - public final long mTimestamp; - public final int mSpaceState; - public final int mShiftState; - - // Outputs - private int mRequiredShiftUpdate = SHIFT_NO_UPDATE; - private boolean mRequiresUpdateSuggestions = false; - private boolean mDidAffectContents = false; - private boolean mDidAutoCorrect = false; - - public InputTransaction(final SettingsValues settingsValues, final Event event, - final long timestamp, final int spaceState, final int shiftState) { - mSettingsValues = settingsValues; - mEvent = event; - mTimestamp = timestamp; - mSpaceState = spaceState; - mShiftState = shiftState; - } - - /** - * Indicate that this transaction requires some type of shift update. - * @param updateType What type of shift update this requires. - */ - public void requireShiftUpdate(final int updateType) { - mRequiredShiftUpdate = Math.max(mRequiredShiftUpdate, updateType); - } - - /** - * Gets what type of shift update this transaction requires. - * @return The shift update type. - */ - public int getRequiredShiftUpdate() { - return mRequiredShiftUpdate; - } - - /** - * Indicate that this transaction requires updating the suggestions. - */ - public void setRequiresUpdateSuggestions() { - mRequiresUpdateSuggestions = true; - } - - /** - * Find out whether this transaction requires updating the suggestions. - * @return Whether this transaction requires updating the suggestions. - */ - public boolean requiresUpdateSuggestions() { - return mRequiresUpdateSuggestions; - } - - /** - * Indicate that this transaction affected the contents of the editor. - */ - public void setDidAffectContents() { - mDidAffectContents = true; - } - - /** - * Find out whether this transaction affected contents of the editor. - * @return Whether this transaction affected contents of the editor. - */ - public boolean didAffectContents() { - return mDidAffectContents; - } - - /** - * Indicate that this transaction performed an auto-correction. - */ - public void setDidAutoCorrect() { - mDidAutoCorrect = true; - } - - /** - * Find out whether this transaction performed an auto-correction. - * @return Whether this transaction performed an auto-correction. - */ - public boolean didAutoCorrect() { - return mDidAutoCorrect; - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/event/InputTransaction.kt b/app/src/main/java/org/dslul/openboard/inputmethod/event/InputTransaction.kt new file mode 100644 index 000000000..0ce410f49 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/event/InputTransaction.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.dslul.openboard.inputmethod.event + +import org.dslul.openboard.inputmethod.latin.settings.SettingsValues + +/** + * An object encapsulating a single transaction for input. + */ +class InputTransaction(// Initial conditions + val mSettingsValues: SettingsValues, val mEvent: Event, + val mTimestamp: Long, val mSpaceState: Int, val mShiftState: Int) { + /** + * Gets what type of shift update this transaction requires. + * @return The shift update type. + */ + // Outputs + var requiredShiftUpdate = SHIFT_NO_UPDATE + private set + private var mRequiresUpdateSuggestions = false + private var mDidAffectContents = false + private var mDidAutoCorrect = false + /** + * Indicate that this transaction requires some type of shift update. + * @param updateType What type of shift update this requires. + */ + fun requireShiftUpdate(updateType: Int) { + requiredShiftUpdate = Math.max(requiredShiftUpdate, updateType) + } + + /** + * Indicate that this transaction requires updating the suggestions. + */ + fun setRequiresUpdateSuggestions() { + mRequiresUpdateSuggestions = true + } + + /** + * Find out whether this transaction requires updating the suggestions. + * @return Whether this transaction requires updating the suggestions. + */ + fun requiresUpdateSuggestions(): Boolean { + return mRequiresUpdateSuggestions + } + + /** + * Indicate that this transaction affected the contents of the editor. + */ + fun setDidAffectContents() { + mDidAffectContents = true + } + + /** + * Find out whether this transaction affected contents of the editor. + * @return Whether this transaction affected contents of the editor. + */ + fun didAffectContents(): Boolean { + return mDidAffectContents + } + + /** + * Indicate that this transaction performed an auto-correction. + */ + fun setDidAutoCorrect() { + mDidAutoCorrect = true + } + + /** + * Find out whether this transaction performed an auto-correction. + * @return Whether this transaction performed an auto-correction. + */ + fun didAutoCorrect(): Boolean { + return mDidAutoCorrect + } + + companion object { + // UPDATE_LATER is stronger than UPDATE_NOW. The reason for this is, if we have to update later, +// it's because something will change that we can't evaluate now, which means that even if we +// re-evaluate now we'll have to do it again later. The only case where that wouldn't apply +// would be if we needed to update now to find out the new state right away, but then we +// can't do it with this deferred mechanism anyway. + const val SHIFT_NO_UPDATE = 0 + const val SHIFT_UPDATE_NOW = 1 + const val SHIFT_UPDATE_LATER = 2 + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java index a2497bec4..a85a96475 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardState.java @@ -619,7 +619,7 @@ public final class KeyboardState { } public void onEvent(final Event event, final int autoCapsFlags, final int recapitalizeMode) { - final int code = event.isFunctionalKeyEvent() ? event.mKeyCode : event.mCodePoint; + final int code = event.isFunctionalKeyEvent() ? event.getMKeyCode() : event.getMCodePoint(); if (DEBUG_EVENT) { Log.d(TAG, "onEvent: code=" + Constants.printableCode(code) + " " + stateToString(autoCapsFlags, recapitalizeMode)); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java index a454e98d4..e0f4ce3bf 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java @@ -1443,7 +1443,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // This method is public for testability of LatinIME, but also in the future it should // completely replace #onCodeInput. public void onEvent(@Nonnull final Event event) { - if (Constants.CODE_SHORTCUT == event.mKeyCode) { + if (Constants.CODE_SHORTCUT == event.getMKeyCode()) { mRichImm.switchToShortcutIme(this); } final InputTransaction completeInputTransaction = @@ -1685,10 +1685,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if (inputTransaction.requiresUpdateSuggestions()) { final int inputStyle; - if (inputTransaction.mEvent.isSuggestionStripPress()) { + if (inputTransaction.getMEvent().isSuggestionStripPress()) { // Suggestion strip press: no input. inputStyle = SuggestedWords.INPUT_STYLE_NONE; - } else if (inputTransaction.mEvent.isGesture()) { + } else if (inputTransaction.getMEvent().isGesture()) { inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH; } else { inputStyle = SuggestedWords.INPUT_STYLE_TYPING; diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/WordComposer.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/WordComposer.java index 41fd8efd7..fad40f500 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/WordComposer.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/WordComposer.java @@ -177,9 +177,9 @@ public final class WordComposer { */ public void applyProcessedEvent(final Event event) { mCombinerChain.applyProcessedEvent(event); - final int primaryCode = event.mCodePoint; - final int keyX = event.mX; - final int keyY = event.mY; + final int primaryCode = event.getMCodePoint(); + final int keyX = event.getMX(); + final int keyY = event.getMY(); final int newIndex = size(); refreshTypedWordCache(); mCursorPositionWithinWord = mCodePointSize; @@ -187,7 +187,7 @@ public final class WordComposer { if (0 == mCodePointSize) { mIsOnlyFirstCharCapitalized = false; } - if (Constants.CODE_DELETE != event.mKeyCode) { + if (Constants.CODE_DELETE != event.getMKeyCode()) { if (newIndex < MAX_WORD_LENGTH) { // In the batch input mode, the {@code mInputPointers} holds batch input points and // shouldn't be overridden by the "typed key" coordinates diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java index 560acaaf9..7b582ab57 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java @@ -442,11 +442,11 @@ public final class InputLogic { final InputTransaction inputTransaction = new InputTransaction(settingsValues, processedEvent, SystemClock.uptimeMillis(), mSpaceState, getActualCapsMode(settingsValues, keyboardShiftMode)); - if (processedEvent.mKeyCode != Constants.CODE_DELETE - || inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) { + if (processedEvent.getMKeyCode() != Constants.CODE_DELETE + || inputTransaction.getMTimestamp() > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) { mDeleteCount = 0; } - mLastKeyTime = inputTransaction.mTimestamp; + mLastKeyTime = inputTransaction.getMTimestamp(); mConnection.beginBatchEdit(); if (!mWordComposer.isComposingWord()) { // TODO: is this useful? It doesn't look like it should be done here, but rather after @@ -455,7 +455,7 @@ public final class InputLogic { } // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state. - if (processedEvent.mCodePoint != Constants.CODE_SPACE) { + if (processedEvent.getMCodePoint() != Constants.CODE_SPACE) { cancelDoubleSpacePeriodCountdown(); } @@ -469,21 +469,21 @@ public final class InputLogic { } else { handleNonFunctionalEvent(currentEvent, inputTransaction, handler); } - currentEvent = currentEvent.mNextEvent; + currentEvent = currentEvent.getMNextEvent(); } // Try to record the word being corrected when the user enters a word character or // the backspace key. if (!mConnection.hasSlowInputConnection() && !mWordComposer.isComposingWord() - && (settingsValues.isWordCodePoint(processedEvent.mCodePoint) || - processedEvent.mKeyCode == Constants.CODE_DELETE)) { + && (settingsValues.isWordCodePoint(processedEvent.getMCodePoint()) || + processedEvent.getMKeyCode() == Constants.CODE_DELETE)) { mWordBeingCorrectedByCursor = getWordAtCursor( settingsValues, currentKeyboardScriptId); } - if (!inputTransaction.didAutoCorrect() && processedEvent.mKeyCode != Constants.CODE_SHIFT - && processedEvent.mKeyCode != Constants.CODE_CAPSLOCK - && processedEvent.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL) + if (!inputTransaction.didAutoCorrect() && processedEvent.getMKeyCode() != Constants.CODE_SHIFT + && processedEvent.getMKeyCode() != Constants.CODE_CAPSLOCK + && processedEvent.getMKeyCode() != Constants.CODE_SWITCH_ALPHA_SYMBOL) mLastComposedWord.deactivate(); - if (Constants.CODE_DELETE != processedEvent.mKeyCode) { + if (Constants.CODE_DELETE != processedEvent.getMKeyCode()) { mEnteredText = null; } mConnection.endBatchEdit(); @@ -643,14 +643,14 @@ public final class InputLogic { */ private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction, final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { - switch (event.mKeyCode) { + switch (event.getMKeyCode()) { case Constants.CODE_DELETE: handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId); // Backspace is a functional key, but it affects the contents of the editor. inputTransaction.setDidAffectContents(); break; case Constants.CODE_SHIFT: - performRecapitalization(inputTransaction.mSettingsValues); + performRecapitalization(inputTransaction.getMSettingsValues()); inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); if (mSuggestedWords.isPrediction()) { inputTransaction.setRequiresUpdateSuggestions(); @@ -694,14 +694,14 @@ public final class InputLogic { break; case Constants.CODE_SHIFT_ENTER: final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER, - event.mKeyCode, event.mX, event.mY, event.isKeyRepeat()); + event.getMKeyCode(), event.getMX(), event.getMY(), event.isKeyRepeat()); handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler); // Shift + Enter is treated as a functional key but it results in adding a new // line, so that does affect the contents of the editor. inputTransaction.setDidAffectContents(); break; default: - throw new RuntimeException("Unknown key code : " + event.mKeyCode); + throw new RuntimeException("Unknown key code : " + event.getMKeyCode()); } } @@ -718,7 +718,7 @@ public final class InputLogic { final InputTransaction inputTransaction, final LatinIME.UIHandler handler) { inputTransaction.setDidAffectContents(); - switch (event.mCodePoint) { + switch (event.getMCodePoint()) { case Constants.CODE_ENTER: final EditorInfo editorInfo = getCurrentInputEditorInfo(); final int imeOptionsActionId = @@ -762,26 +762,26 @@ public final class InputLogic { private void handleNonSpecialCharacterEvent(final Event event, final InputTransaction inputTransaction, final LatinIME.UIHandler handler) { - final int codePoint = event.mCodePoint; + final int codePoint = event.getMCodePoint(); mSpaceState = SpaceState.NONE; - if (inputTransaction.mSettingsValues.isWordSeparator(codePoint) + if (inputTransaction.getMSettingsValues().isWordSeparator(codePoint) || Character.getType(codePoint) == Character.OTHER_SYMBOL) { handleSeparatorEvent(event, inputTransaction, handler); } else { - if (SpaceState.PHANTOM == inputTransaction.mSpaceState) { + if (SpaceState.PHANTOM == inputTransaction.getMSpaceState()) { if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the character at the current cursor position. // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, + unlearnWord(mWordComposer.getTypedWord(), inputTransaction.getMSettingsValues(), Constants.EVENT_BACKSPACE); resetEntireInputState(mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); } else { - commitTyped(inputTransaction.mSettingsValues, LastComposedWord.NOT_A_SEPARATOR); + commitTyped(inputTransaction.getMSettingsValues(), LastComposedWord.NOT_A_SEPARATOR); } } - handleNonSeparatorEvent(event, inputTransaction.mSettingsValues, inputTransaction); + handleNonSeparatorEvent(event, inputTransaction.getMSettingsValues(), inputTransaction); } } @@ -793,7 +793,7 @@ public final class InputLogic { */ private void handleNonSeparatorEvent(final Event event, final SettingsValues settingsValues, final InputTransaction inputTransaction) { - final int codePoint = event.mCodePoint; + final int codePoint = event.getMCodePoint(); // TODO: refactor this method to stop flipping isComposingWord around all the time, and // make it shorter (possibly cut into several pieces). Also factor // handleNonSpecialCharacterEvent which has the same name as other handle* methods but is @@ -802,7 +802,7 @@ public final class InputLogic { // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead. // See onStartBatchInput() to see how to do it. - if (SpaceState.PHANTOM == inputTransaction.mSpaceState + if (SpaceState.PHANTOM == inputTransaction.getMSpaceState() && !settingsValues.isWordConnector(codePoint)) { if (isComposingWord) { // Sanity check @@ -815,7 +815,7 @@ public final class InputLogic { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the character at the current cursor position. // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, + unlearnWord(mWordComposer.getTypedWord(), inputTransaction.getMSettingsValues(), Constants.EVENT_BACKSPACE); resetEntireInputState(mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); @@ -856,7 +856,7 @@ public final class InputLogic { mWordComposer.applyProcessedEvent(event); // If it's the first letter, make note of auto-caps state if (mWordComposer.isSingleLetter()) { - mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState); + mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.getMShiftState()); } setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1); } else { @@ -879,8 +879,8 @@ public final class InputLogic { */ private void handleSeparatorEvent(final Event event, final InputTransaction inputTransaction, final LatinIME.UIHandler handler) { - final int codePoint = event.mCodePoint; - final SettingsValues settingsValues = inputTransaction.mSettingsValues; + final int codePoint = event.getMCodePoint(); + final SettingsValues settingsValues = inputTransaction.getMSettingsValues(); final boolean wasComposingWord = mWordComposer.isComposingWord(); // We avoid sending spaces in languages without spaces if we were composing. final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint @@ -890,7 +890,7 @@ public final class InputLogic { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the separator at the current cursor position. // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, + unlearnWord(mWordComposer.getTypedWord(), inputTransaction.getMSettingsValues(), Constants.EVENT_BACKSPACE); resetEntireInputState(mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); @@ -915,7 +915,7 @@ public final class InputLogic { && mConnection.isInsideDoubleQuoteOrAfterDigit(); final boolean needsPrecedingSpace; - if (SpaceState.PHANTOM != inputTransaction.mSpaceState) { + if (SpaceState.PHANTOM != inputTransaction.getMSpaceState()) { needsPrecedingSpace = false; } else if (Constants.CODE_DOUBLE_QUOTE == codePoint) { // Double quotes behave like they are usually preceded by space iff we are @@ -954,7 +954,7 @@ public final class InputLogic { sendKeyCodePoint(settingsValues, codePoint); } } else { - if ((SpaceState.PHANTOM == inputTransaction.mSpaceState + if ((SpaceState.PHANTOM == inputTransaction.getMSpaceState() && settingsValues.isUsuallyFollowedBySpace(codePoint)) || (Constants.CODE_DOUBLE_QUOTE == codePoint && isInsideDoubleQuoteOrAfterDigit)) { @@ -1008,7 +1008,7 @@ public final class InputLogic { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can remove the character at the current cursor position. // We also need to unlearn the original word that is now being corrected. - unlearnWord(mWordComposer.getTypedWord(), inputTransaction.mSettingsValues, + unlearnWord(mWordComposer.getTypedWord(), inputTransaction.getMSettingsValues(), Constants.EVENT_BACKSPACE); resetEntireInputState(mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */); @@ -1020,7 +1020,7 @@ public final class InputLogic { mWordComposer.reset(); mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); if (!TextUtils.isEmpty(rejectedSuggestion)) { - unlearnWord(rejectedSuggestion, inputTransaction.mSettingsValues, + unlearnWord(rejectedSuggestion, inputTransaction.getMSettingsValues(), Constants.EVENT_REJECTION); } StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length()); @@ -1037,7 +1037,7 @@ public final class InputLogic { } else { if (mLastComposedWord.canRevertCommit()) { final String lastComposedWord = mLastComposedWord.mTypedWord; - revertCommit(inputTransaction, inputTransaction.mSettingsValues); + revertCommit(inputTransaction, inputTransaction.getMSettingsValues()); StatsUtils.onRevertAutoCorrect(); StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode()); // Restart suggestions when backspacing into a reverted word. This is required for @@ -1046,12 +1046,12 @@ public final class InputLogic { // // Note: restartSuggestionsOnWordTouchedByCursor is already called for normal // (non-revert) backspace handling. - if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings() - && inputTransaction.mSettingsValues.mSpacingAndPunctuations + if (inputTransaction.getMSettingsValues().isSuggestionsEnabledPerUserSettings() + && inputTransaction.getMSettingsValues().mSpacingAndPunctuations .mCurrentLanguageHasSpaces && !mConnection.isCursorFollowedByWordCharacter( - inputTransaction.mSettingsValues.mSpacingAndPunctuations)) { - restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues, + inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) { + restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), false /* forStartInput */, currentKeyboardScriptId); } return; @@ -1068,10 +1068,10 @@ public final class InputLogic { // reverting any autocorrect at this point. So we can safely return. return; } - if (SpaceState.DOUBLE == inputTransaction.mSpaceState) { + if (SpaceState.DOUBLE == inputTransaction.getMSpaceState()) { cancelDoubleSpacePeriodCountdown(); if (mConnection.revertDoubleSpacePeriod( - inputTransaction.mSettingsValues.mSpacingAndPunctuations)) { + inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) { // No need to reset mSpaceState, it has already be done (that's why we // receive it as a parameter) inputTransaction.setRequiresUpdateSuggestions(); @@ -1080,7 +1080,7 @@ public final class InputLogic { StatsUtils.onRevertDoubleSpacePeriod(); return; } - } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) { + } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.getMSpaceState()) { if (mConnection.revertSwapPunctuation()) { StatsUtils.onRevertSwapPunctuation(); // Likewise @@ -1097,7 +1097,7 @@ public final class InputLogic { // We also need to unlearn the selected text. final CharSequence selection = mConnection.getSelectedText(0 /* 0 for no styles */); if (!TextUtils.isEmpty(selection)) { - unlearnWord(selection.toString(), inputTransaction.mSettingsValues, + unlearnWord(selection.toString(), inputTransaction.getMSettingsValues(), Constants.EVENT_BACKSPACE); hasUnlearnedWordBeingDeleted = true; } @@ -1109,8 +1109,8 @@ public final class InputLogic { StatsUtils.onBackspaceSelectedText(numCharsDeleted); } else { // There is no selection, just delete one character. - if (inputTransaction.mSettingsValues.isBeforeJellyBean() - || inputTransaction.mSettingsValues.mInputAttributes.isTypeNull() + if (inputTransaction.getMSettingsValues().isBeforeJellyBean() + || inputTransaction.getMSettingsValues().mInputAttributes.isTypeNull() || Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) { // There are three possible reasons to send a key event: either the field has @@ -1131,7 +1131,7 @@ public final class InputLogic { // consider unlearning here because we may have already reached // the previous word, and will lose it after next deletion. hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( - inputTransaction.mSettingsValues, currentKeyboardScriptId); + inputTransaction.getMSettingsValues(), currentKeyboardScriptId); sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); totalDeletedLength++; } @@ -1158,7 +1158,7 @@ public final class InputLogic { // consider unlearning here because we may have already reached // the previous word, and will lose it after next deletion. hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( - inputTransaction.mSettingsValues, currentKeyboardScriptId); + inputTransaction.getMSettingsValues(), currentKeyboardScriptId); final int codePointBeforeCursorToDeleteAgain = mConnection.getCodePointBeforeCursor(); if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { @@ -1174,16 +1174,16 @@ public final class InputLogic { if (!hasUnlearnedWordBeingDeleted) { // Consider unlearning the word being deleted (if we have not done so already). unlearnWordBeingDeleted( - inputTransaction.mSettingsValues, currentKeyboardScriptId); + inputTransaction.getMSettingsValues(), currentKeyboardScriptId); } if (mConnection.hasSlowInputConnection()) { mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); - } else if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings() - && inputTransaction.mSettingsValues.mSpacingAndPunctuations + } else if (inputTransaction.getMSettingsValues().isSuggestionsEnabledPerUserSettings() + && inputTransaction.getMSettingsValues().mSpacingAndPunctuations .mCurrentLanguageHasSpaces && !mConnection.isCursorFollowedByWordCharacter( - inputTransaction.mSettingsValues.mSpacingAndPunctuations)) { - restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues, + inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) { + restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), false /* forStartInput */, currentKeyboardScriptId); } } @@ -1272,20 +1272,20 @@ public final class InputLogic { */ private boolean tryStripSpaceAndReturnWhetherShouldSwapInstead(final Event event, final InputTransaction inputTransaction) { - final int codePoint = event.mCodePoint; + final int codePoint = event.getMCodePoint(); final boolean isFromSuggestionStrip = event.isSuggestionStripPress(); if (Constants.CODE_ENTER == codePoint && - SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) { + SpaceState.SWAP_PUNCTUATION == inputTransaction.getMSpaceState()) { mConnection.removeTrailingSpace(); return false; } - if ((SpaceState.WEAK == inputTransaction.mSpaceState - || SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) + if ((SpaceState.WEAK == inputTransaction.getMSpaceState() + || SpaceState.SWAP_PUNCTUATION == inputTransaction.getMSpaceState()) && isFromSuggestionStrip) { - if (inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(codePoint)) { + if (inputTransaction.getMSettingsValues().isUsuallyPrecededBySpace(codePoint)) { return false; } - if (inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint)) { + if (inputTransaction.getMSettingsValues().isUsuallyFollowedBySpace(codePoint)) { return true; } mConnection.removeTrailingSpace(); @@ -1294,7 +1294,7 @@ public final class InputLogic { } public void startDoubleSpacePeriodCountdown(final InputTransaction inputTransaction) { - mDoubleSpacePeriodCountdownStart = inputTransaction.mTimestamp; + mDoubleSpacePeriodCountdownStart = inputTransaction.getMTimestamp(); } public void cancelDoubleSpacePeriodCountdown() { @@ -1302,8 +1302,8 @@ public final class InputLogic { } public boolean isDoubleSpacePeriodCountdownActive(final InputTransaction inputTransaction) { - return inputTransaction.mTimestamp - mDoubleSpacePeriodCountdownStart - < inputTransaction.mSettingsValues.mDoubleSpacePeriodTimeout; + return inputTransaction.getMTimestamp() - mDoubleSpacePeriodCountdownStart + < inputTransaction.getMSettingsValues().mDoubleSpacePeriodTimeout; } /** @@ -1326,8 +1326,8 @@ public final class InputLogic { final InputTransaction inputTransaction) { // Check the setting, the typed character and the countdown. If any of the conditions is // not fulfilled, return false. - if (!inputTransaction.mSettingsValues.mUseDoubleSpacePeriod - || Constants.CODE_SPACE != event.mCodePoint + if (!inputTransaction.getMSettingsValues().mUseDoubleSpacePeriod + || Constants.CODE_SPACE != event.getMCodePoint() || !isDoubleSpacePeriodCountdownActive(inputTransaction)) { return false; } @@ -1348,7 +1348,7 @@ public final class InputLogic { if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) { cancelDoubleSpacePeriodCountdown(); mConnection.deleteTextBeforeCursor(1); - final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations + final String textToInsert = inputTransaction.getMSettingsValues().mSpacingAndPunctuations .mSentenceSeparatorAndSpace; mConnection.commitText(textToInsert, 1); inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); @@ -1662,7 +1662,7 @@ public final class InputLogic { } mConnection.deleteTextBeforeCursor(deleteLength); if (!TextUtils.isEmpty(committedWord)) { - unlearnWord(committedWordString, inputTransaction.mSettingsValues, + unlearnWord(committedWordString, inputTransaction.getMSettingsValues(), Constants.EVENT_REVERT); } final String stringToCommit = originallyTypedWord + @@ -1696,13 +1696,13 @@ public final class InputLogic { } // Add the suggestion list to the list of suggestions. textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */, - inputTransaction.mSettingsValues.mLocale, + inputTransaction.getMSettingsValues().mLocale, suggestions.toArray(new String[suggestions.size()]), 0 /* flags */, null /* notificationTargetClass */), 0 /* start */, lastCharIndex /* end */, 0 /* flags */); } - if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { + if (inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { mConnection.commitText(textToCommit, 1); if (usePhantomSpace) { mSpaceState = SpaceState.PHANTOM; diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5453d51c1..da2a4690c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -76,6 +76,9 @@ #19FFFFFF #E621272B #21272B + + #ff3c474c + #FFEBEBEB #FF707070