mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-14 22:12:46 +00:00
convert event to kotlin
This commit is contained in:
parent
a57dc0ee19
commit
a059a0798b
18 changed files with 902 additions and 1067 deletions
|
@ -1,30 +1,12 @@
|
||||||
/*
|
package org.dslul.openboard.inputmethod.event
|
||||||
* 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 java.util.*
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic interface for combiners. Combiners are objects that transform chains of input events
|
* 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.
|
* 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.
|
* 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.
|
* @param event the event to combine with the existing state.
|
||||||
* @return the resulting event.
|
* @return the resulting event.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
fun processEvent(previousEvents: ArrayList<Event>?, event: Event?): Event?
|
||||||
Event processEvent(ArrayList<Event> previousEvents, Event event);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the feedback that should be shown to the user for the current state of this combiner.
|
* 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.
|
* @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.
|
* Reset the state of this combiner, for example when the cursor was moved.
|
||||||
*/
|
*/
|
||||||
void reset();
|
fun reset()
|
||||||
}
|
}
|
|
@ -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<Combiner> 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<Event> previousEvents,
|
|
||||||
@Nonnull final Event newEvent) {
|
|
||||||
final ArrayList<Event> 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Combiner>
|
||||||
|
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<Event>?,
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Event> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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?): 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
264
app/src/main/java/org/dslul/openboard/inputmethod/event/Event.kt
Normal file
264
app/src/main/java/org/dslul/openboard/inputmethod/event/Event.kt
Normal file
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,12 +13,9 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package org.dslul.openboard.inputmethod.event
|
||||||
package org.dslul.openboard.inputmethod.event;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic interface for event decoders.
|
* A generic interface for event decoders.
|
||||||
*/
|
*/
|
||||||
public interface EventDecoder {
|
interface EventDecoder
|
||||||
|
|
||||||
}
|
|
|
@ -13,14 +13,13 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.
|
* An event decoder for hardware events.
|
||||||
*/
|
*/
|
||||||
public interface HardwareEventDecoder extends EventDecoder {
|
interface HardwareEventDecoder : EventDecoder {
|
||||||
public Event decodeHardwareKey(final KeyEvent keyEvent);
|
fun decodeHardwareKey(keyEvent: KeyEvent): Event
|
||||||
}
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -619,7 +619,7 @@ public final class KeyboardState {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEvent(final Event event, final int autoCapsFlags, final int recapitalizeMode) {
|
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) {
|
if (DEBUG_EVENT) {
|
||||||
Log.d(TAG, "onEvent: code=" + Constants.printableCode(code)
|
Log.d(TAG, "onEvent: code=" + Constants.printableCode(code)
|
||||||
+ " " + stateToString(autoCapsFlags, recapitalizeMode));
|
+ " " + stateToString(autoCapsFlags, recapitalizeMode));
|
||||||
|
|
|
@ -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
|
// This method is public for testability of LatinIME, but also in the future it should
|
||||||
// completely replace #onCodeInput.
|
// completely replace #onCodeInput.
|
||||||
public void onEvent(@Nonnull final Event event) {
|
public void onEvent(@Nonnull final Event event) {
|
||||||
if (Constants.CODE_SHORTCUT == event.mKeyCode) {
|
if (Constants.CODE_SHORTCUT == event.getMKeyCode()) {
|
||||||
mRichImm.switchToShortcutIme(this);
|
mRichImm.switchToShortcutIme(this);
|
||||||
}
|
}
|
||||||
final InputTransaction completeInputTransaction =
|
final InputTransaction completeInputTransaction =
|
||||||
|
@ -1685,10 +1685,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
if (inputTransaction.requiresUpdateSuggestions()) {
|
if (inputTransaction.requiresUpdateSuggestions()) {
|
||||||
final int inputStyle;
|
final int inputStyle;
|
||||||
if (inputTransaction.mEvent.isSuggestionStripPress()) {
|
if (inputTransaction.getMEvent().isSuggestionStripPress()) {
|
||||||
// Suggestion strip press: no input.
|
// Suggestion strip press: no input.
|
||||||
inputStyle = SuggestedWords.INPUT_STYLE_NONE;
|
inputStyle = SuggestedWords.INPUT_STYLE_NONE;
|
||||||
} else if (inputTransaction.mEvent.isGesture()) {
|
} else if (inputTransaction.getMEvent().isGesture()) {
|
||||||
inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH;
|
inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH;
|
||||||
} else {
|
} else {
|
||||||
inputStyle = SuggestedWords.INPUT_STYLE_TYPING;
|
inputStyle = SuggestedWords.INPUT_STYLE_TYPING;
|
||||||
|
|
|
@ -177,9 +177,9 @@ public final class WordComposer {
|
||||||
*/
|
*/
|
||||||
public void applyProcessedEvent(final Event event) {
|
public void applyProcessedEvent(final Event event) {
|
||||||
mCombinerChain.applyProcessedEvent(event);
|
mCombinerChain.applyProcessedEvent(event);
|
||||||
final int primaryCode = event.mCodePoint;
|
final int primaryCode = event.getMCodePoint();
|
||||||
final int keyX = event.mX;
|
final int keyX = event.getMX();
|
||||||
final int keyY = event.mY;
|
final int keyY = event.getMY();
|
||||||
final int newIndex = size();
|
final int newIndex = size();
|
||||||
refreshTypedWordCache();
|
refreshTypedWordCache();
|
||||||
mCursorPositionWithinWord = mCodePointSize;
|
mCursorPositionWithinWord = mCodePointSize;
|
||||||
|
@ -187,7 +187,7 @@ public final class WordComposer {
|
||||||
if (0 == mCodePointSize) {
|
if (0 == mCodePointSize) {
|
||||||
mIsOnlyFirstCharCapitalized = false;
|
mIsOnlyFirstCharCapitalized = false;
|
||||||
}
|
}
|
||||||
if (Constants.CODE_DELETE != event.mKeyCode) {
|
if (Constants.CODE_DELETE != event.getMKeyCode()) {
|
||||||
if (newIndex < MAX_WORD_LENGTH) {
|
if (newIndex < MAX_WORD_LENGTH) {
|
||||||
// In the batch input mode, the {@code mInputPointers} holds batch input points and
|
// In the batch input mode, the {@code mInputPointers} holds batch input points and
|
||||||
// shouldn't be overridden by the "typed key" coordinates
|
// shouldn't be overridden by the "typed key" coordinates
|
||||||
|
|
|
@ -442,11 +442,11 @@ public final class InputLogic {
|
||||||
final InputTransaction inputTransaction = new InputTransaction(settingsValues,
|
final InputTransaction inputTransaction = new InputTransaction(settingsValues,
|
||||||
processedEvent, SystemClock.uptimeMillis(), mSpaceState,
|
processedEvent, SystemClock.uptimeMillis(), mSpaceState,
|
||||||
getActualCapsMode(settingsValues, keyboardShiftMode));
|
getActualCapsMode(settingsValues, keyboardShiftMode));
|
||||||
if (processedEvent.mKeyCode != Constants.CODE_DELETE
|
if (processedEvent.getMKeyCode() != Constants.CODE_DELETE
|
||||||
|| inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
|
|| inputTransaction.getMTimestamp() > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
|
||||||
mDeleteCount = 0;
|
mDeleteCount = 0;
|
||||||
}
|
}
|
||||||
mLastKeyTime = inputTransaction.mTimestamp;
|
mLastKeyTime = inputTransaction.getMTimestamp();
|
||||||
mConnection.beginBatchEdit();
|
mConnection.beginBatchEdit();
|
||||||
if (!mWordComposer.isComposingWord()) {
|
if (!mWordComposer.isComposingWord()) {
|
||||||
// TODO: is this useful? It doesn't look like it should be done here, but rather after
|
// 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.
|
// 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();
|
cancelDoubleSpacePeriodCountdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,21 +469,21 @@ public final class InputLogic {
|
||||||
} else {
|
} else {
|
||||||
handleNonFunctionalEvent(currentEvent, inputTransaction, handler);
|
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
|
// Try to record the word being corrected when the user enters a word character or
|
||||||
// the backspace key.
|
// the backspace key.
|
||||||
if (!mConnection.hasSlowInputConnection() && !mWordComposer.isComposingWord()
|
if (!mConnection.hasSlowInputConnection() && !mWordComposer.isComposingWord()
|
||||||
&& (settingsValues.isWordCodePoint(processedEvent.mCodePoint) ||
|
&& (settingsValues.isWordCodePoint(processedEvent.getMCodePoint()) ||
|
||||||
processedEvent.mKeyCode == Constants.CODE_DELETE)) {
|
processedEvent.getMKeyCode() == Constants.CODE_DELETE)) {
|
||||||
mWordBeingCorrectedByCursor = getWordAtCursor(
|
mWordBeingCorrectedByCursor = getWordAtCursor(
|
||||||
settingsValues, currentKeyboardScriptId);
|
settingsValues, currentKeyboardScriptId);
|
||||||
}
|
}
|
||||||
if (!inputTransaction.didAutoCorrect() && processedEvent.mKeyCode != Constants.CODE_SHIFT
|
if (!inputTransaction.didAutoCorrect() && processedEvent.getMKeyCode() != Constants.CODE_SHIFT
|
||||||
&& processedEvent.mKeyCode != Constants.CODE_CAPSLOCK
|
&& processedEvent.getMKeyCode() != Constants.CODE_CAPSLOCK
|
||||||
&& processedEvent.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
|
&& processedEvent.getMKeyCode() != Constants.CODE_SWITCH_ALPHA_SYMBOL)
|
||||||
mLastComposedWord.deactivate();
|
mLastComposedWord.deactivate();
|
||||||
if (Constants.CODE_DELETE != processedEvent.mKeyCode) {
|
if (Constants.CODE_DELETE != processedEvent.getMKeyCode()) {
|
||||||
mEnteredText = null;
|
mEnteredText = null;
|
||||||
}
|
}
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
|
@ -643,14 +643,14 @@ public final class InputLogic {
|
||||||
*/
|
*/
|
||||||
private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,
|
private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,
|
||||||
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
|
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
|
||||||
switch (event.mKeyCode) {
|
switch (event.getMKeyCode()) {
|
||||||
case Constants.CODE_DELETE:
|
case Constants.CODE_DELETE:
|
||||||
handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId);
|
handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId);
|
||||||
// Backspace is a functional key, but it affects the contents of the editor.
|
// Backspace is a functional key, but it affects the contents of the editor.
|
||||||
inputTransaction.setDidAffectContents();
|
inputTransaction.setDidAffectContents();
|
||||||
break;
|
break;
|
||||||
case Constants.CODE_SHIFT:
|
case Constants.CODE_SHIFT:
|
||||||
performRecapitalization(inputTransaction.mSettingsValues);
|
performRecapitalization(inputTransaction.getMSettingsValues());
|
||||||
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
|
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
|
||||||
if (mSuggestedWords.isPrediction()) {
|
if (mSuggestedWords.isPrediction()) {
|
||||||
inputTransaction.setRequiresUpdateSuggestions();
|
inputTransaction.setRequiresUpdateSuggestions();
|
||||||
|
@ -694,14 +694,14 @@ public final class InputLogic {
|
||||||
break;
|
break;
|
||||||
case Constants.CODE_SHIFT_ENTER:
|
case Constants.CODE_SHIFT_ENTER:
|
||||||
final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_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);
|
handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler);
|
||||||
// Shift + Enter is treated as a functional key but it results in adding a new
|
// 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.
|
// line, so that does affect the contents of the editor.
|
||||||
inputTransaction.setDidAffectContents();
|
inputTransaction.setDidAffectContents();
|
||||||
break;
|
break;
|
||||||
default:
|
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 InputTransaction inputTransaction,
|
||||||
final LatinIME.UIHandler handler) {
|
final LatinIME.UIHandler handler) {
|
||||||
inputTransaction.setDidAffectContents();
|
inputTransaction.setDidAffectContents();
|
||||||
switch (event.mCodePoint) {
|
switch (event.getMCodePoint()) {
|
||||||
case Constants.CODE_ENTER:
|
case Constants.CODE_ENTER:
|
||||||
final EditorInfo editorInfo = getCurrentInputEditorInfo();
|
final EditorInfo editorInfo = getCurrentInputEditorInfo();
|
||||||
final int imeOptionsActionId =
|
final int imeOptionsActionId =
|
||||||
|
@ -762,26 +762,26 @@ public final class InputLogic {
|
||||||
private void handleNonSpecialCharacterEvent(final Event event,
|
private void handleNonSpecialCharacterEvent(final Event event,
|
||||||
final InputTransaction inputTransaction,
|
final InputTransaction inputTransaction,
|
||||||
final LatinIME.UIHandler handler) {
|
final LatinIME.UIHandler handler) {
|
||||||
final int codePoint = event.mCodePoint;
|
final int codePoint = event.getMCodePoint();
|
||||||
mSpaceState = SpaceState.NONE;
|
mSpaceState = SpaceState.NONE;
|
||||||
if (inputTransaction.mSettingsValues.isWordSeparator(codePoint)
|
if (inputTransaction.getMSettingsValues().isWordSeparator(codePoint)
|
||||||
|| Character.getType(codePoint) == Character.OTHER_SYMBOL) {
|
|| Character.getType(codePoint) == Character.OTHER_SYMBOL) {
|
||||||
handleSeparatorEvent(event, inputTransaction, handler);
|
handleSeparatorEvent(event, inputTransaction, handler);
|
||||||
} else {
|
} else {
|
||||||
if (SpaceState.PHANTOM == inputTransaction.mSpaceState) {
|
if (SpaceState.PHANTOM == inputTransaction.getMSpaceState()) {
|
||||||
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
|
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
|
||||||
// If we are in the middle of a recorrection, we need to commit the recorrection
|
// 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.
|
// 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.
|
// 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);
|
Constants.EVENT_BACKSPACE);
|
||||||
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
||||||
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
||||||
} else {
|
} 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,
|
private void handleNonSeparatorEvent(final Event event, final SettingsValues settingsValues,
|
||||||
final InputTransaction inputTransaction) {
|
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
|
// TODO: refactor this method to stop flipping isComposingWord around all the time, and
|
||||||
// make it shorter (possibly cut into several pieces). Also factor
|
// make it shorter (possibly cut into several pieces). Also factor
|
||||||
// handleNonSpecialCharacterEvent which has the same name as other handle* methods but is
|
// 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.
|
// TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
|
||||||
// See onStartBatchInput() to see how to do it.
|
// See onStartBatchInput() to see how to do it.
|
||||||
if (SpaceState.PHANTOM == inputTransaction.mSpaceState
|
if (SpaceState.PHANTOM == inputTransaction.getMSpaceState()
|
||||||
&& !settingsValues.isWordConnector(codePoint)) {
|
&& !settingsValues.isWordConnector(codePoint)) {
|
||||||
if (isComposingWord) {
|
if (isComposingWord) {
|
||||||
// Sanity check
|
// 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
|
// 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.
|
// 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.
|
// 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);
|
Constants.EVENT_BACKSPACE);
|
||||||
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
||||||
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
||||||
|
@ -856,7 +856,7 @@ public final class InputLogic {
|
||||||
mWordComposer.applyProcessedEvent(event);
|
mWordComposer.applyProcessedEvent(event);
|
||||||
// If it's the first letter, make note of auto-caps state
|
// If it's the first letter, make note of auto-caps state
|
||||||
if (mWordComposer.isSingleLetter()) {
|
if (mWordComposer.isSingleLetter()) {
|
||||||
mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState);
|
mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.getMShiftState());
|
||||||
}
|
}
|
||||||
setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
|
setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -879,8 +879,8 @@ public final class InputLogic {
|
||||||
*/
|
*/
|
||||||
private void handleSeparatorEvent(final Event event, final InputTransaction inputTransaction,
|
private void handleSeparatorEvent(final Event event, final InputTransaction inputTransaction,
|
||||||
final LatinIME.UIHandler handler) {
|
final LatinIME.UIHandler handler) {
|
||||||
final int codePoint = event.mCodePoint;
|
final int codePoint = event.getMCodePoint();
|
||||||
final SettingsValues settingsValues = inputTransaction.mSettingsValues;
|
final SettingsValues settingsValues = inputTransaction.getMSettingsValues();
|
||||||
final boolean wasComposingWord = mWordComposer.isComposingWord();
|
final boolean wasComposingWord = mWordComposer.isComposingWord();
|
||||||
// We avoid sending spaces in languages without spaces if we were composing.
|
// We avoid sending spaces in languages without spaces if we were composing.
|
||||||
final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
|
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
|
// 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.
|
// 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.
|
// 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);
|
Constants.EVENT_BACKSPACE);
|
||||||
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
||||||
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
||||||
|
@ -915,7 +915,7 @@ public final class InputLogic {
|
||||||
&& mConnection.isInsideDoubleQuoteOrAfterDigit();
|
&& mConnection.isInsideDoubleQuoteOrAfterDigit();
|
||||||
|
|
||||||
final boolean needsPrecedingSpace;
|
final boolean needsPrecedingSpace;
|
||||||
if (SpaceState.PHANTOM != inputTransaction.mSpaceState) {
|
if (SpaceState.PHANTOM != inputTransaction.getMSpaceState()) {
|
||||||
needsPrecedingSpace = false;
|
needsPrecedingSpace = false;
|
||||||
} else if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
|
} else if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
|
||||||
// Double quotes behave like they are usually preceded by space iff we are
|
// Double quotes behave like they are usually preceded by space iff we are
|
||||||
|
@ -954,7 +954,7 @@ public final class InputLogic {
|
||||||
sendKeyCodePoint(settingsValues, codePoint);
|
sendKeyCodePoint(settingsValues, codePoint);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ((SpaceState.PHANTOM == inputTransaction.mSpaceState
|
if ((SpaceState.PHANTOM == inputTransaction.getMSpaceState()
|
||||||
&& settingsValues.isUsuallyFollowedBySpace(codePoint))
|
&& settingsValues.isUsuallyFollowedBySpace(codePoint))
|
||||||
|| (Constants.CODE_DOUBLE_QUOTE == codePoint
|
|| (Constants.CODE_DOUBLE_QUOTE == codePoint
|
||||||
&& isInsideDoubleQuoteOrAfterDigit)) {
|
&& isInsideDoubleQuoteOrAfterDigit)) {
|
||||||
|
@ -1008,7 +1008,7 @@ public final class InputLogic {
|
||||||
// If we are in the middle of a recorrection, we need to commit the recorrection
|
// 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.
|
// 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.
|
// 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);
|
Constants.EVENT_BACKSPACE);
|
||||||
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
resetEntireInputState(mConnection.getExpectedSelectionStart(),
|
||||||
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);
|
||||||
|
@ -1020,7 +1020,7 @@ public final class InputLogic {
|
||||||
mWordComposer.reset();
|
mWordComposer.reset();
|
||||||
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
|
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
|
||||||
if (!TextUtils.isEmpty(rejectedSuggestion)) {
|
if (!TextUtils.isEmpty(rejectedSuggestion)) {
|
||||||
unlearnWord(rejectedSuggestion, inputTransaction.mSettingsValues,
|
unlearnWord(rejectedSuggestion, inputTransaction.getMSettingsValues(),
|
||||||
Constants.EVENT_REJECTION);
|
Constants.EVENT_REJECTION);
|
||||||
}
|
}
|
||||||
StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
|
StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
|
||||||
|
@ -1037,7 +1037,7 @@ public final class InputLogic {
|
||||||
} else {
|
} else {
|
||||||
if (mLastComposedWord.canRevertCommit()) {
|
if (mLastComposedWord.canRevertCommit()) {
|
||||||
final String lastComposedWord = mLastComposedWord.mTypedWord;
|
final String lastComposedWord = mLastComposedWord.mTypedWord;
|
||||||
revertCommit(inputTransaction, inputTransaction.mSettingsValues);
|
revertCommit(inputTransaction, inputTransaction.getMSettingsValues());
|
||||||
StatsUtils.onRevertAutoCorrect();
|
StatsUtils.onRevertAutoCorrect();
|
||||||
StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode());
|
StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode());
|
||||||
// Restart suggestions when backspacing into a reverted word. This is required for
|
// 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
|
// Note: restartSuggestionsOnWordTouchedByCursor is already called for normal
|
||||||
// (non-revert) backspace handling.
|
// (non-revert) backspace handling.
|
||||||
if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
|
if (inputTransaction.getMSettingsValues().isSuggestionsEnabledPerUserSettings()
|
||||||
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
|
&& inputTransaction.getMSettingsValues().mSpacingAndPunctuations
|
||||||
.mCurrentLanguageHasSpaces
|
.mCurrentLanguageHasSpaces
|
||||||
&& !mConnection.isCursorFollowedByWordCharacter(
|
&& !mConnection.isCursorFollowedByWordCharacter(
|
||||||
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
|
inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) {
|
||||||
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
|
restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(),
|
||||||
false /* forStartInput */, currentKeyboardScriptId);
|
false /* forStartInput */, currentKeyboardScriptId);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -1068,10 +1068,10 @@ public final class InputLogic {
|
||||||
// reverting any autocorrect at this point. So we can safely return.
|
// reverting any autocorrect at this point. So we can safely return.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
|
if (SpaceState.DOUBLE == inputTransaction.getMSpaceState()) {
|
||||||
cancelDoubleSpacePeriodCountdown();
|
cancelDoubleSpacePeriodCountdown();
|
||||||
if (mConnection.revertDoubleSpacePeriod(
|
if (mConnection.revertDoubleSpacePeriod(
|
||||||
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
|
inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) {
|
||||||
// No need to reset mSpaceState, it has already be done (that's why we
|
// No need to reset mSpaceState, it has already be done (that's why we
|
||||||
// receive it as a parameter)
|
// receive it as a parameter)
|
||||||
inputTransaction.setRequiresUpdateSuggestions();
|
inputTransaction.setRequiresUpdateSuggestions();
|
||||||
|
@ -1080,7 +1080,7 @@ public final class InputLogic {
|
||||||
StatsUtils.onRevertDoubleSpacePeriod();
|
StatsUtils.onRevertDoubleSpacePeriod();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
|
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.getMSpaceState()) {
|
||||||
if (mConnection.revertSwapPunctuation()) {
|
if (mConnection.revertSwapPunctuation()) {
|
||||||
StatsUtils.onRevertSwapPunctuation();
|
StatsUtils.onRevertSwapPunctuation();
|
||||||
// Likewise
|
// Likewise
|
||||||
|
@ -1097,7 +1097,7 @@ public final class InputLogic {
|
||||||
// We also need to unlearn the selected text.
|
// We also need to unlearn the selected text.
|
||||||
final CharSequence selection = mConnection.getSelectedText(0 /* 0 for no styles */);
|
final CharSequence selection = mConnection.getSelectedText(0 /* 0 for no styles */);
|
||||||
if (!TextUtils.isEmpty(selection)) {
|
if (!TextUtils.isEmpty(selection)) {
|
||||||
unlearnWord(selection.toString(), inputTransaction.mSettingsValues,
|
unlearnWord(selection.toString(), inputTransaction.getMSettingsValues(),
|
||||||
Constants.EVENT_BACKSPACE);
|
Constants.EVENT_BACKSPACE);
|
||||||
hasUnlearnedWordBeingDeleted = true;
|
hasUnlearnedWordBeingDeleted = true;
|
||||||
}
|
}
|
||||||
|
@ -1109,8 +1109,8 @@ public final class InputLogic {
|
||||||
StatsUtils.onBackspaceSelectedText(numCharsDeleted);
|
StatsUtils.onBackspaceSelectedText(numCharsDeleted);
|
||||||
} else {
|
} else {
|
||||||
// There is no selection, just delete one character.
|
// There is no selection, just delete one character.
|
||||||
if (inputTransaction.mSettingsValues.isBeforeJellyBean()
|
if (inputTransaction.getMSettingsValues().isBeforeJellyBean()
|
||||||
|| inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()
|
|| inputTransaction.getMSettingsValues().mInputAttributes.isTypeNull()
|
||||||
|| Constants.NOT_A_CURSOR_POSITION
|
|| Constants.NOT_A_CURSOR_POSITION
|
||||||
== mConnection.getExpectedSelectionEnd()) {
|
== mConnection.getExpectedSelectionEnd()) {
|
||||||
// There are three possible reasons to send a key event: either the field has
|
// 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
|
// consider unlearning here because we may have already reached
|
||||||
// the previous word, and will lose it after next deletion.
|
// the previous word, and will lose it after next deletion.
|
||||||
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
||||||
inputTransaction.mSettingsValues, currentKeyboardScriptId);
|
inputTransaction.getMSettingsValues(), currentKeyboardScriptId);
|
||||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
|
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
|
||||||
totalDeletedLength++;
|
totalDeletedLength++;
|
||||||
}
|
}
|
||||||
|
@ -1158,7 +1158,7 @@ public final class InputLogic {
|
||||||
// consider unlearning here because we may have already reached
|
// consider unlearning here because we may have already reached
|
||||||
// the previous word, and will lose it after next deletion.
|
// the previous word, and will lose it after next deletion.
|
||||||
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
||||||
inputTransaction.mSettingsValues, currentKeyboardScriptId);
|
inputTransaction.getMSettingsValues(), currentKeyboardScriptId);
|
||||||
final int codePointBeforeCursorToDeleteAgain =
|
final int codePointBeforeCursorToDeleteAgain =
|
||||||
mConnection.getCodePointBeforeCursor();
|
mConnection.getCodePointBeforeCursor();
|
||||||
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
|
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
|
||||||
|
@ -1174,16 +1174,16 @@ public final class InputLogic {
|
||||||
if (!hasUnlearnedWordBeingDeleted) {
|
if (!hasUnlearnedWordBeingDeleted) {
|
||||||
// Consider unlearning the word being deleted (if we have not done so already).
|
// Consider unlearning the word being deleted (if we have not done so already).
|
||||||
unlearnWordBeingDeleted(
|
unlearnWordBeingDeleted(
|
||||||
inputTransaction.mSettingsValues, currentKeyboardScriptId);
|
inputTransaction.getMSettingsValues(), currentKeyboardScriptId);
|
||||||
}
|
}
|
||||||
if (mConnection.hasSlowInputConnection()) {
|
if (mConnection.hasSlowInputConnection()) {
|
||||||
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
|
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
|
||||||
} else if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
|
} else if (inputTransaction.getMSettingsValues().isSuggestionsEnabledPerUserSettings()
|
||||||
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
|
&& inputTransaction.getMSettingsValues().mSpacingAndPunctuations
|
||||||
.mCurrentLanguageHasSpaces
|
.mCurrentLanguageHasSpaces
|
||||||
&& !mConnection.isCursorFollowedByWordCharacter(
|
&& !mConnection.isCursorFollowedByWordCharacter(
|
||||||
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
|
inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) {
|
||||||
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
|
restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(),
|
||||||
false /* forStartInput */, currentKeyboardScriptId);
|
false /* forStartInput */, currentKeyboardScriptId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1272,20 +1272,20 @@ public final class InputLogic {
|
||||||
*/
|
*/
|
||||||
private boolean tryStripSpaceAndReturnWhetherShouldSwapInstead(final Event event,
|
private boolean tryStripSpaceAndReturnWhetherShouldSwapInstead(final Event event,
|
||||||
final InputTransaction inputTransaction) {
|
final InputTransaction inputTransaction) {
|
||||||
final int codePoint = event.mCodePoint;
|
final int codePoint = event.getMCodePoint();
|
||||||
final boolean isFromSuggestionStrip = event.isSuggestionStripPress();
|
final boolean isFromSuggestionStrip = event.isSuggestionStripPress();
|
||||||
if (Constants.CODE_ENTER == codePoint &&
|
if (Constants.CODE_ENTER == codePoint &&
|
||||||
SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
|
SpaceState.SWAP_PUNCTUATION == inputTransaction.getMSpaceState()) {
|
||||||
mConnection.removeTrailingSpace();
|
mConnection.removeTrailingSpace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((SpaceState.WEAK == inputTransaction.mSpaceState
|
if ((SpaceState.WEAK == inputTransaction.getMSpaceState()
|
||||||
|| SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState)
|
|| SpaceState.SWAP_PUNCTUATION == inputTransaction.getMSpaceState())
|
||||||
&& isFromSuggestionStrip) {
|
&& isFromSuggestionStrip) {
|
||||||
if (inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(codePoint)) {
|
if (inputTransaction.getMSettingsValues().isUsuallyPrecededBySpace(codePoint)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint)) {
|
if (inputTransaction.getMSettingsValues().isUsuallyFollowedBySpace(codePoint)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
mConnection.removeTrailingSpace();
|
mConnection.removeTrailingSpace();
|
||||||
|
@ -1294,7 +1294,7 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startDoubleSpacePeriodCountdown(final InputTransaction inputTransaction) {
|
public void startDoubleSpacePeriodCountdown(final InputTransaction inputTransaction) {
|
||||||
mDoubleSpacePeriodCountdownStart = inputTransaction.mTimestamp;
|
mDoubleSpacePeriodCountdownStart = inputTransaction.getMTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelDoubleSpacePeriodCountdown() {
|
public void cancelDoubleSpacePeriodCountdown() {
|
||||||
|
@ -1302,8 +1302,8 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDoubleSpacePeriodCountdownActive(final InputTransaction inputTransaction) {
|
public boolean isDoubleSpacePeriodCountdownActive(final InputTransaction inputTransaction) {
|
||||||
return inputTransaction.mTimestamp - mDoubleSpacePeriodCountdownStart
|
return inputTransaction.getMTimestamp() - mDoubleSpacePeriodCountdownStart
|
||||||
< inputTransaction.mSettingsValues.mDoubleSpacePeriodTimeout;
|
< inputTransaction.getMSettingsValues().mDoubleSpacePeriodTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1326,8 +1326,8 @@ public final class InputLogic {
|
||||||
final InputTransaction inputTransaction) {
|
final InputTransaction inputTransaction) {
|
||||||
// Check the setting, the typed character and the countdown. If any of the conditions is
|
// Check the setting, the typed character and the countdown. If any of the conditions is
|
||||||
// not fulfilled, return false.
|
// not fulfilled, return false.
|
||||||
if (!inputTransaction.mSettingsValues.mUseDoubleSpacePeriod
|
if (!inputTransaction.getMSettingsValues().mUseDoubleSpacePeriod
|
||||||
|| Constants.CODE_SPACE != event.mCodePoint
|
|| Constants.CODE_SPACE != event.getMCodePoint()
|
||||||
|| !isDoubleSpacePeriodCountdownActive(inputTransaction)) {
|
|| !isDoubleSpacePeriodCountdownActive(inputTransaction)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1348,7 +1348,7 @@ public final class InputLogic {
|
||||||
if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
|
if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
|
||||||
cancelDoubleSpacePeriodCountdown();
|
cancelDoubleSpacePeriodCountdown();
|
||||||
mConnection.deleteTextBeforeCursor(1);
|
mConnection.deleteTextBeforeCursor(1);
|
||||||
final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
|
final String textToInsert = inputTransaction.getMSettingsValues().mSpacingAndPunctuations
|
||||||
.mSentenceSeparatorAndSpace;
|
.mSentenceSeparatorAndSpace;
|
||||||
mConnection.commitText(textToInsert, 1);
|
mConnection.commitText(textToInsert, 1);
|
||||||
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
|
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
|
||||||
|
@ -1662,7 +1662,7 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
mConnection.deleteTextBeforeCursor(deleteLength);
|
mConnection.deleteTextBeforeCursor(deleteLength);
|
||||||
if (!TextUtils.isEmpty(committedWord)) {
|
if (!TextUtils.isEmpty(committedWord)) {
|
||||||
unlearnWord(committedWordString, inputTransaction.mSettingsValues,
|
unlearnWord(committedWordString, inputTransaction.getMSettingsValues(),
|
||||||
Constants.EVENT_REVERT);
|
Constants.EVENT_REVERT);
|
||||||
}
|
}
|
||||||
final String stringToCommit = originallyTypedWord +
|
final String stringToCommit = originallyTypedWord +
|
||||||
|
@ -1696,13 +1696,13 @@ public final class InputLogic {
|
||||||
}
|
}
|
||||||
// Add the suggestion list to the list of suggestions.
|
// Add the suggestion list to the list of suggestions.
|
||||||
textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */,
|
textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */,
|
||||||
inputTransaction.mSettingsValues.mLocale,
|
inputTransaction.getMSettingsValues().mLocale,
|
||||||
suggestions.toArray(new String[suggestions.size()]), 0 /* flags */,
|
suggestions.toArray(new String[suggestions.size()]), 0 /* flags */,
|
||||||
null /* notificationTargetClass */),
|
null /* notificationTargetClass */),
|
||||||
0 /* start */, lastCharIndex /* end */, 0 /* flags */);
|
0 /* start */, lastCharIndex /* end */, 0 /* flags */);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
|
if (inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
|
||||||
mConnection.commitText(textToCommit, 1);
|
mConnection.commitText(textToCommit, 1);
|
||||||
if (usePhantomSpace) {
|
if (usePhantomSpace) {
|
||||||
mSpaceState = SpaceState.PHANTOM;
|
mSpaceState = SpaceState.PHANTOM;
|
||||||
|
|
|
@ -76,6 +76,9 @@
|
||||||
<color name="suggested_word_background_selected_lxx_dark">#19FFFFFF</color>
|
<color name="suggested_word_background_selected_lxx_dark">#19FFFFFF</color>
|
||||||
<color name="gesture_floating_preview_color_lxx_dark">#E621272B</color>
|
<color name="gesture_floating_preview_color_lxx_dark">#E621272B</color>
|
||||||
<color name="emoji_tab_page_indicator_background_lxx_dark">#21272B</color>
|
<color name="emoji_tab_page_indicator_background_lxx_dark">#21272B</color>
|
||||||
|
|
||||||
|
<color name="popup_background_material_dark_theme">#ff3c474c</color>
|
||||||
|
|
||||||
<!-- Color resources for setup wizard and tutorial -->
|
<!-- Color resources for setup wizard and tutorial -->
|
||||||
<color name="setup_background">#FFEBEBEB</color>
|
<color name="setup_background">#FFEBEBEB</color>
|
||||||
<color name="setup_text_dark">#FF707070</color>
|
<color name="setup_text_dark">#FF707070</color>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue