diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a107b1..8a31530 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,17 +83,16 @@ make installd A programming layout must contains every ASCII characters. The current programming layouts are: QWERTY, Dvorak. -Ideally, programming layouts should allow to type every Latin-script languages -by containing every dead-keys. See for example: 0bf7ff5 (Latvian), 573c13f (Swedish). -It is also possible to add some characters that are hidden in other languages, -for example 93e84ba (ß), though the space is limited. - Layouts are defined in XML, see `res/xml/qwerty.xml`. An entry must be added to the layout option in `res/values/arrays.xml`, to both `pref_layout_values` (correspond to the file name) and `pref_layout_entries` (display name). The layout must also be referenced in `srcs/juloo.keyboard2/Config.java` in `layoutId_of_string`. +Keys with a name starting in `loc ` are hidden unless they are configured for +the user's installed languages in `res/xml/method.xml`. These keys are optional +and will be added automatically when necessary. + Some users cannot easily type the characters close the the edges of the screen due to a bulky phone case. It is best to avoid placing important characters there (such as the digits or punctuation). diff --git a/srcs/juloo.keyboard2/Config.java b/srcs/juloo.keyboard2/Config.java index fd0c3ec..199fe35 100644 --- a/srcs/juloo.keyboard2/Config.java +++ b/srcs/juloo.keyboard2/Config.java @@ -9,6 +9,7 @@ import android.preference.PreferenceManager; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.KeyEvent; +import java.util.Iterator; import java.util.Set; import java.util.HashSet; @@ -142,20 +143,26 @@ final class Config /** Update the layout according to the configuration. * - Remove the switching key if it isn't needed - * - Remove keys from other locales (not in 'extra_keys') + * - Remove "localized" keys from other locales (not in 'extra_keys') * - Replace the action key to show the right label * - Swap the enter and action keys */ - public KeyboardData modify_layout(KeyboardData kw) + public KeyboardData modify_layout(KeyboardData original_kw) { // Update the name to avoid caching in KeyModifier final KeyValue action_key = (actionLabel == null) ? null : KeyValue.getKeyByName("action").withNameAndSymbol(actionLabel, actionLabel); - return kw.replaceKeys(new KeyboardData.MapKeys() { + // Extra keys are removed from the set as they are encountered during the + // first iteration then automatically added. + final Set extra_keys = new HashSet(this.extra_keys); + KeyboardData kw = original_kw.mapKeys(new KeyboardData.MapKeyValues() { public KeyValue apply(KeyValue key) { if (key == null) return null; + boolean is_extra_key = extra_keys.contains(key.name); + if (is_extra_key) + extra_keys.remove(key.name); switch (key.eventCode) { case KeyValue.EVENT_CHANGE_METHOD: @@ -170,9 +177,7 @@ final class Config default: if (key.flags != 0) { - if ((key.flags & KeyValue.FLAG_LOCALIZED) != 0 && - extra_keys != null && - !extra_keys.contains(key.name)) + if ((key.flags & KeyValue.FLAG_LOCALIZED) != 0 && !is_extra_key) return null; if ((key.flags & lockable_modifiers) != 0) return key.withFlags(key.flags | KeyValue.FLAG_LOCK); @@ -181,6 +186,17 @@ final class Config } } }); + if (extra_keys.size() > 0) + { + final Iterator extra_keys_it = extra_keys.iterator(); + kw = kw.addExtraKeys( + new Iterator() + { + public boolean hasNext() { return extra_keys_it.hasNext(); } + public KeyValue next() { return KeyValue.getKeyByName(extra_keys_it.next()); } + }); + } + return kw; } private float getDipPref(DisplayMetrics dm, SharedPreferences prefs, String pref_name, float def) diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java index d9f0012..8ebf0b8 100644 --- a/srcs/juloo.keyboard2/Keyboard2.java +++ b/srcs/juloo.keyboard2/Keyboard2.java @@ -295,7 +295,7 @@ public class Keyboard2 extends InputMethodService if (_config.programming_layout == -1) return; KeyboardData layout = - getLayout(_config.programming_layout).replaceKeys(new KeyboardData.MapKeys() { + getLayout(_config.programming_layout).mapKeys(new KeyboardData.MapKeyValues() { public KeyValue apply(KeyValue key) { if (key != null && key.eventCode == KeyValue.EVENT_SWITCH_PROGRAMMING) diff --git a/srcs/juloo.keyboard2/KeyboardData.java b/srcs/juloo.keyboard2/KeyboardData.java index adbf790..83b1548 100644 --- a/srcs/juloo.keyboard2/KeyboardData.java +++ b/srcs/juloo.keyboard2/KeyboardData.java @@ -3,10 +3,11 @@ package juloo.keyboard2; import android.content.res.Resources; import android.content.res.XmlResourceParser; import java.util.ArrayList; +import java.util.function.Function; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.function.Function; class KeyboardData { @@ -16,14 +17,42 @@ class KeyboardData /* Total height of the keyboard. Unit is abstract. */ public final float keysHeight; - public KeyboardData replaceKeys(MapKeys f) + public KeyboardData mapKeys(MapKey f) { ArrayList rows_ = new ArrayList(); for (Row r : rows) - rows_.add(r.replaceKeys(f)); + rows_.add(r.mapKeys(f)); return new KeyboardData(rows_, keysWidth); } + /** Add keys from the given iterator into the keyboard. Extra keys are added + * on the empty key4 corner of the second row, from right to left. If there's + * not enough room, key3 of the second row is tried then key2 and key1 of the + * third row. */ + public KeyboardData addExtraKeys(Iterator k) + { + ArrayList rows = new ArrayList(this.rows); + addExtraKeys_to_row(rows, k, 1, 4); + addExtraKeys_to_row(rows, k, 1, 3); + addExtraKeys_to_row(rows, k, 2, 2); + addExtraKeys_to_row(rows, k, 2, 1); + return new KeyboardData(rows, keysWidth); + } + + private static void addExtraKeys_to_row(ArrayList rows, final Iterator extra_keys, int row_i, final int d) + { + if (!extra_keys.hasNext()) + return; + rows.set(row_i, rows.get(row_i).mapKeys(new MapKey(){ + public Key apply(Key k) { + if (k.getKeyValue(d) == null && extra_keys.hasNext()) + return k.withKeyValue(d, extra_keys.next()); + else + return k; + } + })); + } + private static Row _bottomRow = null; private static Map _layoutCache = new HashMap(); @@ -117,22 +146,21 @@ class KeyboardData return new Row(keys, h, shift); } - public Row replaceKeys(MapKeys f) + public Row mapKeys(MapKey f) { ArrayList keys_ = new ArrayList(); for (Key k : keys) - keys_.add(k.replaceKeys(f)); + keys_.add(f.apply(k)); return new Row(keys_, height, shift); } /** Change the width of every keys so that the row is 's' units wide. */ public Row updateWidth(float newWidth) { - float s = newWidth / keysWidth; - ArrayList keys_ = new ArrayList(); - for (Key k : keys) - keys_.add(k.scaleWidth(s)); - return new Row(keys_, height, shift); + final float s = newWidth / keysWidth; + return mapKeys(new MapKey(){ + public Key apply(Key k) { return k.scaleWidth(s); } + }); } } @@ -183,18 +211,39 @@ class KeyboardData return new Key(k0, k1, k2, k3, k4, width, shift, edgekeys); } - public Key replaceKeys(MapKeys f) - { - return new Key(f.apply(key0), f.apply(key1), f.apply(key2), - f.apply(key3), f.apply(key4), width, shift, edgekeys); - } - /** New key with the width multiplied by 's'. */ public Key scaleWidth(float s) { return new Key(key0, key1, key2, key3, key4, width * s, shift, edgekeys); } + public KeyValue getKeyValue(int i) + { + switch (i) + { + case 0: return key0; + case 1: return key1; + case 2: return key2; + case 3: return key3; + case 4: return key4; + default: return null; + } + } + + public Key withKeyValue(int i, KeyValue kv) + { + KeyValue k0 = key0, k1 = key1, k2 = key2, k3 = key3, k4 = key4; + switch (i) + { + case 0: k0 = kv; break; + case 1: k1 = kv; break; + case 2: k2 = kv; break; + case 3: k3 = kv; break; + case 4: k4 = kv; break; + } + return new Key(k0, k1, k2, k3, k4, width, shift, edgekeys); + } + /* * See Pointers.onTouchMove() for the represented direction. */ @@ -235,8 +284,18 @@ class KeyboardData } // Not using Function to keep compatibility with Android 6. - public static abstract interface MapKeys { - public KeyValue apply(KeyValue kv); + public static abstract interface MapKey { + public Key apply(Key k); + } + + public static abstract class MapKeyValues implements MapKey { + abstract public KeyValue apply(KeyValue kv); + + public Key apply(Key k) + { + return new Key(apply(k.key0), apply(k.key1), apply(k.key2), + apply(k.key3), apply(k.key4), k.width, k.shift, k.edgekeys); + } } /** Parsing utils */