diff options
33 files changed, 1033 insertions, 382 deletions
diff --git a/java/res/drawable-hdpi/sym_bkeyboard_delete_rtl.png b/java/res/drawable-hdpi/sym_bkeyboard_delete_rtl.png Binary files differnew file mode 100644 index 000000000..102eac781 --- /dev/null +++ b/java/res/drawable-hdpi/sym_bkeyboard_delete_rtl.png diff --git a/java/res/drawable-hdpi/sym_keyboard_delete_rtl.png b/java/res/drawable-hdpi/sym_keyboard_delete_rtl.png Binary files differnew file mode 100644 index 000000000..a508452bd --- /dev/null +++ b/java/res/drawable-hdpi/sym_keyboard_delete_rtl.png diff --git a/java/res/drawable-hdpi/sym_keyboard_delete_rtl_holo.png b/java/res/drawable-hdpi/sym_keyboard_delete_rtl_holo.png Binary files differnew file mode 100644 index 000000000..a77e4a00e --- /dev/null +++ b/java/res/drawable-hdpi/sym_keyboard_delete_rtl_holo.png diff --git a/java/res/drawable-mdpi/sym_bkeyboard_delete_rtl.png b/java/res/drawable-mdpi/sym_bkeyboard_delete_rtl.png Binary files differnew file mode 100644 index 000000000..32253ea9b --- /dev/null +++ b/java/res/drawable-mdpi/sym_bkeyboard_delete_rtl.png diff --git a/java/res/drawable-mdpi/sym_keyboard_delete_rtl.png b/java/res/drawable-mdpi/sym_keyboard_delete_rtl.png Binary files differnew file mode 100644 index 000000000..d436c23a9 --- /dev/null +++ b/java/res/drawable-mdpi/sym_keyboard_delete_rtl.png diff --git a/java/res/drawable-mdpi/sym_keyboard_delete_rtl_holo.png b/java/res/drawable-mdpi/sym_keyboard_delete_rtl_holo.png Binary files differnew file mode 100644 index 000000000..2e75d8536 --- /dev/null +++ b/java/res/drawable-mdpi/sym_keyboard_delete_rtl_holo.png diff --git a/java/res/drawable-sw600dp-hdpi/sym_keyboard_delete_rtl_holo.png b/java/res/drawable-sw600dp-hdpi/sym_keyboard_delete_rtl_holo.png Binary files differnew file mode 100644 index 000000000..5c19f3258 --- /dev/null +++ b/java/res/drawable-sw600dp-hdpi/sym_keyboard_delete_rtl_holo.png diff --git a/java/res/drawable-sw600dp-mdpi/sym_keyboard_delete_rtl_holo.png b/java/res/drawable-sw600dp-mdpi/sym_keyboard_delete_rtl_holo.png Binary files differnew file mode 100644 index 000000000..994854106 --- /dev/null +++ b/java/res/drawable-sw600dp-mdpi/sym_keyboard_delete_rtl_holo.png diff --git a/java/res/drawable-sw600dp-xhdpi/sym_keyboard_delete_rtl_holo.png b/java/res/drawable-sw600dp-xhdpi/sym_keyboard_delete_rtl_holo.png Binary files differnew file mode 100644 index 000000000..f3310a1cd --- /dev/null +++ b/java/res/drawable-sw600dp-xhdpi/sym_keyboard_delete_rtl_holo.png diff --git a/java/res/drawable-xhdpi/sym_bkeyboard_delete_rtl.png b/java/res/drawable-xhdpi/sym_bkeyboard_delete_rtl.png Binary files differnew file mode 100644 index 000000000..23aee2b3b --- /dev/null +++ b/java/res/drawable-xhdpi/sym_bkeyboard_delete_rtl.png diff --git a/java/res/drawable-xhdpi/sym_keyboard_delete_rtl.png b/java/res/drawable-xhdpi/sym_keyboard_delete_rtl.png Binary files differnew file mode 100644 index 000000000..3f9d3dd65 --- /dev/null +++ b/java/res/drawable-xhdpi/sym_keyboard_delete_rtl.png diff --git a/java/res/drawable-xhdpi/sym_keyboard_delete_rtl_holo.png b/java/res/drawable-xhdpi/sym_keyboard_delete_rtl_holo.png Binary files differnew file mode 100644 index 000000000..1f4890c89 --- /dev/null +++ b/java/res/drawable-xhdpi/sym_keyboard_delete_rtl_holo.png diff --git a/java/res/values-sw600dp/donottranslate.xml b/java/res/values-sw600dp/donottranslate.xml deleted file mode 100644 index 6d94c2811..000000000 --- a/java/res/values-sw600dp/donottranslate.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** -** Copyright 2011, 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. -*/ ---> -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Default value of the visibility of the suggestion strip --> - <string name="prefs_suggestion_visibility_default_value" translatable="false">1</string> -</resources> diff --git a/java/res/values-sw768dp/donottranslate.xml b/java/res/values-sw768dp/donottranslate.xml deleted file mode 100644 index 672dea589..000000000 --- a/java/res/values-sw768dp/donottranslate.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** -** Copyright 2010, 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. -*/ ---> -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Default value of the visibility of the suggestion strip --> - <string name="prefs_suggestion_visibility_default_value" translatable="false">2</string> -</resources> diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index d0d66d68b..fa5c08ba7 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -157,6 +157,7 @@ <attr name="iconToSymbolKey" format="reference" /> <attr name="iconToSymbolKeyWithShortcut" format="reference" /> <attr name="iconDeleteKey" format="reference" /> + <attr name="iconDeleteRtlKey" format="reference" /> <attr name="iconSettingsKey" format="reference" /> <attr name="iconShortcutKey" format="reference" /> <attr name="iconSpaceKey" format="reference" /> @@ -218,25 +219,26 @@ <enum name="iconToSymbolKey" value="2" /> <enum name="iconToSymbolKeyWithShortcut" value="3" /> <enum name="iconDeleteKey" value="4" /> - <enum name="iconSettingsKey" value="5" /> - <enum name="iconShortcutKey" value="6" /> - <enum name="iconSpaceKey" value="7" /> - <enum name="iconReturnKey" value="8" /> - <enum name="iconSearchKey" value="9" /> - <enum name="iconTabKey" value="10" /> + <enum name="iconDeleteRtlKey" value="5" /> + <enum name="iconSettingsKey" value="6" /> + <enum name="iconShortcutKey" value="7" /> + <enum name="iconSpaceKey" value="8" /> + <enum name="iconReturnKey" value="9" /> + <enum name="iconSearchKey" value="10" /> + <enum name="iconTabKey" value="11" /> </attr> <!-- Shift key icon for shifted state --> <attr name="keyIconShifted" format="enum"> <!-- This should be aligned with KeyboardIcons.ICON_SHIFTED_* --> - <enum name="iconShiftedShiftKey" value="11" /> + <enum name="iconShiftedShiftKey" value="12" /> </attr> <!-- The icon to show in the popup preview. --> <attr name="keyIconPreview" format="enum"> <!-- This should be aligned with KeyboardIcons.ICON_PREVIEW_* --> - <enum name="iconPreviewSpaceKey" value="12" /> - <enum name="iconPreviewTabKey" value="13" /> - <enum name="iconPreviewSettingsKey" value="14" /> - <enum name="iconPreviewShortcutKey" value="15" /> + <enum name="iconPreviewSpaceKey" value="13" /> + <enum name="iconPreviewTabKey" value="14" /> + <enum name="iconPreviewSettingsKey" value="15" /> + <enum name="iconPreviewShortcutKey" value="16" /> </attr> <!-- The key style to specify a set of key attributes defined by <key_style/> --> <attr name="keyStyle" format="string" /> diff --git a/java/res/values/keyboard-icons-black.xml b/java/res/values/keyboard-icons-black.xml index e2c688995..313bf02e1 100644 --- a/java/res/values/keyboard-icons-black.xml +++ b/java/res/values/keyboard-icons-black.xml @@ -18,12 +18,14 @@ <style name="KeyboardIcons.Black"> <!-- Keyboard icons --> <!-- TODO: The following holo icon for phone (drawable-hdpi and drawable-xhdpi) are too - large for phone. + ambiguous. sym_bkeyboard_voice_off --> <item name="iconShiftKey">@drawable/sym_bkeyboard_shift</item> <item name="iconToSymbolKeyWithShortcut">@drawable/sym_bkeyboard_123_mic</item> <item name="iconDeleteKey">@drawable/sym_bkeyboard_delete</item> + <!-- TODO: update this icon drawable --> + <item name="iconDeleteRtlKey">@drawable/sym_bkeyboard_delete_rtl</item> <item name="iconSettingsKey">@drawable/sym_bkeyboard_settings</item> <item name="iconShortcutKey">@drawable/sym_bkeyboard_mic</item> <item name="iconSpaceKey">@drawable/sym_bkeyboard_space</item> diff --git a/java/res/values/keyboard-icons-ics.xml b/java/res/values/keyboard-icons-ics.xml index bf2d13d30..a2bfd7a2f 100644 --- a/java/res/values/keyboard-icons-ics.xml +++ b/java/res/values/keyboard-icons-ics.xml @@ -17,27 +17,14 @@ <resources> <style name="KeyboardIcons.IceCreamSandwich"> <!-- Keyboard icons --> - <!-- TODO: The following holo icon for phone (drawable-hdpi and drawable-xhdpi) are too - large for phone. - sym_keyboard_shift_holo - sym_keyboard_shift_locked_holo - sym_keyboard_delete_holo, - sym_keyboard_settings_holo - sym_keyboard_voice_holo - sym_keyboard_voice_holo_off - sym_keyboard_space_holo - sym_keyboard_return_holo - sym_keyboard_tab_holo - --> <!-- TODO: The following holo icon for phone (drawable-hdpi and drawable-xhdpi) are missing. sym_keyboard_123_mic_holo - sym_keyboard_search_holo - sym_keyboard_feedback_tab_holo - sym_keyboard_feedback_voice_holo --> <item name="iconShiftKey">@drawable/sym_keyboard_shift_holo</item> <item name="iconToSymbolKeyWithShortcut">@drawable/sym_keyboard_123_mic_holo</item> <item name="iconDeleteKey">@drawable/sym_keyboard_delete_holo</item> + <!-- TODO: update this icon drawable --> + <item name="iconDeleteRtlKey">@drawable/sym_keyboard_delete_rtl_holo</item> <item name="iconSettingsKey">@drawable/sym_keyboard_settings_holo</item> <item name="iconShortcutKey">@drawable/sym_keyboard_voice_holo</item> <item name="iconSpaceKey">@drawable/sym_keyboard_space_holo</item> diff --git a/java/res/values/keyboard-icons-white.xml b/java/res/values/keyboard-icons-white.xml index 8bc7539fa..b6fa89026 100644 --- a/java/res/values/keyboard-icons-white.xml +++ b/java/res/values/keyboard-icons-white.xml @@ -20,6 +20,8 @@ <item name="iconShiftKey">@drawable/sym_keyboard_shift</item> <item name="iconToSymbolKeyWithShortcut">@drawable/sym_keyboard_123_mic</item> <item name="iconDeleteKey">@drawable/sym_keyboard_delete</item> + <!-- TODO: update this icon drawable --> + <item name="iconDeleteRtlKey">@drawable/sym_keyboard_delete_rtl</item> <item name="iconSettingsKey">@drawable/sym_keyboard_settings</item> <item name="iconShortcutKey">@drawable/sym_keyboard_mic</item> <item name="iconSpaceKey">@drawable/sym_keyboard_space</item> diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml index 3d8600391..cc949c9dc 100644 --- a/java/res/xml/kbd_key_styles.xml +++ b/java/res/xml/kbd_key_styles.xml @@ -59,12 +59,24 @@ latin:keyIconShifted="iconShiftedShiftKey" latin:parentStyle="functionalKeyStyle" latin:isSticky="true" /> - <key-style - latin:styleName="deleteKeyStyle" - latin:code="@integer/key_delete" - latin:keyIcon="iconDeleteKey" - latin:parentStyle="functionalKeyStyle" - latin:isRepeatable="true" /> + <switch> + <case latin:localeCode="ar|iw"> + <key-style + latin:styleName="deleteKeyStyle" + latin:code="@integer/key_delete" + latin:keyIcon="iconDeleteRtlKey" + latin:parentStyle="functionalKeyStyle" + latin:isRepeatable="true" /> + </case> + <default> + <key-style + latin:styleName="deleteKeyStyle" + latin:code="@integer/key_delete" + latin:keyIcon="iconDeleteKey" + latin:parentStyle="functionalKeyStyle" + latin:isRepeatable="true" /> + </default> + </switch> <!-- Return key style --> <switch> <case diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java index b512f5ac7..b807dd325 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java @@ -21,6 +21,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Message; +import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; @@ -36,11 +37,9 @@ import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder; -import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; -import java.util.ArrayList; import java.util.WeakHashMap; /** @@ -65,18 +64,14 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke // Mini keyboard private PopupWindow mPopupWindow; - private PopupPanel mPopupMiniKeyboardPanel; + private PopupPanel mPopupPanel; + private int mPopupPanelPointerTrackerId; private final WeakHashMap<Key, PopupPanel> mPopupPanelCache = new WeakHashMap<Key, PopupPanel>(); /** Listener for {@link KeyboardActionListener}. */ private KeyboardActionListener mKeyboardActionListener; - private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>(); - - // TODO: Let the PointerTracker class manage this pointer queue - private final PointerTrackerQueue mPointerQueue; - private final boolean mHasDistinctMultitouch; private int mOldPointerCount = 1; private int mOldKeyIndex; @@ -249,7 +244,7 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); - mPointerQueue = mHasDistinctMultitouch ? new PointerTrackerQueue() : null; + PointerTracker.init(mHasDistinctMultitouch, getContext()); } public void startIgnoringDoubleTap() { @@ -259,9 +254,7 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke public void setKeyboardActionListener(KeyboardActionListener listener) { mKeyboardActionListener = listener; - for (PointerTracker tracker : mPointerTrackers) { - tracker.setKeyboardActionListener(listener); - } + PointerTracker.setKeyboardActionListener(listener); } /** @@ -304,17 +297,15 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke @Override public void setKeyboard(Keyboard keyboard) { if (getKeyboard() != null) { - dismissAllKeyPreviews(); + PointerTracker.dismissAllKeyPreviews(); } // Remove any pending messages, except dismissing preview mKeyTimerHandler.cancelKeyTimers(); super.setKeyboard(keyboard); mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); - for (PointerTracker tracker : mPointerTrackers) { - tracker.setKeyDetector(mKeyDetector); - } mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth()); + PointerTracker.setKeyDetector(mKeyDetector); mPopupPanelCache.clear(); } @@ -343,14 +334,6 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke return mKeyDetector.isProximityCorrectionEnabled(); } - // TODO: clean up this method. - private void dismissAllKeyPreviews() { - for (PointerTracker tracker : mPointerTrackers) { - tracker.setReleasedKeyGraphics(); - dismissKeyPreview(tracker); - } - } - @Override public void cancelAllMessages() { mKeyTimerHandler.cancelAllMessages(); @@ -363,15 +346,13 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke return false; } + // Check if we are already displaying popup panel. + if (mPopupPanel != null) + return false; final Key parentKey = tracker.getKey(keyIndex); if (parentKey == null) return false; - boolean result = onLongPress(parentKey, tracker); - if (result) { - dismissAllKeyPreviews(); - tracker.onLongPressed(); - } - return result; + return onLongPress(parentKey, tracker); } private void onLongPressShiftKey(PointerTracker tracker) { @@ -398,35 +379,6 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke final PopupMiniKeyboardView miniKeyboardView = (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view); - miniKeyboardView.setKeyboardActionListener(new KeyboardActionListener() { - @Override - public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { - mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y); - dismissMiniKeyboard(); - } - - @Override - public void onTextInput(CharSequence text) { - mKeyboardActionListener.onTextInput(text); - dismissMiniKeyboard(); - } - - @Override - public void onCancelInput() { - mKeyboardActionListener.onCancelInput(); - dismissMiniKeyboard(); - } - - @Override - public void onPress(int primaryCode, boolean withSliding) { - mKeyboardActionListener.onPress(primaryCode, withSliding); - } - @Override - public void onRelease(int primaryCode, boolean withSliding) { - mKeyboardActionListener.onRelease(primaryCode, withSliding); - } - }); - final Keyboard parentKeyboard = getKeyboard(); final Keyboard miniKeyboard = new MiniKeyboardBuilder( this, parentKeyboard.getPopupKeyboardResId(), parentKey, parentKeyboard).build(); @@ -440,7 +392,7 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke @Override protected boolean needsToDimKeyboard() { - return mPopupMiniKeyboardPanel != null; + return mPopupPanel != null; } /** @@ -466,38 +418,28 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke // Allow popup window to be drawn off the screen. mPopupWindow.setClippingEnabled(false); } - mPopupMiniKeyboardPanel = popupPanel; + mPopupPanel = popupPanel; + mPopupPanelPointerTrackerId = tracker.mPointerId; + + tracker.onLongPressed(); popupPanel.showPanel(this, parentKey, tracker, mPopupWindow); + final int translatedX = popupPanel.translateX(tracker.getLastX()); + final int translatedY = popupPanel.translateY(tracker.getLastY()); + tracker.onDownEvent(translatedX, translatedY, SystemClock.uptimeMillis(), popupPanel); invalidateAllKeys(); return true; } private PointerTracker getPointerTracker(final int id) { - final ArrayList<PointerTracker> pointers = mPointerTrackers; - final KeyboardActionListener listener = mKeyboardActionListener; - final Keyboard keyboard = getKeyboard(); - - // Create pointer trackers until we can get 'id+1'-th tracker, if needed. - for (int i = pointers.size(); i <= id; i++) { - final PointerTracker tracker = - new PointerTracker(i, getContext(), mKeyTimerHandler, mKeyDetector, this, - mPointerQueue); - if (keyboard != null) - tracker.setKeyDetector(mKeyDetector); - if (listener != null) - tracker.setKeyboardActionListener(listener); - pointers.add(tracker); - } - - return pointers.get(id); + return PointerTracker.getPointerTracker(id, this); } public boolean isInSlidingKeyInput() { - if (mPopupMiniKeyboardPanel != null) { - return mPopupMiniKeyboardPanel.isInSlidingKeyInput(); + if (mPopupPanel != null) { + return true; } else { - return mPointerQueue.isInSlidingKeyInput(); + return PointerTracker.isAnyInSlidingKeyInput(); } } @@ -521,9 +463,9 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke } // Gesture detector must be enabled only when mini-keyboard is not on the screen. - if (mPopupMiniKeyboardPanel == null && mGestureDetector != null + if (mPopupPanel == null && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) { - dismissAllKeyPreviews(); + PointerTracker.dismissAllKeyPreviews(); mKeyTimerHandler.cancelKeyTimers(); return true; } @@ -531,13 +473,13 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke final long eventTime = me.getEventTime(); final int index = me.getActionIndex(); final int id = me.getPointerId(index); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - - // Needs to be called after the gesture detector gets a turn, as it may have displayed the - // mini keyboard - if (mPopupMiniKeyboardPanel != null) { - return mPopupMiniKeyboardPanel.onTouchEvent(me); + final int x, y; + if (mPopupPanel != null && id == mPopupPanelPointerTrackerId) { + x = mPopupPanel.translateX((int)me.getX(index)); + y = mPopupPanel.translateY((int)me.getY(index)); + } else { + x = (int)me.getX(index); + y = (int)me.getY(index); } if (mKeyTimerHandler.isInKeyRepeat()) { @@ -585,7 +527,15 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke if (action == MotionEvent.ACTION_MOVE) { for (int i = 0; i < pointerCount; i++) { final PointerTracker tracker = getPointerTracker(me.getPointerId(i)); - tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime); + final int px, py; + if (mPopupPanel != null && tracker.mPointerId == mPopupPanelPointerTrackerId) { + px = mPopupPanel.translateX((int)me.getX(i)); + py = mPopupPanel.translateY((int)me.getY(i)); + } else { + px = (int)me.getX(i); + py = (int)me.getY(i); + } + tracker.onMoveEvent(px, py, eventTime); } } else { processMotionEvent(getPointerTracker(id), action, x, y, eventTime, this); @@ -621,10 +571,11 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke mPopupPanelCache.clear(); } - private boolean dismissMiniKeyboard() { + public boolean dismissMiniKeyboard() { if (mPopupWindow != null && mPopupWindow.isShowing()) { mPopupWindow.dismiss(); - mPopupMiniKeyboardPanel = null; + mPopupPanel = null; + mPopupPanelPointerTrackerId = -1; invalidateAllKeys(); return true; } diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index 39d607d95..5f5475ce8 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -96,8 +96,10 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { protected boolean onLongPress(Key key, PointerTracker tracker) { int primaryCode = key.mCode; if (primaryCode == Keyboard.CODE_SETTINGS) { + tracker.onLongPressed(); return invokeOnKey(Keyboard.CODE_SETTINGS_LONGPRESS); } else if (primaryCode == '0' && getLatinKeyboard().isPhoneKeyboard()) { + tracker.onLongPressed(); // Long pressing on 0 in phone number keypad gives you a '+'. return invokeOnKey('+'); } else { diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 29a575ad0..aa2f3af48 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -18,7 +18,6 @@ package com.android.inputmethod.keyboard; import android.content.Context; import android.content.res.Resources; -import android.os.SystemClock; import android.util.Log; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; @@ -26,6 +25,7 @@ import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -78,23 +78,24 @@ public class PointerTracker { public void cancelKeyTimers(); } - public final int mPointerId; - + private static KeyboardSwitcher sKeyboardSwitcher; + private static boolean sConfigSlidingKeyInputEnabled; // Timing constants - private final int mDelayBeforeKeyRepeatStart; - private final int mLongPressKeyTimeout; - private final int mLongPressShiftKeyTimeout; + private static int sDelayBeforeKeyRepeatStart; + private static int sLongPressKeyTimeout; + private static int sLongPressShiftKeyTimeout; + private static int sTouchNoiseThresholdMillis; + private static int sTouchNoiseThresholdDistanceSquared; + + private static final List<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); + private static PointerTrackerQueue sPointerTrackerQueue; + + public final int mPointerId; private DrawingProxy mDrawingProxy; private TimerProxy mTimerProxy; - private final PointerTrackerQueue mPointerTrackerQueue; private KeyDetector mKeyDetector; private KeyboardActionListener mListener = EMPTY_LISTENER; - private final KeyboardSwitcher mKeyboardSwitcher; - private final boolean mConfigSlidingKeyInputEnabled; - - private final int mTouchNoiseThresholdMillis; - private final int mTouchNoiseThresholdDistanceSquared; private Keyboard mKeyboard; private List<Key> mKeys; @@ -124,7 +125,7 @@ public class PointerTracker { private boolean mIsRepeatableKey; // true if this pointer is in sliding key input - private boolean mIsInSlidingKeyInput; + boolean mIsInSlidingKeyInput; // true if sliding key is allowed. private boolean mIsAllowedSlidingKeyInput; @@ -136,7 +137,7 @@ public class PointerTracker { // true if this pointer is in sliding language switch private boolean mIsInSlidingLanguageSwitch; private int mSpaceKeyIndex; - private final SubtypeSwitcher mSubtypeSwitcher; + private static SubtypeSwitcher sSubtypeSwitcher; // Empty {@link KeyboardActionListener} private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() { @@ -152,31 +153,72 @@ public class PointerTracker { public void onCancelInput() {} }; - public PointerTracker(int id, Context context, TimerProxy timerProxy, KeyDetector keyDetector, - DrawingProxy drawingProxy, PointerTrackerQueue queue) { - if (drawingProxy == null || timerProxy == null || keyDetector == null) - throw new NullPointerException(); - mPointerId = id; - mDrawingProxy = drawingProxy; - mTimerProxy = timerProxy; - mPointerTrackerQueue = queue; // This is null for non-distinct multi-touch device. - setKeyDetectorInner(keyDetector); - mKeyboardSwitcher = KeyboardSwitcher.getInstance(); + public static void init(boolean hasDistinctMultitouch, Context context) { + if (hasDistinctMultitouch) { + sPointerTrackerQueue = new PointerTrackerQueue(); + } else { + sPointerTrackerQueue = null; + } + final Resources res = context.getResources(); - mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled); - mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); - mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); - mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout); - mTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis); + sConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled); + sDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); + sLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); + sLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout); + sTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis); final float touchNoiseThresholdDistance = res.getDimension( R.dimen.config_touch_noise_threshold_distance); - mTouchNoiseThresholdDistanceSquared = (int)( + sTouchNoiseThresholdDistanceSquared = (int)( touchNoiseThresholdDistance * touchNoiseThresholdDistance); - mSubtypeSwitcher = SubtypeSwitcher.getInstance(); + sKeyboardSwitcher = KeyboardSwitcher.getInstance(); + sSubtypeSwitcher = SubtypeSwitcher.getInstance(); } - public void setKeyboardActionListener(KeyboardActionListener listener) { - mListener = listener; + public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) { + final List<PointerTracker> trackers = sTrackers; + + // Create pointer trackers until we can get 'id+1'-th tracker, if needed. + for (int i = trackers.size(); i <= id; i++) { + final PointerTracker tracker = new PointerTracker(i, handler); + trackers.add(tracker); + } + + return trackers.get(id); + } + + public static boolean isAnyInSlidingKeyInput() { + return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; + } + + public static void setKeyboardActionListener(KeyboardActionListener listener) { + for (final PointerTracker tracker : sTrackers) { + tracker.mListener = listener; + } + } + + public static void setKeyDetector(KeyDetector keyDetector) { + for (final PointerTracker tracker : sTrackers) { + tracker.setKeyDetectorInner(keyDetector); + // Mark that keyboard layout has been changed. + tracker.mKeyboardLayoutHasBeenChanged = true; + } + } + + public static void dismissAllKeyPreviews() { + for (final PointerTracker tracker : sTrackers) { + tracker.setReleasedKeyGraphics(); + tracker.dismissKeyPreview(); + } + } + + public PointerTracker(int id, KeyEventHandler handler) { + if (handler == null) + throw new NullPointerException(); + mPointerId = id; + setKeyDetectorInner(handler.getKeyDetector()); + mListener = handler.getKeyboardActionListener(); + mDrawingProxy = handler.getDrawingProxy(); + mTimerProxy = handler.getTimerProxy(); } // Returns true if keyboard has been changed by this callback. @@ -244,14 +286,6 @@ public class PointerTracker { mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; } - public void setKeyDetector(KeyDetector keyDetector) { - if (keyDetector == null) - throw new NullPointerException(); - setKeyDetectorInner(keyDetector); - // Mark that keyboard layout has been changed. - mKeyboardLayoutHasBeenChanged = true; - } - public boolean isInSlidingKeyInput() { return mIsInSlidingKeyInput; } @@ -361,15 +395,16 @@ public class PointerTracker { printTouchEvent("onDownEvent:", x, y, eventTime); mDrawingProxy = handler.getDrawingProxy(); + mTimerProxy = handler.getTimerProxy(); setKeyboardActionListener(handler.getKeyboardActionListener()); setKeyDetectorInner(handler.getKeyDetector()); // Naive up-to-down noise filter. final long deltaT = eventTime - mUpTime; - if (deltaT < mTouchNoiseThresholdMillis) { + if (deltaT < sTouchNoiseThresholdMillis) { final int dx = x - mLastX; final int dy = y - mLastY; final int distanceSquared = (dx * dx + dy * dy); - if (distanceSquared < mTouchNoiseThresholdDistanceSquared) { + if (distanceSquared < sTouchNoiseThresholdDistanceSquared) { if (DEBUG_MODE) Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT + " distance=" + distanceSquared); @@ -378,7 +413,7 @@ public class PointerTracker { } } - final PointerTrackerQueue queue = mPointerTrackerQueue; + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { if (isOnModifierKey(x, y)) { // Before processing a down event of modifier key, all pointers already being @@ -394,7 +429,7 @@ public class PointerTracker { int keyIndex = onDownKey(x, y, eventTime); // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding // from modifier key, or 3) this pointer is on mini-keyboard. - mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex) + mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex) || mKeyDetector instanceof MiniKeyboardKeyDetector; mKeyboardLayoutHasBeenChanged = false; mKeyAlreadyProcessed = false; @@ -495,8 +530,8 @@ public class PointerTracker { else if (isSpaceKey(keyIndex) && !mIsInSlidingLanguageSwitch && mKeyboard instanceof LatinKeyboard) { final LatinKeyboard keyboard = ((LatinKeyboard)mKeyboard); - if (mSubtypeSwitcher.useSpacebarLanguageSwitcher() - && mSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) { + if (sSubtypeSwitcher.useSpacebarLanguageSwitcher() + && sSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) { final int diff = x - mKeyX; if (keyboard.shouldTriggerSpacebarSlidingLanguageSwitch(diff)) { // Detect start sliding language switch. @@ -505,7 +540,7 @@ public class PointerTracker { keyboard.updateSpacebarPreviewIcon(diff); // Display spacebar slide language switcher. showKeyPreview(keyIndex); - final PointerTrackerQueue queue = mPointerTrackerQueue; + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) queue.releaseAllPointersExcept(this, eventTime, true); } @@ -533,7 +568,7 @@ public class PointerTracker { if (DEBUG_EVENT) printTouchEvent("onUpEvent :", x, y, eventTime); - final PointerTrackerQueue queue = mPointerTrackerQueue; + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { if (isModifier()) { // Before processing an up event of modifier key, all pointers already being @@ -598,10 +633,10 @@ public class PointerTracker { public void onLongPressed() { mKeyAlreadyProcessed = true; - final PointerTrackerQueue queue = mPointerTrackerQueue; + setReleasedKeyGraphics(); + dismissKeyPreview(); + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { - // TODO: Support chording + long-press input. - queue.releaseAllPointersExcept(this, SystemClock.uptimeMillis(), true); queue.remove(this); } } @@ -610,7 +645,7 @@ public class PointerTracker { if (DEBUG_EVENT) printTouchEvent("onCancelEvt:", x, y, eventTime); - final PointerTrackerQueue queue = mPointerTrackerQueue; + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { queue.releaseAllPointersExcept(this, eventTime, true); queue.remove(this); @@ -631,7 +666,7 @@ public class PointerTracker { if (key != null && key.mRepeatable) { dismissKeyPreview(); onRepeatKey(keyIndex); - mTimerProxy.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this); + mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, keyIndex, this); mIsRepeatableKey = true; } else { mIsRepeatableKey = false; @@ -685,16 +720,16 @@ public class PointerTracker { private void startLongPressTimer(int keyIndex) { Key key = getKey(keyIndex); if (key.mCode == Keyboard.CODE_SHIFT) { - mTimerProxy.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this); + mTimerProxy.startLongPressShiftTimer(sLongPressShiftKeyTimeout, keyIndex, this); } else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) { // We need not start long press timer on the key which has manual temporary upper case // code defined and the keyboard is in manual temporary upper case mode. return; - } else if (mKeyboardSwitcher.isInMomentarySwitchState()) { + } else if (sKeyboardSwitcher.isInMomentarySwitchState()) { // We use longer timeout for sliding finger input started from the symbols mode key. - mTimerProxy.startLongPressTimer(mLongPressKeyTimeout * 3, keyIndex, this); + mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, keyIndex, this); } else { - mTimerProxy.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); + mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, keyIndex, this); } } diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java index a3d9c0465..af8e59568 100644 --- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java @@ -18,26 +18,73 @@ package com.android.inputmethod.keyboard; import android.content.Context; import android.content.res.Resources; -import android.os.SystemClock; +import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.widget.PopupWindow; +import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; +import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.latin.R; /** * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting * key presses and touch movements. */ -public class PopupMiniKeyboardView extends LatinKeyboardBaseView implements PopupPanel { +public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel { private final int[] mCoordinates = new int[2]; private final boolean mConfigShowMiniKeyboardAtTouchedPoint; + private final KeyDetector mKeyDetector; + private final int mVerticalCorrection; + + private LatinKeyboardBaseView mParentKeyboardView; private int mOriginX; private int mOriginY; - private long mDownTime; + + private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy() { + @Override + public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {} + @Override + public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {} + @Override + public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) {} + @Override + public void cancelLongPressTimers() {} + @Override + public void cancelKeyTimers() {} + }; + + private final KeyboardActionListener mListner = new KeyboardActionListener() { + @Override + public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) { + mParentKeyboardView.getKeyboardActionListener() + .onCodeInput(primaryCode, keyCodes, x, y); + mParentKeyboardView.dismissMiniKeyboard(); + } + + @Override + public void onTextInput(CharSequence text) { + mParentKeyboardView.getKeyboardActionListener().onTextInput(text); + mParentKeyboardView.dismissMiniKeyboard(); + } + + @Override + public void onCancelInput() { + mParentKeyboardView.getKeyboardActionListener().onCancelInput(); + mParentKeyboardView.dismissMiniKeyboard(); + } + + @Override + public void onPress(int primaryCode, boolean withSliding) { + mParentKeyboardView.getKeyboardActionListener().onPress(primaryCode, withSliding); + } + @Override + public void onRelease(int primaryCode, boolean withSliding) { + mParentKeyboardView.getKeyboardActionListener().onRelease(primaryCode, withSliding); + } + }; public PopupMiniKeyboardView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.popupMiniKeyboardViewStyle); @@ -46,6 +93,12 @@ public class PopupMiniKeyboardView extends LatinKeyboardBaseView implements Popu public PopupMiniKeyboardView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); + mVerticalCorrection = a.getDimensionPixelOffset( + R.styleable.KeyboardView_verticalCorrection, 0); + a.recycle(); + final Resources res = context.getResources(); mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean( R.bool.config_show_mini_keyboard_at_touched_point); @@ -53,11 +106,37 @@ public class PopupMiniKeyboardView extends LatinKeyboardBaseView implements Popu mKeyDetector = new MiniKeyboardKeyDetector(res.getDimension( R.dimen.mini_keyboard_slide_allowance)); // Remove gesture detector on mini-keyboard - mGestureDetector = null; setKeyPreviewPopupEnabled(false, 0); } @Override + public void setKeyboard(Keyboard keyboard) { + super.setKeyboard(keyboard); + mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), + -getPaddingTop() + mVerticalCorrection); + } + + @Override + public KeyDetector getKeyDetector() { + return mKeyDetector; + } + + @Override + public KeyboardActionListener getKeyboardActionListener() { + return mListner; + } + + @Override + public DrawingProxy getDrawingProxy() { + return this; + } + + @Override + public TimerProxy getTimerProxy() { + return EMPTY_TIMER_PROXY; + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // Do nothing for the mini keyboard. } @@ -70,8 +149,9 @@ public class PopupMiniKeyboardView extends LatinKeyboardBaseView implements Popu } @Override - public void showPanel(KeyboardView parentKeyboardView, Key parentKey, + public void showPanel(LatinKeyboardBaseView parentKeyboardView, Key parentKey, PointerTracker tracker, PopupWindow window) { + mParentKeyboardView = parentKeyboardView; final View container = (View)getParent(); final MiniKeyboard miniKeyboard = (MiniKeyboard)getKeyboard(); final Keyboard parentKeyboard = parentKeyboardView.getKeyboard(); @@ -99,19 +179,15 @@ public class PopupMiniKeyboardView extends LatinKeyboardBaseView implements Popu mOriginX = x + container.getPaddingLeft() - mCoordinates[0]; mOriginY = y + container.getPaddingTop() - mCoordinates[1]; - mDownTime = SystemClock.uptimeMillis(); - - // Inject down event on the key to mini keyboard. - final MotionEvent downEvent = MotionEvent.obtain(mDownTime, mDownTime, - MotionEvent.ACTION_DOWN, pointX - mOriginX, - pointY + parentKey.mHeight / 2 - mOriginY, 0); - onTouchEvent(downEvent); - downEvent.recycle(); } @Override - public boolean onTouchEvent(MotionEvent me) { - me.offsetLocation(-mOriginX, -mOriginY); - return super.onTouchEvent(me); + public int translateX(int x) { + return x - mOriginX; + } + + @Override + public int translateY(int y) { + return y - mOriginY; } } diff --git a/java/src/com/android/inputmethod/keyboard/PopupPanel.java b/java/src/com/android/inputmethod/keyboard/PopupPanel.java index 2d9130fcb..f94d1c562 100644 --- a/java/src/com/android/inputmethod/keyboard/PopupPanel.java +++ b/java/src/com/android/inputmethod/keyboard/PopupPanel.java @@ -16,7 +16,6 @@ package com.android.inputmethod.keyboard; -import android.view.MotionEvent; import android.widget.PopupWindow; public interface PopupPanel extends PointerTracker.KeyEventHandler { @@ -27,19 +26,20 @@ public interface PopupPanel extends PointerTracker.KeyEventHandler { * @param tracker the pointer tracker that pressesd the parent key * @param window PopupWindow to be used to show this popup panel */ - public void showPanel(KeyboardView parentKeyboardView, Key parentKey, + public void showPanel(LatinKeyboardBaseView parentKeyboardView, Key parentKey, PointerTracker tracker, PopupWindow window); /** - * Check if the pointer is in siding key input mode. - * @return true if the pointer is sliding key input mode. + * Translate X-coordinate of touch event to the local X-coordinate of this PopupPanel. + * @param x the global X-coordinate + * @return the local X-coordinate to this PopupPanel */ - public boolean isInSlidingKeyInput(); + public int translateX(int x); /** - * The motion event handler. - * @param me the MotionEvent to be processed. - * @return true if the motion event is processed and should be consumed. + * Translate Y-coordinate of touch event to the local Y-coordinate of this PopupPanel. + * @param y the global Y-coordinate + * @return the local Y-coordinate to this PopupPanel */ - public boolean onTouchEvent(MotionEvent me); + public int translateY(int y); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java index 1530fed6f..535a6954c 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -34,21 +34,22 @@ public class KeyboardIconsSet { private static final int ICON_TO_SYMBOL_KEY = 2; private static final int ICON_TO_SYMBOL_KEY_WITH_SHORTCUT = 3; private static final int ICON_DELETE_KEY = 4; - private static final int ICON_SETTINGS_KEY = 5; - private static final int ICON_SHORTCUT_KEY = 6; - private static final int ICON_SPACE_KEY = 7; - private static final int ICON_RETURN_KEY = 8; - private static final int ICON_SEARCH_KEY = 9; - private static final int ICON_TAB_KEY = 10; + private static final int ICON_DELETE_RTL_KEY = 5; + private static final int ICON_SETTINGS_KEY = 6; + private static final int ICON_SHORTCUT_KEY = 7; + private static final int ICON_SPACE_KEY = 8; + private static final int ICON_RETURN_KEY = 9; + private static final int ICON_SEARCH_KEY = 10; + private static final int ICON_TAB_KEY = 11; // This should be aligned with Keyboard.keyIconShifted enum. - private static final int ICON_SHIFTED_SHIFT_KEY = 11; + private static final int ICON_SHIFTED_SHIFT_KEY = 12; // This should be aligned with Keyboard.keyIconPreview enum. - private static final int ICON_PREVIEW_SPACE_KEY = 12; - private static final int ICON_PREVIEW_TAB_KEY = 13; - private static final int ICON_PREVIEW_SETTINGS_KEY = 14; - private static final int ICON_PREVIEW_SHORTCUT_KEY = 15; + private static final int ICON_PREVIEW_SPACE_KEY = 13; + private static final int ICON_PREVIEW_TAB_KEY = 14; + private static final int ICON_PREVIEW_SETTINGS_KEY = 15; + private static final int ICON_PREVIEW_SHORTCUT_KEY = 16; - private static final int ICON_LAST = 15; + private static final int ICON_LAST = 16; private final Drawable mIcons[] = new Drawable[ICON_LAST + 1]; @@ -62,6 +63,8 @@ public class KeyboardIconsSet { return ICON_TO_SYMBOL_KEY_WITH_SHORTCUT; case R.styleable.Keyboard_iconDeleteKey: return ICON_DELETE_KEY; + case R.styleable.Keyboard_iconDeleteRtlKey: + return ICON_DELETE_RTL_KEY; case R.styleable.Keyboard_iconSettingsKey: return ICON_SETTINGS_KEY; case R.styleable.Keyboard_iconShortcutKey: diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java index f87cd869e..545b27fdc 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java @@ -63,7 +63,7 @@ public class PointerTrackerQueue { mQueue.remove(tracker); } - public boolean isInSlidingKeyInput() { + public boolean isAnyInSlidingKeyInput() { for (final PointerTracker tracker : mQueue) { if (tracker.isInSlidingKeyInput()) return true; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index cec30eaab..5304d830d 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1803,19 +1803,18 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar final InputConnection ic = getCurrentInputConnection(); final CharSequence punctuation = ic.getTextBeforeCursor(1, 0); if (deleteChar) ic.deleteSurroundingText(1, 0); - int toDelete = mCommittedLength; - final CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); - if (!TextUtils.isEmpty(toTheLeft) - && mSettingsValues.isWordSeparator(toTheLeft.charAt(0))) { - toDelete--; - } - ic.deleteSurroundingText(toDelete, 0); + final CharSequence textToTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); + final int toDeleteLength = (!TextUtils.isEmpty(textToTheLeft) + && mSettingsValues.isWordSeparator(textToTheLeft.charAt(0))) + ? mCommittedLength - 1 : mCommittedLength; + ic.deleteSurroundingText(toDeleteLength, 0); + // Re-insert punctuation only when the deleted character was word separator and the // composing text wasn't equal to the auto-corrected text. if (deleteChar && !TextUtils.isEmpty(punctuation) && mSettingsValues.isWordSeparator(punctuation.charAt(0)) - && !TextUtils.equals(mComposingStringBuilder, toTheLeft)) { + && !TextUtils.equals(mComposingStringBuilder, textToTheLeft)) { ic.commitText(mComposingStringBuilder, 1); TextEntryState.acceptedTyped(mComposingStringBuilder); ic.commitText(punctuation, 1); diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index c06bd736e..2aaa26c8d 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -16,12 +16,14 @@ package com.android.inputmethod.latin; +import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; +import android.os.RemoteException; import android.provider.UserDictionary.Words; public class UserDictionary extends ExpandableDictionary { @@ -99,24 +101,34 @@ public class UserDictionary extends ExpandableDictionary { values.put(Words.APP_ID, 0); final ContentResolver contentResolver = getContext().getContentResolver(); + final ContentProviderClient client = + contentResolver.acquireContentProviderClient(Words.CONTENT_URI); + if (null == client) return; new Thread("addWord") { @Override public void run() { - Cursor cursor = contentResolver.query(Words.CONTENT_URI, PROJECTION_ADD, - "word=? and ((locale IS NULL) or (locale=?))", - new String[] { word, mLocale }, null); - if (cursor != null && cursor.moveToFirst()) { - String locale = cursor.getString(cursor.getColumnIndex(Words.LOCALE)); - // If locale is null, we will not override the entry. - if (locale != null && locale.equals(mLocale.toString())) { - long id = cursor.getLong(cursor.getColumnIndex(Words._ID)); - Uri uri = Uri.withAppendedPath(Words.CONTENT_URI, Long.toString(id)); - // Update the entry with new frequency value. - contentResolver.update(uri, values, null, null); + try { + final Cursor cursor = client.query(Words.CONTENT_URI, PROJECTION_ADD, + "word=? and ((locale IS NULL) or (locale=?))", + new String[] { word, mLocale }, null); + if (cursor != null && cursor.moveToFirst()) { + final String locale = cursor.getString(cursor.getColumnIndex(Words.LOCALE)); + // If locale is null, we will not override the entry. + if (locale != null && locale.equals(mLocale.toString())) { + final long id = cursor.getLong(cursor.getColumnIndex(Words._ID)); + final Uri uri = + Uri.withAppendedPath(Words.CONTENT_URI, Long.toString(id)); + // Update the entry with new frequency value. + client.update(uri, values, null, null); + } + } else { + // Insert new entry. + client.insert(Words.CONTENT_URI, values); } - } else { - // Insert new entry. - contentResolver.insert(Words.CONTENT_URI, values); + } catch (RemoteException e) { + // If we come here, the activity is already about to be killed, and we + // have no means of contacting the content provider any more. + // See ContentResolver#insert, inside the catch(){} } } }.start(); diff --git a/native/Android.mk b/native/Android.mk index d9f4f1d38..c22191319 100644 --- a/native/Android.mk +++ b/native/Android.mk @@ -19,6 +19,7 @@ LOCAL_SRC_FILES := \ src/unigram_dictionary.cpp #FLAG_DBG := true +#FLAG_DO_PROFILE := true TARGETING_UNBUNDLED_FROYO := true @@ -39,10 +40,15 @@ LOCAL_MODULE := libjni_latinime LOCAL_MODULE_TAGS := user +ifeq ($(FLAG_DO_PROFILE), true) + $(warning Making profiling version of native library) + LOCAL_CFLAGS += -DFLAG_DO_PROFILE +else # FLAG_DO_PROFILE ifeq ($(FLAG_DBG), true) $(warning Making debug version of native library) LOCAL_CFLAGS += -DFLAG_DBG LOCAL_SHARED_LIBRARIES := libcutils libutils -endif +endif # FLAG_DBG +endif # FLAG_DO_PROFILE include $(BUILD_SHARED_LIBRARY) diff --git a/native/src/binary_format.h b/native/src/binary_format.h new file mode 100644 index 000000000..e9f108e25 --- /dev/null +++ b/native/src/binary_format.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef LATINIME_BINARY_FORMAT_H +#define LATINIME_BINARY_FORMAT_H + +namespace latinime { + +class BinaryFormat { +private: + const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; + const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F; + const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2; + +public: + static int getGroupCountAndForwardPointer(const uint8_t* const dict, int* pos); + static uint8_t getFlagsAndForwardPointer(const uint8_t* const dict, int* pos); + static int32_t getCharCodeAndForwardPointer(const uint8_t* const dict, int* pos); + static int readFrequencyWithoutMovingPointer(const uint8_t* const dict, const int pos); + static int skipOtherCharacters(const uint8_t* const dict, const int pos); + static int skipAttributes(const uint8_t* const dict, const int pos); + static int skipChildrenPosition(const uint8_t flags, const int pos); + static int skipFrequency(const uint8_t flags, const int pos); + static int skipAllAttributes(const uint8_t* const dict, const uint8_t flags, const int pos); + static int skipChildrenPosAndAttributes(const uint8_t* const dict, const uint8_t flags, + const int pos); + static int readChildrenPosition(const uint8_t* const dict, const uint8_t flags, const int pos); + static bool hasChildrenInFlags(const uint8_t flags); + static int getAttributeAddressAndForwardPointer(const uint8_t* const dict, const uint8_t flags, + int *pos); +}; + +inline int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t* const dict, int* pos) { + return dict[(*pos)++]; +} + +inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t* const dict, int* pos) { + return dict[(*pos)++]; +} + +inline int32_t BinaryFormat::getCharCodeAndForwardPointer(const uint8_t* const dict, int* pos) { + const int origin = *pos; + const int32_t character = dict[origin]; + if (character < MINIMAL_ONE_BYTE_CHARACTER_VALUE) { + if (character == CHARACTER_ARRAY_TERMINATOR) { + *pos = origin + 1; + return NOT_A_CHARACTER; + } else { + *pos = origin + 3; + const int32_t char_1 = character << 16; + const int32_t char_2 = char_1 + (dict[origin + 1] << 8); + return char_2 + dict[origin + 2]; + } + } else { + *pos = origin + 1; + return character; + } +} + +inline int BinaryFormat::readFrequencyWithoutMovingPointer(const uint8_t* const dict, + const int pos) { + return dict[pos]; +} + +inline int BinaryFormat::skipOtherCharacters(const uint8_t* const dict, const int pos) { + int currentPos = pos; + int32_t character = dict[currentPos++]; + while (CHARACTER_ARRAY_TERMINATOR != character) { + if (character < MINIMAL_ONE_BYTE_CHARACTER_VALUE) { + currentPos += MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE; + } + character = dict[currentPos++]; + } + return currentPos; +} + +static inline int attributeAddressSize(const uint8_t flags) { + static const int ATTRIBUTE_ADDRESS_SHIFT = 4; + return (flags & UnigramDictionary::MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT; + /* Note: this is a value-dependant optimization of what may probably be + more readably written this way: + switch (flags * UnigramDictionary::MASK_ATTRIBUTE_ADDRESS_TYPE) { + case UnigramDictionary::FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1; + case UnigramDictionary::FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2; + case UnigramDictionary::FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3; + default: return 0; + } + */ +} + +inline int BinaryFormat::skipAttributes(const uint8_t* const dict, const int pos) { + int currentPos = pos; + uint8_t flags = getFlagsAndForwardPointer(dict, ¤tPos); + while (flags & UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT) { + currentPos += attributeAddressSize(flags); + flags = getFlagsAndForwardPointer(dict, ¤tPos); + } + currentPos += attributeAddressSize(flags); + return currentPos; +} + +static inline int childrenAddressSize(const uint8_t flags) { + static const int CHILDREN_ADDRESS_SHIFT = 6; + return (UnigramDictionary::MASK_GROUP_ADDRESS_TYPE & flags) >> CHILDREN_ADDRESS_SHIFT; + /* See the note in attributeAddressSize. The same applies here */ +} + +inline int BinaryFormat::skipChildrenPosition(const uint8_t flags, const int pos) { + return pos + childrenAddressSize(flags); +} + +inline int BinaryFormat::skipFrequency(const uint8_t flags, const int pos) { + return UnigramDictionary::FLAG_IS_TERMINAL & flags ? pos + 1 : pos; +} + +inline int BinaryFormat::skipAllAttributes(const uint8_t* const dict, const uint8_t flags, + const int pos) { + // This function skips all attributes. The format makes provision for future extension + // with other attributes (notably shortcuts) but for the time being, bigrams are the + // only attributes that may be found in a character group, so we only look at bigrams + // in this version. + if (UnigramDictionary::FLAG_HAS_BIGRAMS & flags) { + return skipAttributes(dict, pos); + } else { + return pos; + } +} + +inline int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t* const dict, + const uint8_t flags, const int pos) { + int currentPos = pos; + currentPos = skipChildrenPosition(flags, currentPos); + currentPos = skipAllAttributes(dict, flags, currentPos); + return currentPos; +} + +inline int BinaryFormat::readChildrenPosition(const uint8_t* const dict, const uint8_t flags, + const int pos) { + int offset = 0; + switch (UnigramDictionary::MASK_GROUP_ADDRESS_TYPE & flags) { + case UnigramDictionary::FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: + offset = dict[pos]; + break; + case UnigramDictionary::FLAG_GROUP_ADDRESS_TYPE_TWOBYTES: + offset = dict[pos] << 8; + offset += dict[pos + 1]; + break; + case UnigramDictionary::FLAG_GROUP_ADDRESS_TYPE_THREEBYTES: + offset = dict[pos] << 16; + offset += dict[pos + 1] << 8; + offset += dict[pos + 2]; + break; + default: + // If we come here, it means we asked for the children of a word with + // no children. + return -1; + } + return pos + offset; +} + +inline bool BinaryFormat::hasChildrenInFlags(const uint8_t flags) { + return (UnigramDictionary::FLAG_GROUP_ADDRESS_TYPE_NOADDRESS + != (UnigramDictionary::MASK_GROUP_ADDRESS_TYPE & flags)); +} + +inline int BinaryFormat::getAttributeAddressAndForwardPointer(const uint8_t* const dict, + const uint8_t flags, int *pos) { + int offset = 0; + const int origin = *pos; + switch (UnigramDictionary::MASK_ATTRIBUTE_ADDRESS_TYPE & flags) { + case UnigramDictionary::FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: + offset = dict[origin]; + *pos = origin + 1; + break; + case UnigramDictionary::FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: + offset = dict[origin] << 8; + offset += dict[origin + 1]; + *pos = origin + 2; + break; + case UnigramDictionary::FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES: + offset = dict[origin] << 16; + offset += dict[origin + 1] << 8; + offset += dict[origin + 2]; + *pos = origin + 3; + break; + } + if (UnigramDictionary::FLAG_ATTRIBUTE_OFFSET_NEGATIVE & flags) { + return origin - offset; + } else { + return origin + offset; + } +} + +} // namespace latinime + +#endif // LATINIME_BINARY_FORMAT_H diff --git a/native/src/defines.h b/native/src/defines.h index 0a3240507..a516190af 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -18,18 +18,7 @@ #ifndef LATINIME_DEFINES_H #define LATINIME_DEFINES_H -#ifdef FLAG_DBG -#include <cutils/log.h> -#ifndef LOG_TAG -#define LOG_TAG "LatinIME: " -#endif -#define DEBUG_DICT true -#define DEBUG_DICT_FULL false -#define DEBUG_SHOW_FOUND_WORD DEBUG_DICT_FULL -#define DEBUG_NODE DEBUG_DICT_FULL -#define DEBUG_TRACE DEBUG_DICT_FULL -#define DEBUG_PROXIMITY_INFO true - +#ifdef FLAG_DO_PROFILE // Profiler #include <time.h> #define PROF_BUF_SIZE 100 @@ -76,16 +65,7 @@ static void prof_out(void) { } } -#else // FLAG_DBG -#define LOGE(fmt, ...) -#define LOGI(fmt, ...) -#define DEBUG_DICT false -#define DEBUG_DICT_FULL false -#define DEBUG_SHOW_FOUND_WORD false -#define DEBUG_NODE false -#define DEBUG_TRACE false -#define DEBUG_PROXIMITY_INFO false - +#else // FLAG_DO_PROFILE #define PROF_BUF_SIZE 0 #define PROF_RESET #define PROF_COUNT(prof_buf_id) @@ -97,6 +77,30 @@ static void prof_out(void) { #define PROF_CLOCKOUT(prof_buf_id) #define PROF_OUTALL +#endif // FLAG_DO_PROFILE + +#ifdef FLAG_DBG +#include <cutils/log.h> +#ifndef LOG_TAG +#define LOG_TAG "LatinIME: " +#endif +#define DEBUG_DICT true +#define DEBUG_DICT_FULL false +#define DEBUG_SHOW_FOUND_WORD DEBUG_DICT_FULL +#define DEBUG_NODE DEBUG_DICT_FULL +#define DEBUG_TRACE DEBUG_DICT_FULL +#define DEBUG_PROXIMITY_INFO true + +#else // FLAG_DBG +#define LOGE(fmt, ...) +#define LOGI(fmt, ...) +#define DEBUG_DICT false +#define DEBUG_DICT_FULL false +#define DEBUG_SHOW_FOUND_WORD false +#define DEBUG_NODE false +#define DEBUG_TRACE false +#define DEBUG_PROXIMITY_INFO false + #endif // FLAG_DBG #ifndef U_SHORT_MAX @@ -126,8 +130,11 @@ static void prof_out(void) { #define FLAG_BIGRAM_FREQ 0x7F #define DICTIONARY_VERSION_MIN 200 +// TODO: remove this constant when the switch to the new dict format is over #define DICTIONARY_HEADER_SIZE 2 +#define NEW_DICTIONARY_HEADER_SIZE 5 #define NOT_VALID_WORD -99 +#define NOT_A_CHARACTER -1 #define KEYCODE_SPACE ' ' diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index 42951bf7a..698584e54 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -25,6 +25,10 @@ #include "dictionary.h" #include "unigram_dictionary.h" +#ifdef NEW_DICTIONARY_FORMAT +#include "binary_format.h" +#endif // NEW_DICTIONARY_FORMAT + namespace latinime { const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] = @@ -36,11 +40,20 @@ const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] = UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion) +#ifndef NEW_DICTIONARY_FORMAT : DICT_ROOT(streamStart), +#else // NEW_DICTIONARY_FORMAT + : DICT_ROOT(streamStart + NEW_DICTIONARY_HEADER_SIZE), +#endif // NEW_DICTIONARY_FORMAT MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords), MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion), TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier), +#ifndef NEW_DICTIONARY_FORMAT ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0), +#else // NEW_DICTIONARY_FORMAT + // TODO : remove this variable. + ROOT_POS(0), +#endif // NEW_DICTIONARY_FORMAT BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(*mInputCodes)), MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) { if (DEBUG_DICT) { @@ -722,8 +735,6 @@ bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength, } #ifndef NEW_DICTIONARY_FORMAT -// TODO: Don't forget to bring inline functions back to over where they are used. - // The following functions will be entirely replaced with new implementations. void UnigramDictionary::getWordsOld(const int initialPos, const int inputLength, const int skipPos, const int excessivePos, const int transposedPos,int *nextLetters, @@ -999,10 +1010,241 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, const in #else // NEW_DICTIONARY_FORMAT +// Wrapper for getMostFrequentWordLikeInner, which matches it to the previous +// interface. +inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex, + const int inputLength, unsigned short *word) { + uint16_t inWord[inputLength]; + + for (int i = 0; i < inputLength; ++i) { + inWord[i] = *getInputCharsAt(startInputIndex + i); + } + return getMostFrequentWordLikeInner(inWord, inputLength, word); +} + +// This function will take the position of a character array within a CharGroup, +// and check it actually like-matches the word in inWord starting at startInputIndex, +// that is, it matches it with case and accents squashed. +// The function returns true if there was a full match, false otherwise. +// The function will copy on-the-fly the characters in the CharGroup to outNewWord. +// It will also place the end position of the array in outPos; in outInputIndex, +// it will place the index of the first char AFTER the match if there was a match, +// and the initial position if there was not. It makes sense because if there was +// a match we want to continue searching, but if there was not, we want to go to +// the next CharGroup. +// In and out parameters may point to the same location. This function takes care +// not to use any input parameters after it wrote into its outputs. +static inline bool testCharGroupForContinuedLikeness(const uint8_t flags, + const uint8_t* const root, const int startPos, + const uint16_t* const inWord, const int startInputIndex, + int32_t* outNewWord, int* outInputIndex, int* outPos) { + const bool hasMultipleChars = (0 != (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags)); + int pos = startPos; + int32_t character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos); + int32_t baseChar = toBaseLowerCase(character); + const uint16_t wChar = toBaseLowerCase(inWord[startInputIndex]); + + if (baseChar != wChar) { + *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos; + *outInputIndex = startInputIndex; + return false; + } + int inputIndex = startInputIndex; + outNewWord[inputIndex] = character; + if (hasMultipleChars) { + character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos); + while (NOT_A_CHARACTER != character) { + baseChar = toBaseLowerCase(character); + if (toBaseLowerCase(inWord[++inputIndex]) != baseChar) { + *outPos = BinaryFormat::skipOtherCharacters(root, pos); + *outInputIndex = startInputIndex; + return false; + } + outNewWord[inputIndex] = character; + character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos); + } + } + *outInputIndex = inputIndex + 1; + *outPos = pos; + return true; +} + +// This function is invoked when a word like the word searched for is found. +// It will compare the frequency to the max frequency, and if greater, will +// copy the word into the output buffer. In output value maxFreq, it will +// write the new maximum frequency if it changed. +static inline void onTerminalWordLike(const int freq, int32_t* newWord, const int length, + short unsigned int* outWord, int* maxFreq) { + if (freq > *maxFreq) { + for (int q = 0; q < length; ++q) + outWord[q] = newWord[q]; + outWord[length] = 0; + *maxFreq = freq; + } +} + +// Will find the highest frequency of the words like the one passed as an argument, +// that is, everything that only differs by case/accents. +int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t * const inWord, + const int length, short unsigned int* outWord) { + int32_t newWord[MAX_WORD_LENGTH_INTERNAL]; + int depth = 0; + int maxFreq = -1; + const uint8_t* const root = DICT_ROOT; + + mStackChildCount[0] = root[0]; + mStackInputIndex[0] = 0; + mStackSiblingPos[0] = 1; + while (depth >= 0) { + const int charGroupCount = mStackChildCount[depth]; + int pos = mStackSiblingPos[depth]; + for (int charGroupIndex = charGroupCount - 1; charGroupIndex >= 0; --charGroupIndex) { + int inputIndex = mStackInputIndex[depth]; + const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos); + // Test whether all chars in this group match with the word we are searching for. If so, + // we want to traverse its children (or if the length match, evaluate its frequency). + // Note that this function will output the position regardless, but will only write + // into inputIndex if there is a match. + const bool isAlike = testCharGroupForContinuedLikeness(flags, root, pos, inWord, + inputIndex, newWord, &inputIndex, &pos); + if (isAlike && (FLAG_IS_TERMINAL & flags) && (inputIndex == length)) { + const int frequency = BinaryFormat::readFrequencyWithoutMovingPointer(root, pos); + onTerminalWordLike(frequency, newWord, inputIndex, outWord, &maxFreq); + } + pos = BinaryFormat::skipFrequency(flags, pos); + const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos); + const int childrenNodePos = BinaryFormat::readChildrenPosition(root, flags, pos); + // If we had a match and the word has children, we want to traverse them. We don't have + // to traverse words longer than the one we are searching for, since they will not match + // anyway, so don't traverse unless inputIndex < length. + if (isAlike && (-1 != childrenNodePos) && (inputIndex < length)) { + // Save position for this depth, to get back to this once children are done + mStackChildCount[depth] = charGroupIndex; + mStackSiblingPos[depth] = siblingPos; + // Prepare stack values for next depth + ++depth; + int childrenPos = childrenNodePos; + mStackChildCount[depth] = + BinaryFormat::getGroupCountAndForwardPointer(root, &childrenPos); + mStackSiblingPos[depth] = childrenPos; + mStackInputIndex[depth] = inputIndex; + pos = childrenPos; + // Go to the next depth level. + ++depth; + break; + } else { + // No match, or no children, or word too long to ever match: go the next sibling. + pos = siblingPos; + } + } + --depth; + } + return maxFreq; +} + +// This function gets the frequency of the exact matching word in the dictionary. +// If no match is found, it returns -1. +int UnigramDictionary::getFrequency(const uint16_t* const inWord, const int length) const { + int pos = 0; + int wordPos = 0; + const uint8_t* const root = DICT_ROOT; + + while (true) { + // If we already traversed the tree further than the word is long, there means + // there was no match (or we would have found it). + if (wordPos > length) return -1; + int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos); + const uint16_t wChar = inWord[wordPos]; + while (true) { + // If there are no more character groups in this node, it means we could not + // find a matching character for this depth, therefore there is no match. + if (0 >= charGroupCount) return -1; + const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos); + int32_t character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos); + if (character == wChar) { + // This is the correct node. Only one character group may start with the same + // char within a node, so either we found our match in this node, or there is + // no match and we can return -1. So we will check all the characters in this + // character group indeed does match. + if (FLAG_HAS_MULTIPLE_CHARS & flags) { + character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos); + while (NOT_A_CHARACTER != character) { + ++wordPos; + // If we shoot the length of the word we search for, or if we find a single + // character that does not match, as explained above, it means the word is + // not in the dictionary (by virtue of this chargroup being the only one to + // match the word on the first character, but not matching the whole word). + if (wordPos > length) return -1; + if (inWord[wordPos] != character) return -1; + character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos); + } + } + // If we come here we know that so far, we do match. Either we are on a terminal + // and we match the length, in which case we found it, or we traverse children. + // If we don't match the length AND don't have children, then a word in the + // dictionary fully matches a prefix of the searched word but not the full word. + ++wordPos; + if (FLAG_IS_TERMINAL & flags) { + if (wordPos == length) { + return BinaryFormat::readFrequencyWithoutMovingPointer(root, pos); + } + pos = BinaryFormat::skipFrequency(FLAG_IS_TERMINAL, pos); + } + if (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS == (MASK_GROUP_ADDRESS_TYPE & flags)) + return -1; + // We have children and we are still shorter than the word we are searching for, so + // we need to traverse children. Put the pointer on the children position, and + // break + pos = BinaryFormat::readChildrenPosition(root, flags, pos); + break; + } else { + // This chargroup does not match, so skip the remaining part and go to the next. + if (FLAG_HAS_MULTIPLE_CHARS & flags) { + pos = BinaryFormat::skipOtherCharacters(root, pos); + } + pos = BinaryFormat::skipFrequency(flags, pos); + pos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos); + } + --charGroupCount; + } + } +} + +bool UnigramDictionary::isValidWord(const uint16_t* const inWord, const int length) const { + return -1 != getFrequency(inWord, length); +} + +int UnigramDictionary::getBigrams(unsigned short *word, int length, int *codes, int codesSize, + unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams, + int maxAlternatives) { + // TODO: add implementation. + return 0; +} + +// TODO: remove this function. +int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offset, + int length) const { + return -1; +} + +// ProcessCurrentNode returns a boolean telling whether to traverse children nodes or not. +// If the return value is false, then the caller should read in the output "nextSiblingPosition" +// to find out the address of the next sibling node and pass it to a new call of processCurrentNode. +// It is worthy to note that when false is returned, the output values other than +// nextSiblingPosition are undefined. +// If the return value is true, then the caller must proceed to traverse the children of this +// node. processCurrentNode will output the information about the children: their count in +// newCount, their position in newChildrenPosition, the traverseAllNodes flag in +// newTraverseAllNodes, the match weight into newMatchRate, the input index into newInputIndex, the +// diffs into newDiffs, the sibling position in nextSiblingPosition, and the output index into +// newOutputIndex. Please also note the following caveat: processCurrentNode does not know when +// there aren't any more nodes at this level, it merely returns the address of the first byte after +// the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any +// given level, as output into newCount when traversing this level's parent. inline bool UnigramDictionary::processCurrentNode(const int initialPos, const int initialDepth, const int maxDepth, const bool initialTraverseAllNodes, int matchWeight, int inputIndex, const int initialDiffs, const int skipPos, const int excessivePos, const int transposedPos, - int *nextLetters, const int nextLettersSize, int *newCount, int *newChildPosition, + int *nextLetters, const int nextLettersSize, int *newCount, int *newChildrenPosition, bool *newTraverseAllNodes, int *newMatchRate, int *newInputIndex, int *newDiffs, int *nextSiblingPosition, int *newOutputIndex) { if (DEBUG_DICT) { @@ -1012,84 +1254,187 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, const in if (transposedPos >= 0) ++inputCount; assert(inputCount <= 1); } - unsigned short c; - int childPosition; - bool terminal; - int freq; - bool isSameAsUserTypedLength = false; - int pos = initialPos; int depth = initialDepth; int traverseAllNodes = initialTraverseAllNodes; int diffs = initialDiffs; - const uint8_t flags = 0; // No flags for now - - if (excessivePos == depth && inputIndex < mInputLength - 1) ++inputIndex; - - *nextSiblingPosition = Dictionary::setDictionaryValues(DICT_ROOT, IS_LATEST_DICT_VERSION, pos, - &c, &childPosition, &terminal, &freq); - *newOutputIndex = depth + 1; + // Flags contain the following information: + // - Address type (MASK_GROUP_ADDRESS_TYPE) on two bits: + // - FLAG_GROUP_ADDRESS_TYPE_{ONE,TWO,THREE}_BYTES means there are children and their address + // is on the specified number of bytes. + // - FLAG_GROUP_ADDRESS_TYPE_NOADDRESS means there are no children, and therefore no address. + // - FLAG_HAS_MULTIPLE_CHARS: whether this node has multiple char or not. + // - FLAG_IS_TERMINAL: whether this node is a terminal or not (it may still have children) + // - FLAG_HAS_BIGRAMS: whether this node has bigrams or not + const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(DICT_ROOT, &pos); + const bool hasMultipleChars = (0 != (FLAG_HAS_MULTIPLE_CHARS & flags)); + + // This gets only ONE character from the stream. Next there will be: + // if FLAG_HAS_MULTIPLE CHARS: the other characters of the same node + // else if FLAG_IS_TERMINAL: the frequency + // else if MASK_GROUP_ADDRESS_TYPE is not NONE: the children address + // Note that you can't have a node that both is not a terminal and has no children. + int32_t c = BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos); + assert(NOT_A_CHARACTER != c); + + // We are going to loop through each character and make it look like it's a different + // node each time. To do that, we will process characters in this node in order until + // we find the character terminator. This is signalled by getCharCode* returning + // NOT_A_CHARACTER. + // As a special case, if there is only one character in this node, we must not read the + // next bytes so we will simulate the NOT_A_CHARACTER return by testing the flags. + // This way, each loop run will look like a "virtual node". + do { + // We prefetch the next char. If 'c' is the last char of this node, we will have + // NOT_A_CHARACTER in the next char. From this we can decide whether this virtual node + // should behave as a terminal or not and whether we have children. + const int32_t nextc = hasMultipleChars + ? BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos) : NOT_A_CHARACTER; + const bool isLastChar = (NOT_A_CHARACTER == nextc); + // If there are more chars in this nodes, then this virtual node is not a terminal. + // If we are on the last char, this virtual node is a terminal if this node is. + const bool isTerminal = isLastChar && (0 != (FLAG_IS_TERMINAL & flags)); + // If there are more chars in this node, then this virtual node has children. + // If we are on the last char, this virtual node has children if this node has. + const bool hasChildren = (!isLastChar) || BinaryFormat::hasChildrenInFlags(flags); + + // This has to be done for each virtual char (this forwards the "inputIndex" which + // is the index in the user-inputted chars, as read by getInputCharsAt. + if (excessivePos == depth && inputIndex < mInputLength - 1) ++inputIndex; + if (traverseAllNodes || needsToSkipCurrentNode(c, inputIndex, skipPos, depth)) { + mWord[depth] = c; + if (traverseAllNodes && isTerminal) { + // The frequency should be here, because we come here only if this is actually + // a terminal node, and we are on its last char. + const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos); + onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight, skipPos, + excessivePos, transposedPos, freq, false, nextLetters, nextLettersSize); + } + if (!hasChildren) { + // If we don't have children here, that means we finished processing all + // characters of this node (we are on the last virtual node), AND we are in + // traverseAllNodes mode, which means we are searching for *completions*. We + // should skip the frequency if we have a terminal, and report the position + // of the next sibling. We don't have to return other values because we are + // returning false, as in "don't traverse children". + if (isTerminal) pos = BinaryFormat::skipFrequency(flags, pos); + *nextSiblingPosition = + BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos); + return false; + } + } else { + const int *currentChars = getInputCharsAt(inputIndex); - const bool needsToTraverseChildrenNodes = childPosition != 0; + if (transposedPos >= 0) { + if (inputIndex == transposedPos) currentChars += MAX_PROXIMITY_CHARS; + if (inputIndex == (transposedPos + 1)) currentChars -= MAX_PROXIMITY_CHARS; + } - // If we are only doing traverseAllNodes, no need to look at the typed characters. - if (traverseAllNodes || needsToSkipCurrentNode(c, inputIndex, skipPos, depth)) { - mWord[depth] = c; - if (traverseAllNodes && terminal) { - onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight, skipPos, - excessivePos, transposedPos, freq, false, nextLetters, nextLettersSize); + const int matchedProximityCharId = getMatchedProximityId(currentChars, c, skipPos, + excessivePos, transposedPos); + if (UNRELATED_CHAR == matchedProximityCharId) { + // We found that this is an unrelated character, so we should give up traversing + // this node and its children entirely. + // However we may not be on the last virtual node yet so we skip the remaining + // characters in this node, the frequency if it's there, read the next sibling + // position to output it, then return false. + // We don't have to output other values because we return false, as in + // "don't traverse children". + if (!isLastChar) { + pos = BinaryFormat::skipOtherCharacters(DICT_ROOT, pos); + } + pos = BinaryFormat::skipFrequency(flags, pos); + *nextSiblingPosition = + BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos); + return false; + } + mWord[depth] = c; + // If inputIndex is greater than mInputLength, that means there is no + // proximity chars. So, we don't need to check proximity. + if (SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) { + multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &matchWeight); + } + const bool isSameAsUserTypedLength = mInputLength == inputIndex + 1 + || (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2); + if (isSameAsUserTypedLength && isTerminal) { + const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos); + onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight, skipPos, + excessivePos, transposedPos, freq, true, nextLetters, nextLettersSize); + } + // This character matched the typed character (enough to traverse the node at least) + // so we just evaluated it. Now we should evaluate this virtual node's children - that + // is, if it has any. If it has no children, we're done here - so we skip the end of + // the node, output the siblings position, and return false "don't traverse children". + // Note that !hasChildren implies isLastChar, so we know we don't have to skip any + // remaining char in this group for there can't be any. + if (!hasChildren) { + pos = BinaryFormat::skipFrequency(flags, pos); + *nextSiblingPosition = + BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos); + return false; + } + // Start traversing all nodes after the index exceeds the user typed length + traverseAllNodes = isSameAsUserTypedLength; + diffs = diffs + ((NEAR_PROXIMITY_CHAR == matchedProximityCharId) ? 1 : 0); + // Finally, we are ready to go to the next character, the next "virtual node". + // We should advance the input index. + // We do this in this branch of the 'if traverseAllNodes' because we are still matching + // characters to input; the other branch is not matching them but searching for + // completions, this is why it does not have to do it. + ++inputIndex; } - if (!needsToTraverseChildrenNodes) return false; - *newTraverseAllNodes = traverseAllNodes; - *newMatchRate = matchWeight; - *newDiffs = diffs; - *newInputIndex = inputIndex; - } else { - const int *currentChars = getInputCharsAt(inputIndex); - - if (transposedPos >= 0) { - if (inputIndex == transposedPos) currentChars += MAX_PROXIMITY_CHARS; - if (inputIndex == (transposedPos + 1)) currentChars -= MAX_PROXIMITY_CHARS; + // Optimization: Prune out words that are too long compared to how much was typed. + if (depth >= maxDepth || diffs > mMaxEditDistance) { + // We are giving up parsing this node and its children. Skip the rest of the node, + // output the sibling position, and return that we don't want to traverse children. + if (!isLastChar) { + pos = BinaryFormat::skipOtherCharacters(DICT_ROOT, pos); + } + pos = BinaryFormat::skipFrequency(flags, pos); + *nextSiblingPosition = + BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos); + return false; } - int matchedProximityCharId = getMatchedProximityId(currentChars, c, skipPos, excessivePos, - transposedPos); - if (UNRELATED_CHAR == matchedProximityCharId) return false; - mWord[depth] = c; - // If inputIndex is greater than mInputLength, that means there is no - // proximity chars. So, we don't need to check proximity. - if (SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) { - multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &matchWeight); - } - bool isSameAsUserTypedLength = mInputLength == inputIndex + 1 - || (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2); - if (isSameAsUserTypedLength && terminal) { - onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight, skipPos, - excessivePos, transposedPos, freq, true, nextLetters, nextLettersSize); - } - if (!needsToTraverseChildrenNodes) return false; - // Start traversing all nodes after the index exceeds the user typed length - *newTraverseAllNodes = isSameAsUserTypedLength; - *newMatchRate = matchWeight; - *newDiffs = diffs + ((NEAR_PROXIMITY_CHAR == matchedProximityCharId) ? 1 : 0); - *newInputIndex = inputIndex + 1; - } - // Optimization: Prune out words that are too long compared to how much was typed. - if (depth >= maxDepth || *newDiffs > mMaxEditDistance) { - return false; - } + // Prepare for the next character. Promote the prefetched char to current char - the loop + // will take care of prefetching the next. If we finally found our last char, nextc will + // contain NOT_A_CHARACTER. + c = nextc; + // Also, the next char is one "virtual node" depth more than this char. + ++depth; + } while (NOT_A_CHARACTER != c); // If inputIndex is greater than mInputLength, that means there are no proximity chars. - // TODO: Check if this can be isSameAsUserTypedLength only. - if (isSameAsUserTypedLength || mInputLength <= *newInputIndex) { - *newTraverseAllNodes = true; + // Here, that's all we are interested in so we don't need to check for isSameAsUserTypedLength. + if (mInputLength <= *newInputIndex) { + traverseAllNodes = true; } - // get the count of nodes and increment childAddress. - *newCount = Dictionary::getCount(DICT_ROOT, &childPosition); - *newChildPosition = childPosition; - if (DEBUG_DICT) assert(needsToTraverseChildrenNodes); - return needsToTraverseChildrenNodes; + + // All the output values that are purely computation by this function are held in local + // variables. Output them to the caller. + *newTraverseAllNodes = traverseAllNodes; + *newMatchRate = matchWeight; + *newDiffs = diffs; + *newInputIndex = inputIndex; + *newOutputIndex = depth; + + // Now we finished processing this node, and we want to traverse children. If there are no + // children, we can't come here. + assert(BinaryFormat::hasChildrenInFlags(flags)); + + // If this node was a terminal it still has the frequency under the pointer (it may have been + // read, but not skipped - see readFrequencyWithoutMovingPointer). + // Next come the children position, then possibly attributes (attributes are bigrams only for + // now, maybe something related to shortcuts in the future). + // Once this is read, we still need to output the number of nodes in the immediate children of + // this node, so we read and output it before returning true, as in "please traverse children". + pos = BinaryFormat::skipFrequency(flags, pos); + int childrenPos = BinaryFormat::readChildrenPosition(DICT_ROOT, flags, pos); + *nextSiblingPosition = BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos); + *newCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &childrenPos); + *newChildrenPosition = childrenPos; + return true; } #endif // NEW_DICTIONARY_FORMAT diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index 789c49596..dcc8f2a9a 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -36,10 +36,51 @@ class UnigramDictionary { } ProximityType; public: +#ifdef NEW_DICTIONARY_FORMAT + + // Mask and flags for children address type selection. + static const int MASK_GROUP_ADDRESS_TYPE = 0xC0; + static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00; + static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40; + static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80; + static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0; + + // Flag for single/multiple char group + static const int FLAG_HAS_MULTIPLE_CHARS = 0x20; + + // Flag for terminal groups + static const int FLAG_IS_TERMINAL = 0x10; + + // Flag for bigram presence + static const int FLAG_HAS_BIGRAMS = 0x04; + + // Attribute (bigram/shortcut) related flags: + // Flag for presence of more attributes + static const int FLAG_ATTRIBUTE_HAS_NEXT = 0x80; + // Flag for sign of offset. If this flag is set, the offset value must be negated. + static const int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40; + + // Mask for attribute frequency, stored on 4 bits inside the flags byte. + static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F; + + // Mask and flags for attribute address type selection. + static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30; + static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10; + static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20; + static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30; +#endif // NEW_DICTIONARY_FORMAT + UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultipler, int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion); +#ifndef NEW_DICTIONARY_FORMAT bool isValidWord(unsigned short *word, int length); +#else // NEW_DICTIONARY_FORMAT + bool isValidWord(const uint16_t* const inWord, const int length) const; + int getBigrams(unsigned short *word, int length, int *codes, int codesSize, + unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams, + int maxAlternatives); +#endif // NEW_DICTIONARY_FORMAT int getBigramPosition(int pos, unsigned short *word, int offset, int length) const; int getSuggestions(const ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize, const int flags, @@ -92,6 +133,7 @@ private: } int getMostFrequentWordLike(const int startInputIndex, const int inputLength, unsigned short *word); +#ifndef NEW_DICTIONARY_FORMAT void getWordsRec(const int childrenCount, const int pos, const int depth, const int maxDepth, const bool traverseAllNodes, const int snr, const int inputIndex, const int diffs, const int skipPos, const int excessivePos, const int transposedPos, int *nextLetters, @@ -104,6 +146,11 @@ private: bool processCurrentNodeForExactMatch(const int firstChildPos, const int startInputIndex, const int depth, unsigned short *word, int *newChildPosition, int *newCount, bool *newTerminal, int *newFreq, int *siblingPos); +#else // NEW_DICTIONARY_FORMAT + int getFrequency(const uint16_t* const inWord, const int length) const; + int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length, + short unsigned int* outWord); +#endif // NEW_DICTIONARY_FORMAT const uint8_t* const DICT_ROOT; const int MAX_WORD_LENGTH; |