Refactor: Preferred positions for extra keys

The new PreferredPos class represents where an extra key should be
placed
Currently used to place keys at the same positions they were placed
before.
This commit is contained in:
Jules Aguillon 2023-09-15 18:00:27 +02:00
parent d771e9d2c7
commit 66b1bdc9c9
5 changed files with 120 additions and 48 deletions

View File

@ -9,8 +9,10 @@ import android.util.TypedValue;
import android.view.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class Config
@ -59,8 +61,8 @@ final class Config
public int actionId; // Meaningful only when 'actionLabel' isn't 'null'
public boolean swapEnterActionKey; // Swap the "enter" and "action" keys
public ExtraKeys extra_keys_subtype;
public Set<KeyValue> extra_keys_param;
public List<KeyValue> extra_keys_custom;
public Map<KeyValue, KeyboardData.PreferredPos> extra_keys_param;
public Map<KeyValue, KeyboardData.PreferredPos> extra_keys_custom;
public final IKeyEventHandler handler;
public boolean orientation_landscape = false;
@ -174,16 +176,16 @@ final class Config
final KeyValue action_key = action_key();
// Extra keys are removed from the set as they are encountered during the
// first iteration then automatically added.
final Set<KeyValue> extra_keys = new HashSet<KeyValue>();
final Map<KeyValue, KeyboardData.PreferredPos> extra_keys = new HashMap<KeyValue, KeyboardData.PreferredPos>();
final Set<KeyValue> remove_keys = new HashSet<KeyValue>();
extra_keys.addAll(extra_keys_param);
extra_keys.addAll(extra_keys_custom);
extra_keys.putAll(extra_keys_param);
extra_keys.putAll(extra_keys_custom);
if (extra_keys_subtype != null)
{
Set<KeyValue> present = new HashSet<KeyValue>();
present.addAll(kw.getKeys().keySet());
present.addAll(extra_keys_param);
present.addAll(extra_keys_custom);
present.addAll(extra_keys_param.keySet());
present.addAll(extra_keys_custom.keySet());
extra_keys_subtype.compute(extra_keys,
new ExtraKeys.Query(kw.script, present));
}
@ -193,7 +195,7 @@ final class Config
kw = kw.mapKeys(new KeyboardData.MapKeyValues() {
public KeyValue apply(KeyValue key, boolean localized)
{
boolean is_extra_key = extra_keys.contains(key);
boolean is_extra_key = extra_keys.containsKey(key);
if (is_extra_key)
extra_keys.remove(key);
if (localized && !is_extra_key)
@ -246,7 +248,7 @@ final class Config
if (number_row)
kw = kw.addNumberRow();
if (extra_keys.size() > 0)
kw = kw.addExtraKeys(extra_keys.iterator());
kw = kw.addExtraKeys(extra_keys.entrySet().iterator());
return kw;
}

View File

@ -10,8 +10,9 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
@ -30,14 +31,15 @@ public class CustomExtraKeysPreference extends ListGroupPreference<String>
setKey(KEY);
}
public static List<KeyValue> get(SharedPreferences prefs)
public static Map<KeyValue, KeyboardData.PreferredPos> get(SharedPreferences prefs)
{
List<KeyValue> kvs = new ArrayList<KeyValue>();
Map<KeyValue, KeyboardData.PreferredPos> kvs =
new HashMap<KeyValue, KeyboardData.PreferredPos>();
List<String> key_names = load_from_preferences(KEY, prefs, null, SERIALIZER);
if (key_names != null)
{
for (String key_name : key_names)
kvs.add(KeyValue.makeStringKey(key_name));
kvs.put(KeyValue.makeStringKey(key_name), KeyboardData.PreferredPos.DEFAULT);
}
return kvs;
}

View File

@ -22,7 +22,7 @@ class ExtraKeys
/** Add the keys that should be added to the keyboard into [dst]. Keys
already added to [dst] might have an impact, see [ExtraKey.compute]. */
public void compute(Set<KeyValue> dst, Query q)
public void compute(Map<KeyValue, KeyboardData.PreferredPos> dst, Query q)
{
for (ExtraKey k : _ks)
k.compute(dst, q);
@ -72,7 +72,7 @@ class ExtraKeys
}
/** Whether the key should be added to the keyboard. */
public void compute(Set<KeyValue> dst, Query q)
public void compute(Map<KeyValue, KeyboardData.PreferredPos> dst, Query q)
{
// Add the alternative if it's the only one. The list of alternatives is
// enforced to be complete by the merging step. The same [kv] will not
@ -80,11 +80,14 @@ class ExtraKeys
// alternatives.
// Selecting the dead key in the "Add key to the keyboard" option would
// disable this behavior for a key.
boolean use_alternative = (alternatives.size() == 1 && !dst.contains(kv));
boolean use_alternative = (alternatives.size() == 1 && !dst.containsKey(kv));
if
((q.script == null || script == null || q.script.equals(script))
&& (alternatives.size() == 0 || !q.present.containsAll(alternatives)))
dst.add(use_alternative ? alternatives.get(0) : kv);
{
KeyValue kv_ = use_alternative ? alternatives.get(0) : kv;
dst.put(kv_, KeyboardData.PreferredPos.DEFAULT);
}
}
/** Return a new key from two. [kv] are expected to be equal. [script] is

View File

@ -8,7 +8,8 @@ import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/** This class implements the "extra keys" preference but also defines the
@ -100,14 +101,15 @@ public class ExtraKeysPreference extends PreferenceCategory
}
/** Get the set of enabled extra keys. */
public static Set<KeyValue> get_extra_keys(SharedPreferences prefs)
public static Map<KeyValue, KeyboardData.PreferredPos> get_extra_keys(SharedPreferences prefs)
{
HashSet<KeyValue> ks = new HashSet<KeyValue>();
Map<KeyValue, KeyboardData.PreferredPos> ks =
new HashMap<KeyValue, KeyboardData.PreferredPos>();
for (String key_name : extra_keys)
{
if (prefs.getBoolean(pref_key_of_key_name(key_name),
default_checked(key_name)))
ks.add(KeyValue.getKeyByName(key_name));
ks.put(KeyValue.getKeyByName(key_name), KeyboardData.PreferredPos.DEFAULT);
}
return ks;
}

View File

@ -36,26 +36,68 @@ class KeyboardData
return new KeyboardData(rows_, keysWidth, modmap, script);
}
/** 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<KeyValue> k)
/** Add keys from the given iterator into the keyboard. Preferred position is
specified via [PreferredPos]. */
public KeyboardData addExtraKeys(Iterator<Map.Entry<KeyValue, PreferredPos>> extra_keys)
{
/* Keys that couldn't be placed at their preferred position. */
ArrayList<KeyValue> unplaced_keys = new ArrayList<KeyValue>();
ArrayList<Row> rows = new ArrayList<Row>(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);
if (k.hasNext())
while (extra_keys.hasNext())
{
for (int r = 0; r < rows.size(); r++)
for (int c = 1; c <= 4; c++)
addExtraKeys_to_row(rows, k, r, c);
Map.Entry<KeyValue, PreferredPos> kp = extra_keys.next();
if (!add_key_to_preferred_pos(rows, kp.getKey(), kp.getValue()))
unplaced_keys.add(kp.getKey());
}
for (KeyValue kv : unplaced_keys)
add_key_to_preferred_pos(rows, kv, PreferredPos.ANYWHERE);
return new KeyboardData(rows, keysWidth, modmap, script);
}
/** Place a key on the keyboard according to its preferred position. Mutates
[rows]. Returns [false] if it couldn't be placed. */
boolean add_key_to_preferred_pos(List<Row> rows, KeyValue kv, PreferredPos pos)
{
for (KeyPos p : pos.positions)
if (add_key_to_pos(rows, kv, p))
return true;
return false;
}
/** Place a key on the keyboard. A value of [-1] in one of the coordinate
means that the key can be placed anywhere in that coordinate, see
[PreferredPos]. Mutates [rows]. Returns [false] if it couldn't be placed.
*/
boolean add_key_to_pos(List<Row> rows, KeyValue kv, KeyPos p)
{
int i_row = p.row;
int i_row_end = p.row;
if (p.row == -1) { i_row = 0; i_row_end = rows.size() - 1; }
for (; i_row <= i_row_end; i_row++)
{
Row row = rows.get(i_row);
int i_col = p.col;
int i_col_end = p.col;
if (p.col == -1) { i_col = 0; i_col_end = row.keys.size() - 1; }
for (; i_col <= i_col_end; i_col++)
{
Key col = row.keys.get(i_col);
int i_dir = p.dir;
int i_dir_end = p.dir;
if (p.dir == -1) { i_dir = 1; i_dir_end = 4; }
for (; i_dir <= i_dir_end; i_dir++)
{
if (col.getKeyValue(i_dir) == null)
{
row.keys.set(i_col, col.withKeyValue(i_dir, kv));
return true;
}
}
}
}
return false;
}
public KeyboardData addNumPad(KeyboardData num_pad)
{
ArrayList<Row> extendedRows = new ArrayList<Row>();
@ -107,20 +149,6 @@ class KeyboardData
return _key_pos;
}
private static void addExtraKeys_to_row(ArrayList<Row> rows, final Iterator<KeyValue> extra_keys, int row_i, final int d)
{
if (!extra_keys.hasNext() || row_i >= rows.size())
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;
}
}));
}
public static Row bottom_row;
public static Row number_row;
public static KeyboardData num_pad;
@ -263,6 +291,11 @@ class KeyboardData
return new Row(keys, h, shift);
}
public Row copy()
{
return new Row(new ArrayList<Key>(keys), height, shift);
}
public void getKeys(Map<KeyValue, KeyPos> dst, int row)
{
for (int c = 0; c < keys.size(); c++)
@ -490,6 +523,36 @@ class KeyboardData
}
}
/** See [addExtraKeys()]. */
public final static class PreferredPos
{
public static final PreferredPos DEFAULT;
public static final PreferredPos ANYWHERE;
/** Array of positions to try in order. The special value [-1] as [row],
[col] or [dir] means that the field is unspecified. Every possible
values are tried for unspecified fields. Unspecified fields are
searched in this order: [dir], [col], [row]. */
public KeyPos[] positions = ANYWHERE_POSITIONS;
public PreferredPos() {}
static final KeyPos[] ANYWHERE_POSITIONS =
new KeyPos[]{ new KeyPos(-1, -1, -1) };
static
{
DEFAULT = new PreferredPos();
DEFAULT.positions = new KeyPos[]{
new KeyPos(1, -1, 4),
new KeyPos(1, -1, 3),
new KeyPos(2, -1, 2),
new KeyPos(2, -1, 1)
};
ANYWHERE = new PreferredPos();
}
}
/** Parsing utils */
/** Returns [false] on [END_DOCUMENT] or [END_TAG], [true] otherwise. */