abouttreesummaryrefslogcommitdiff
path: root/srcs
diff options
context:
space:
mode:
authorJules Aguillon2024-09-29 21:58:22 +0200
committerGitHub2024-09-29 21:58:22 +0200
commit9f22e53a3ba8f064e69e3a84c371a7f29ee9e05c (patch)
tree96735329102cdde5460818f22803e67dc42c6356 /srcs
parente309b76c0a8cb4c322b5fa902a080e19c2fe1f08 (diff)
downloadunexpected-keyboard-9f22e53a3ba8f064e69e3a84c371a7f29ee9e05c.tar.gz
unexpected-keyboard-9f22e53a3ba8f064e69e3a84c371a7f29ee9e05c.zip
Add complex keys (#774)
This allows to add new kinds of keys that need more data without making KeyValue's footprint bigger for common keys. This changes the [_symbol] field into [_payload], which holds the same as the previous field for more common keys but can hold bigger objects for keys of the new "Complex" kind. This also adds a complex key: String keys with a symbol different than the outputted string. Unit tests are added as the Java language is not helpful in making robust code.
Diffstat (limited to 'srcs')
-rw-r--r--srcs/juloo.keyboard2/Config.java3
-rw-r--r--srcs/juloo.keyboard2/KeyEventHandler.java11
-rw-r--r--srcs/juloo.keyboard2/KeyValue.java142
-rw-r--r--srcs/juloo.keyboard2/KeyValueParser.java150
-rw-r--r--srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java2
5 files changed, 286 insertions, 22 deletions
diff --git a/srcs/juloo.keyboard2/Config.java b/srcs/juloo.keyboard2/Config.java
index 5e60815..6d77f6f 100644
--- a/srcs/juloo.keyboard2/Config.java
+++ b/srcs/juloo.keyboard2/Config.java
@@ -210,8 +210,7 @@ public final class Config
KeyValue action_key()
{
// Update the name to avoid caching in KeyModifier
- return (actionLabel == null) ? null :
- KeyValue.getKeyByName("action").withSymbol(actionLabel);
+ return (actionLabel == null) ? null : KeyValue.makeActionKey(actionLabel);
}
/** Update the layout according to the configuration.
diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java
index 087ac5b..c54ffa5 100644
--- a/srcs/juloo.keyboard2/KeyEventHandler.java
+++ b/srcs/juloo.keyboard2/KeyEventHandler.java
@@ -97,6 +97,7 @@ public final class KeyEventHandler
_recv.set_compose_pending(true);
break;
case Cursor_move: move_cursor(key.getCursorMove()); break;
+ case Complex: send_complex_key(key.getComplexKind(), key.getComplex()); break;
}
update_meta_state(old_mods);
}
@@ -215,6 +216,16 @@ public final class KeyEventHandler
conn.performContextMenuAction(id);
}
+ void send_complex_key(KeyValue.Complex.Kind kind, KeyValue.Complex val)
+ {
+ switch (kind)
+ {
+ case StringWithSymbol:
+ send_text(((KeyValue.Complex.StringWithSymbol)val).str);
+ break;
+ }
+ }
+
@SuppressLint("InlinedApi")
void handle_editing_key(KeyValue.Editing ev)
{
diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java
index 2a329df..8adacf0 100644
--- a/srcs/juloo.keyboard2/KeyValue.java
+++ b/srcs/juloo.keyboard2/KeyValue.java
@@ -91,7 +91,8 @@ public final class KeyValue implements Comparable<KeyValue>
{
Char, String, Keyevent, Event, Compose_pending, Hangul_initial,
Hangul_medial, Modifier, Editing, Placeholder,
- Cursor_move // Value is encoded as a 16-bit integer
+ Cursor_move, // Value is encoded as a 16-bit integer.
+ Complex, // [_payload] is a [KeyValue.Complex], value is [Complex.Kind].
}
private static final int FLAGS_OFFSET = 19;
@@ -129,7 +130,13 @@ public final class KeyValue implements Comparable<KeyValue>
check((((Kind.values().length - 1) << KIND_OFFSET) & ~KIND_BITS) == 0);
}
- private final String _symbol;
+ /**
+ * The symbol that is rendered on the keyboard as a [String].
+ * Except for keys of kind:
+ * - [String], this is also the string to output.
+ * - [Complex], this is an instance of [KeyValue.Complex].
+ */
+ private final Object _payload;
/** This field encodes three things: Kind, flags and value. */
private final int _code;
@@ -153,7 +160,9 @@ public final class KeyValue implements Comparable<KeyValue>
When [getKind() == Kind.String], also the string to send. */
public String getString()
{
- return _symbol;
+ if (getKind() == Kind.Complex)
+ return ((Complex)_payload).getSymbol();
+ return (String)_payload;
}
/** Defined only when [getKind() == Kind.Char]. */
@@ -211,25 +220,32 @@ public final class KeyValue implements Comparable<KeyValue>
return (short)(_code & VALUE_BITS);
}
- /* Update the char and the symbol. */
- public KeyValue withChar(char c)
+ /** Defined only when [getKind() == Kind.Complex]. */
+ public Complex getComplex()
{
- return new KeyValue(String.valueOf(c), Kind.Char, c, getFlags());
+ return (Complex)_payload;
}
- public KeyValue withSymbol(String s)
+ /** Defined only when [getKind() == Kind.Complex]. */
+ public Complex.Kind getComplexKind()
{
- return new KeyValue(s, (_code & KIND_BITS), (_code & VALUE_BITS), getFlags());
+ return Complex.Kind.values()[(_code & VALUE_BITS)];
+ }
+
+ /* Update the char and the symbol. */
+ public KeyValue withChar(char c)
+ {
+ return new KeyValue(String.valueOf(c), Kind.Char, c, getFlags());
}
public KeyValue withKeyevent(int code)
{
- return new KeyValue(_symbol, Kind.Keyevent, code, getFlags());
+ return new KeyValue(getString(), Kind.Keyevent, code, getFlags());
}
public KeyValue withFlags(int f)
{
- return new KeyValue(_symbol, (_code & KIND_BITS), (_code & VALUE_BITS), f);
+ return new KeyValue(getString(), (_code & KIND_BITS), (_code & VALUE_BITS), f);
}
@Override
@@ -247,7 +263,9 @@ public final class KeyValue implements Comparable<KeyValue>
d = _code - snd._code;
if (d != 0)
return d;
- return _symbol.compareTo(snd._symbol);
+ if (getKind() == Kind.Complex)
+ return ((Complex)_payload).compareTo((Complex)snd._payload);
+ return ((String)_payload).compareTo((String)snd._payload);
}
/** Type-safe alternative to [equals]. */
@@ -255,24 +273,36 @@ public final class KeyValue implements Comparable<KeyValue>
{
if (snd == null)
return false;
- return _symbol.equals(snd._symbol) && _code == snd._code;
+ return _code == snd._code && _payload.equals(snd._payload);
}
@Override
public int hashCode()
{
- return _symbol.hashCode() + _code;
+ return _payload.hashCode() + _code;
}
- public KeyValue(String s, int kind, int value, int flags)
+ public String toString()
{
- _symbol = s;
+ int value = _code & VALUE_BITS;
+ return "[KeyValue " + getKind().toString() + "+" + getFlags() + "+" + value + " \"" + getString() + "\"]";
+ }
+
+ private KeyValue(Object p, int kind, int value, int flags)
+ {
+ _payload = p;
_code = (kind & KIND_BITS) | (flags & FLAGS_BITS) | (value & VALUE_BITS);
}
- public KeyValue(String s, Kind k, int v, int f)
+ public KeyValue(Complex p, Complex.Kind value, int flags)
{
- this(s, (k.ordinal() << KIND_OFFSET), v, f);
+ this((Object)p, (Kind.Complex.ordinal() << KIND_OFFSET), value.ordinal(),
+ flags);
+ }
+
+ public KeyValue(String p, Kind k, int v, int f)
+ {
+ this(p, (k.ordinal() << KIND_OFFSET), v, f);
}
private static KeyValue charKey(String symbol, char c, int flags)
@@ -397,6 +427,11 @@ public final class KeyValue implements Comparable<KeyValue>
return KeyValue.makeCharKey((char)precomposed);
}
+ public static KeyValue makeActionKey(String symbol)
+ {
+ return eventKey(symbol, Event.ACTION, FLAG_SMALLER_FONT);
+ }
+
/** Make a key that types a string. A char key is returned for a string of
length 1. */
public static KeyValue makeStringKey(String str, int flags)
@@ -407,12 +442,36 @@ public final class KeyValue implements Comparable<KeyValue>
return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
}
+ public static KeyValue makeStringKeyWithSymbol(String str, String symbol, int flags)
+ {
+ return new KeyValue(new Complex.StringWithSymbol(str, symbol),
+ Complex.Kind.StringWithSymbol, flags);
+ }
+
/** Make a modifier key for passing to [KeyModifier]. */
public static KeyValue makeInternalModifier(Modifier mod)
{
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
}
+ public static KeyValue parseKeyDefinition(String str)
+ {
+ if (str.length() < 2 || str.charAt(0) != ':')
+ return makeStringKey(str);
+ try
+ {
+ return KeyValueParser.parse(str);
+ }
+ catch (KeyValueParser.ParseError _e)
+ {
+ return makeStringKey(str);
+ }
+ }
+
+ /**
+ * Return a key by its name. If the given name doesn't correspond to a key
+ * defined in this function, it is passed to [parseStringKey] as a fallback.
+ */
public static KeyValue getKeyByName(String name)
{
switch (name)
@@ -599,8 +658,8 @@ public final class KeyValue implements Comparable<KeyValue>
case "ㅍ": return makeHangulInitial("ㅍ", 17);
case "ㅎ": return makeHangulInitial("ㅎ", 18);
- /* Fallback to a string key that types its name */
- default: return makeStringKey(name);
+ /* The key is not one of the special ones. */
+ default: return parseKeyDefinition(name);
}
}
@@ -610,4 +669,49 @@ public final class KeyValue implements Comparable<KeyValue>
if (!b)
throw new RuntimeException("Assertion failure");
}
+
+ public static abstract class Complex
+ {
+ public abstract String getSymbol();
+
+ /** [compareTo] can assume that [snd] is an instance of the same class. */
+ public abstract int compareTo(Complex snd);
+
+ public boolean equals(Object snd)
+ {
+ if (snd instanceof Complex)
+ return compareTo((Complex)snd) == 0;
+ return false;
+ }
+
+ /** [hashCode] will be called on this class. */
+
+ /** The kind is stored in the [value] field of the key. */
+ public static enum Kind
+ {
+ StringWithSymbol,
+ }
+
+ public static final class StringWithSymbol extends Complex
+ {
+ public final String str;
+ private final String _symbol;
+
+ public StringWithSymbol(String _str, String _sym)
+ {
+ str = _str;
+ _symbol = _sym;
+ }
+
+ public String getSymbol() { return _symbol; }
+
+ public int compareTo(Complex _snd)
+ {
+ StringWithSymbol snd = (StringWithSymbol)_snd;
+ int d = str.compareTo(snd.str);
+ if (d != 0) return d;
+ return _symbol.compareTo(snd._symbol);
+ }
+ }
+ };
}
diff --git a/srcs/juloo.keyboard2/KeyValueParser.java b/srcs/juloo.keyboard2/KeyValueParser.java
new file mode 100644
index 0000000..178046e
--- /dev/null
+++ b/srcs/juloo.keyboard2/KeyValueParser.java
@@ -0,0 +1,150 @@
+package juloo.keyboard2;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+Parse a key definition. The syntax for a key definition is:
+- [:(kind) (attributes):(payload)].
+- If [str] doesn't start with a [:] character, it is interpreted as an
+ arbitrary string key.
+
+[(kind)] specifies the kind of the key, it can be:
+- [str]: An arbitrary string key. The payload is the string to output when
+ typed and is quoted by single quotes ([']). The payload can contain single
+ quotes if they are escaped with a backslash ([\']).
+
+The [(attributes)] part is a space-separated list of attributes, all optional,
+of the form: [attrname='attrvalue'].
+
+Attributes can be:
+- [flags]: Add flags that change the behavior of the key.
+ Value is a coma separated list of:
+ - [dim]: Make the symbol dimmer on the keyboard.
+ - [small]: Make the symbol smaller on the keyboard.
+- [symbol]: Specify the symbol that is rendered on the keyboard.
+ It can contain single quotes if they are escaped: ([\']).
+
+Examples:
+- [:str flags=dim,small symbol='MyKey':'My arbitrary string'].
+- [:str:'My arbitrary string'].
+
+*/
+public final class KeyValueParser
+{
+ static Pattern START_PAT;
+ static Pattern ATTR_PAT;
+ static Pattern QUOTED_PAT;
+ static Pattern PAYLOAD_START_PAT;
+
+ static public KeyValue parse(String str) throws ParseError
+ {
+ String symbol = null;
+ int flags = 0;
+ init();
+ // Kind
+ Matcher m = START_PAT.matcher(str);
+ if (!m.lookingAt())
+ parseError("Expected kind, for example \":str ...\".", m);
+ String kind = m.group(1);
+ // Attributes
+ while (true)
+ {
+ if (!match(m, ATTR_PAT))
+ break;
+ String attr_name = m.group(1);
+ String attr_value = parseSingleQuotedString(m);
+ switch (attr_name)
+ {
+ case "flags":
+ flags = parseFlags(attr_value, m);
+ break;
+ case "symbol":
+ symbol = attr_value;
+ break;
+
+ default:
+ parseError("Unknown attribute "+attr_name, m);
+ }
+ }
+ // Payload
+ if (!match(m, PAYLOAD_START_PAT))
+ parseError("Unexpected character", m);
+ switch (kind)
+ {
+ case "str":
+ String payload = parseSingleQuotedString(m);
+ if (symbol == null)
+ return KeyValue.makeStringKey(payload, flags);
+ return KeyValue.makeStringKeyWithSymbol(payload, symbol, flags);
+
+ default: break;
+ }
+ parseError("Unknown kind '"+kind+"'", m, 1);
+ return null; // Unreachable
+ }
+
+ static String parseSingleQuotedString(Matcher m) throws ParseError
+ {
+ if (!match(m, QUOTED_PAT))
+ parseError("Expected quoted string", m);
+ return m.group(1).replace("\\'", "'");
+ }
+
+ static int parseFlags(String s, Matcher m) throws ParseError
+ {
+ int flags = 0;
+ for (String f : s.split(","))
+ {
+ switch (f)
+ {
+ case "dim": flags |= KeyValue.FLAG_SECONDARY; break;
+ case "small": flags |= KeyValue.FLAG_SMALLER_FONT; break;
+ default: parseError("Unknown flag "+f, m);
+ }
+ }
+ return flags;
+ }
+
+ static boolean match(Matcher m, Pattern pat)
+ {
+ try { m.region(m.end(), m.regionEnd()); } catch (Exception _e) {}
+ m.usePattern(pat);
+ return m.lookingAt();
+ }
+
+ static void init()
+ {
+ if (START_PAT != null)
+ return;
+ START_PAT = Pattern.compile(":(\\w+)");
+ ATTR_PAT = Pattern.compile("\\s*(\\w+)\\s*=");
+ QUOTED_PAT = Pattern.compile("'(([^'\\\\]+|\\\\')*)'");
+ PAYLOAD_START_PAT = Pattern.compile("\\s*:");
+ }
+
+ static void parseError(String msg, Matcher m) throws ParseError
+ {
+ parseError(msg, m, m.regionStart());
+ }
+
+ static void parseError(String msg, Matcher m, int i) throws ParseError
+ {
+ StringBuilder msg_ = new StringBuilder("Syntax error");
+ try
+ {
+ char c = m.group(0).charAt(0);
+ msg_.append(" at character '").append(c).append("'");
+ } catch (IllegalStateException _e) {}
+ msg_.append(" at position ");
+ msg_.append(i);
+ msg_.append(": ");
+ msg_.append(msg);
+ throw new ParseError(msg_.toString());
+ }
+
+ public static class ParseError extends Exception
+ {
+ public ParseError(String msg) { super(msg); }
+ };
+}
diff --git a/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
index cf47d46..fda07ec 100644
--- a/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
+++ b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
@@ -40,7 +40,7 @@ public class CustomExtraKeysPreference extends ListGroupPreference<String>
if (key_names != null)
{
for (String key_name : key_names)
- kvs.put(KeyValue.makeStringKey(key_name), KeyboardData.PreferredPos.DEFAULT);
+ kvs.put(KeyValue.parseKeyDefinition(key_name), KeyboardData.PreferredPos.DEFAULT);
}
return kvs;
}