From e47227ce6d521de9db0a4161ae3b1655acbae2a9 Mon Sep 17 00:00:00 2001
From: Emil Kosz
Date: Mon, 25 Nov 2024 21:14:45 +0100
Subject: [PATCH] Some major changes, keymap restructuring with device and
language keymaps
---
app/src/main/AndroidManifest.xml | 3 -
.../cfpifpsi/titanboard/KeymapManager.java | 228 ---
.../java/eur/cfpifpsi/titanboard/MetaKey.java | 6 +-
.../cfpifpsi/titanboard/PreferenceHolder.java | 4 +-
.../cfpifpsi/titanboard/TitanboardIME.java | 6 +-
.../titanboard/keymapping/KeySymbols.java | 52 +
.../titanboard/keymapping/Keymap.java | 58 +
.../titanboard/keymapping/KeymapLoader.java | 248 +++
.../titanboard/keymapping/KeymapManager.java | 108 ++
.../titanboard/keymapping/LanguageKeymap.java | 61 +
.../titanboard/views/ClipboardView.java | 21 +-
.../cfpifpsi/titanboard/views/InputView.java | 45 +-
.../titanboard/views/SymbolsView.java | 7 +-
.../cfpifpsi/titanboard/views/TweaksView.java | 2 +-
app/src/main/res/drawable/ic_faders.xml | 82 +
app/src/main/res/drawable/ic_tweaks.xml | 56 -
app/src/main/res/layout/input.xml | 8 +-
app/src/main/res/values/arrays.xml | 16 +-
app/src/main/res/values/strings.xml | 63 +-
app/src/main/res/xml/device_keymaps.xml | 123 ++
app/src/main/res/xml/keymaps_titan.xml | 1664 -----------------
app/src/main/res/xml/language_keymaps.xml | 1284 +++++++++++++
app/src/main/res/xml/preferences.xml | 16 +-
23 files changed, 2159 insertions(+), 2002 deletions(-)
delete mode 100644 app/src/main/java/eur/cfpifpsi/titanboard/KeymapManager.java
create mode 100644 app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeySymbols.java
create mode 100644 app/src/main/java/eur/cfpifpsi/titanboard/keymapping/Keymap.java
create mode 100644 app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapLoader.java
create mode 100644 app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapManager.java
create mode 100644 app/src/main/java/eur/cfpifpsi/titanboard/keymapping/LanguageKeymap.java
create mode 100644 app/src/main/res/drawable/ic_faders.xml
delete mode 100644 app/src/main/res/drawable/ic_tweaks.xml
create mode 100644 app/src/main/res/xml/device_keymaps.xml
delete mode 100644 app/src/main/res/xml/keymaps_titan.xml
create mode 100644 app/src/main/res/xml/language_keymaps.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 65a8c9f..597a967 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,9 +29,6 @@
android:roundIcon="@drawable/ic_app_round"
android:supportsRtl="true"
tools:targetApi="31">
- .
- */
-
-package eur.cfpifpsi.titanboard;
-
-import android.content.Context;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class KeymapManager {
- private static final String TAG_KEY = "key"; //NON-NLS
- private static final String TAG_KEYMAP = "keymap"; //NON-NLS
- private static final String TAG_BASE = "base"; //NON-NLS
- private static final String TAG_ALT = "alt"; //NON-NLS
- private static final String TAG_SHIFT = "shift"; //NON-NLS
- private static final String ATTR_CODE = "code"; //NON-NLS
- private static final String ATTR_VALUE = "value"; //NON-NLS
- private static final String ATTR_LABEL = "label"; //NON-NLS
- private static final String ATTR_LATCHED_LABEL = "latched-label"; //NON-NLS
- private static final String ATTR_LOCKED_LABEL = "locked-label"; //NON-NLS
-
- private final Context mContext;
- private final HashMap mKeymaps;
- private Keymap mCurrentKeymap;
-
- public KeymapManager(Context context) {
- mContext = context;
- mKeymaps = new HashMap<>(context.getResources()
- .getStringArray(R.array.preferences_keymaps).length);
- loadDeviceKeymaps(context.getResources().getXml(R.xml.keymaps_titan));
- }
-
- private void loadDeviceKeymaps(XmlPullParser parser) {
- Keymap keymap = null;
- int code = 0;
- String label = null, latchedLabel = null, lockedLabel = null;
- Key key = null;
-
- while (true) {
- int eventType;
-
- try {
- eventType = parser.getEventType();
- } catch (XmlPullParserException e) {
- break;
- }
-
- if (eventType == XmlPullParser.END_DOCUMENT) break;
-
- // We encounter a new keymap.
- if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_KEYMAP)) {
- keymap = new Keymap();
- int attributeCount = parser.getAttributeCount();
- for (int i = 0; i < attributeCount; ++i) {
- switch (parser.getAttributeName(i)) {
- case ATTR_LABEL:
- label = parser.getAttributeValue(i);
- break;
- case ATTR_LATCHED_LABEL:
- latchedLabel = parser.getAttributeValue(i);
- break;
- case ATTR_LOCKED_LABEL:
- lockedLabel = parser.getAttributeValue(i);
- break;
- }
- }
- } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG_KEYMAP)
- && keymap != null) {
- keymap.code = label;
- keymap.latchedCode = latchedLabel;
- keymap.lockedCode = lockedLabel;
- mKeymaps.put(label, keymap);
- }
-
- // We got a new key.
- if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_KEY)) {
- key = new Key();
- int attributeCount = parser.getAttributeCount();
- for (int i = 0; i < attributeCount; ++i) {
- if (parser.getAttributeName(i).equals(ATTR_CODE)) {
- code = Integer.parseInt(parser.getAttributeValue(i));
- }
- }
- } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG_KEY)
- && keymap != null) {
- keymap.put(code, key);
- }
-
- // We encountered a base value.
- if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_BASE)) {
- int attributeCount = parser.getAttributeCount();
- for (int i = 0; i < attributeCount; ++i) {
- if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
- key.symbolsBase.add(parser.getAttributeValue(i));
- }
- }
- }
- // We encountered a shift value.
- if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_SHIFT)) {
- int attributeCount = parser.getAttributeCount();
- for (int i = 0; i < attributeCount; ++i) {
- if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
- key.symbolsShift.add(parser.getAttributeValue(i));
- }
- }
- }
- // We encountered an alt value.
- if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_ALT)) {
- int attributeCount = parser.getAttributeCount();
- for (int i = 0; i < attributeCount; ++i) {
- if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
- key.symbolsAlt.add(parser.getAttributeValue(i));
- }
- }
- }
-
- try {
- parser.next();
- } catch (XmlPullParserException | IOException e) {
- break;
- }
- }
- }
-
- public ArrayList getSymbols(int keyCode, MetaKeyManager metaKeyManager) {
- ArrayList alt = mCurrentKeymap.getSymbolsAlt(keyCode);
- ArrayList shift = mCurrentKeymap.getSymbolsShift(keyCode);
-
- if (metaKeyManager.getAlt().isActive() && alt != null && !alt.isEmpty()) {
- return alt;
- }
-
- if (metaKeyManager.getShift().isActive() && shift != null && !shift.isEmpty()) {
- return shift;
- }
-
- return mCurrentKeymap.getSymbolsBase(keyCode);
- }
-
- public void adjustCurrentKeymap(List selectedKeymaps) {
- if (!selectedKeymaps.contains(mCurrentKeymap.code)) {
- nextKeymap(selectedKeymaps);
- }
- }
-
- public void nextKeymap(List selectedKeymaps) {
- if (!selectedKeymaps.isEmpty() && mCurrentKeymap != null) {
- int index = selectedKeymaps.indexOf(mCurrentKeymap.code);
- index++;
-
- if (index == selectedKeymaps.size()) {
- index = 0;
- }
-
- mCurrentKeymap = mKeymaps.get(selectedKeymaps.get(index));
- } else {
- mCurrentKeymap =
- mKeymaps.get(mContext.getResources().getString(R.string.keymap_default));
- }
- }
-
- public String getCurrentLabel() {
- return mCurrentKeymap.code;
- }
-
- public String getCurrentLatchedLabel() {
- return mCurrentKeymap.latchedCode;
- }
-
- public String getCurrentLockedLabel() {
- return mCurrentKeymap.lockedCode;
- }
-
- static class Keymap {
- private final static int INITIAL_KEYMAP_SIZE = 64; // Enough for just about any QWERTY.
- private final HashMap mMap = new HashMap<>(INITIAL_KEYMAP_SIZE);
- String code;
- String latchedCode;
- String lockedCode;
-
- public ArrayList getSymbolsBase(int keyCode) {
- Key key = mMap.get(keyCode);
- return key == null ? null : key.symbolsBase;
- }
-
- public ArrayList getSymbolsAlt(int keyCode) {
- Key key = mMap.get(keyCode);
- return key == null ? null : key.symbolsAlt;
- }
-
- public ArrayList getSymbolsShift(int keyCode) {
- Key key = mMap.get(keyCode);
- return key == null ? null : key.symbolsShift;
- }
-
- public void put(Integer key, Key value) {
- mMap.put(key, value);
- }
- }
-
- static class Key {
- private final static int INITIAL_SIZE = 6;
- final ArrayList symbolsBase = new ArrayList<>(INITIAL_SIZE);
- final ArrayList symbolsShift = new ArrayList<>(INITIAL_SIZE);
- final ArrayList symbolsAlt = new ArrayList<>(INITIAL_SIZE);
- }
-}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/MetaKey.java b/app/src/main/java/eur/cfpifpsi/titanboard/MetaKey.java
index 7d9ad49..d3d6bcf 100644
--- a/app/src/main/java/eur/cfpifpsi/titanboard/MetaKey.java
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/MetaKey.java
@@ -8,7 +8,7 @@ public class MetaKey {
private boolean mLatched;
private boolean mLocked;
private boolean mUsed;
- private long mKeyUpTime;
+ private long mLastKeyUpTime;
public void onDown() {
mUsed = false;
@@ -20,11 +20,11 @@ public class MetaKey {
}
public void onUp(KeyEvent event) {
- if (event.getEventTime() - mKeyUpTime < TOGGLE_DURATION) {
+ if (event.getEventTime() - mLastKeyUpTime < TOGGLE_DURATION) {
mLocked = true;
}
- mKeyUpTime = event.getEventTime();
+ mLastKeyUpTime = event.getEventTime();
mPressed = false;
mLatched = !mLatched && !mLocked && !mUsed;
}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/PreferenceHolder.java b/app/src/main/java/eur/cfpifpsi/titanboard/PreferenceHolder.java
index 76499cc..9824ab2 100644
--- a/app/src/main/java/eur/cfpifpsi/titanboard/PreferenceHolder.java
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/PreferenceHolder.java
@@ -77,9 +77,9 @@ public class PreferenceHolder implements SharedPreferences.OnSharedPreferenceCha
@SuppressWarnings("unchecked") //NON-NLS
public ArrayList getSelectedKeymaps() {
Set selectedKeymaps = (Set)
- getOrDefault(mContext.getString(R.string.preferences_key_keymaps),
+ getOrDefault(mContext.getString(R.string.preferences_key_languages),
mContext.getResources()
- .getStringArray(R.array.preferences_keymap_defaults));
+ .getStringArray(R.array.preferences_language_defaults));
return new ArrayList<>(selectedKeymaps);
}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/TitanboardIME.java b/app/src/main/java/eur/cfpifpsi/titanboard/TitanboardIME.java
index a5d6c4f..ea6a4e3 100644
--- a/app/src/main/java/eur/cfpifpsi/titanboard/TitanboardIME.java
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/TitanboardIME.java
@@ -32,14 +32,15 @@ import android.view.inputmethod.InputConnection;
import java.util.ArrayList;
+import eur.cfpifpsi.titanboard.keymapping.KeymapManager;
import eur.cfpifpsi.titanboard.views.InputView;
-// TODO: Revamp the keymap system — separate device keymap, keymap types (12-key, alphabetic, etc.)
// TODO: Add auto-capitalization
// TODO: Add auto-periods
// TODO: Add auto-correct
// TODO: Emojis with popup window
// TODO: Composing with diacritics, polytonic Greek as well
+// TODO: Fix up the tweaks popup so it matches in style other UI elements
public class TitanboardIME extends InputMethodService {
public MetaKeyManager mMetaKeyManager;
@@ -122,7 +123,7 @@ public class TitanboardIME extends InputMethodService {
return false;
}
- ArrayList symbols = mKeymapManager.getSymbols(keyCode, mMetaKeyManager);
+ ArrayList symbols = mKeymapManager.getCurrentKeySymbolsMeta(keyCode, mMetaKeyManager);
InputConnection conn = getCurrentInputConnection();
EditorInfo info = getCurrentInputEditorInfo();
@@ -224,7 +225,6 @@ public class TitanboardIME extends InputMethodService {
@Override
public void onFinishInput() {
mMetaKeyManager.resetMetaKeyState();
- mInputView.mPopup.dismiss();
}
// Cyclers & runnables
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeySymbols.java b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeySymbols.java
new file mode 100644
index 0000000..fde3a53
--- /dev/null
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeySymbols.java
@@ -0,0 +1,52 @@
+/*
+ * An input method for physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package eur.cfpifpsi.titanboard.keymapping;
+
+import java.util.ArrayList;
+
+public final class KeySymbols {
+ private final static int INITIAL_SIZE = 6;
+ private final ArrayList mBase = new ArrayList<>(INITIAL_SIZE);
+ private final ArrayList mShift = new ArrayList<>(INITIAL_SIZE);
+ private final ArrayList mAlt = new ArrayList<>(INITIAL_SIZE);
+
+ public ArrayList getBase() {
+ return mBase;
+ }
+
+ public ArrayList getShift() {
+ return mShift;
+ }
+
+ public ArrayList getAlt() {
+ return mAlt;
+ }
+
+ public void addBase(String symbol) {
+ mBase.add(symbol);
+ }
+
+ public void addShift(String symbol) {
+ mShift.add(symbol);
+ }
+
+ public void addAlt(String symbol) {
+ mAlt.add(symbol);
+ }
+}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/Keymap.java b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/Keymap.java
new file mode 100644
index 0000000..e3d4c07
--- /dev/null
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/Keymap.java
@@ -0,0 +1,58 @@
+/*
+ * An input method for physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package eur.cfpifpsi.titanboard.keymapping;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import eur.cfpifpsi.titanboard.MetaKeyManager;
+
+public class Keymap {
+ private final static int INITIAL_KEYMAP_SIZE = 64; // Enough for just about any QWERTY.
+ private final HashMap mMap = new HashMap<>(INITIAL_KEYMAP_SIZE);
+
+ public ArrayList getSymbolsMeta(int keyCode, MetaKeyManager metaKeyManager) {
+ if (metaKeyManager.getAlt().isActive()) {
+ return getSymbolsAlt(keyCode);
+ } else if (metaKeyManager.getShift().isActive()) {
+ return getSymbolsShift(keyCode);
+ } else {
+ return getSymbolsBase(keyCode);
+ }
+ }
+
+ public ArrayList getSymbolsBase(int keyCode) {
+ KeySymbols key = mMap.get(keyCode);
+ return key == null ? null : key.getBase();
+ }
+
+ public ArrayList getSymbolsAlt(int keyCode) {
+ KeySymbols key = mMap.get(keyCode);
+ return key == null ? null : key.getAlt();
+ }
+
+ public ArrayList getSymbolsShift(int keyCode) {
+ KeySymbols key = mMap.get(keyCode);
+ return key == null ? null : key.getShift();
+ }
+
+ public void putKeySymbols(Integer key, KeySymbols value) {
+ mMap.put(key, value);
+ }
+}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapLoader.java b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapLoader.java
new file mode 100644
index 0000000..050e52b
--- /dev/null
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapLoader.java
@@ -0,0 +1,248 @@
+/*
+ * An input method for physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package eur.cfpifpsi.titanboard.keymapping;
+
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public final class KeymapLoader {
+ private static final String TAG_SYMBOLS = "symbols"; //NON-NLS
+ private static final String TAG_ATTACHED_SYMBOLS = "attached-symbols";
+ private static final String TAG_LANGUAGE_KEYMAP = "language-keymap"; //NON-NLS
+ private static final String TAG_DEVICE_KEYMAP = "device-keymap"; //NON-NLS
+ private static final String TAG_SYMBOL = "symbol";
+ private static final String TAG_BASE = "base"; //NON-NLS
+ private static final String TAG_ALT = "alt"; //NON-NLS
+ private static final String TAG_SHIFT = "shift"; //NON-NLS
+ private static final String ATTR_FOR_CODE = "for-code"; //NON-NLS
+ private static final String ATTR_FOR_SYMBOL = "for-symbol"; //NON-NLS
+ private static final String ATTR_VALUE = "value"; //NON-NLS
+ private static final String ATTR_CODE = "code"; //NON-NLS
+ private static final String ATTR_LATCHED_CODE = "latched-code"; //NON-NLS
+ private static final String ATTR_LOCKED_CODE = "locked-code"; //NON-NLS
+ private static final String ATTR_NAME = "name";
+
+ public static void loadLanguageKeymaps(HashMap keymaps,
+ XmlPullParser parser) {
+ LanguageKeymap keymap = null;
+
+ int code = 0;
+ KeySymbols key = null;
+
+ String forSymbol = null;
+ ArrayList attachedSymbols = null;
+
+ String keymapName = null;
+
+ while (true) {
+ int eventType;
+
+ try {
+ eventType = parser.getEventType();
+ } catch (XmlPullParserException e) {
+ break;
+ }
+
+ if (eventType == XmlPullParser.END_DOCUMENT) break;
+
+ // We encounter a new keymap.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_LANGUAGE_KEYMAP)) {
+ keymap = new LanguageKeymap();
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ switch (parser.getAttributeName(i)) {
+ case ATTR_CODE:
+ keymapName = parser.getAttributeValue(i);
+ keymap.setCode(parser.getAttributeValue(i));
+ break;
+ case ATTR_LATCHED_CODE:
+ keymap.setLatchedCode(parser.getAttributeValue(i));
+ break;
+ case ATTR_LOCKED_CODE:
+ keymap.setLockedCode(parser.getAttributeValue(i));
+ break;
+ }
+ }
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG_LANGUAGE_KEYMAP)
+ && keymap != null) {
+ keymaps.put(keymap.getCode(), keymap);
+ }
+
+ // We got a new key.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_SYMBOLS)) {
+ key = new KeySymbols();
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).equals(ATTR_FOR_CODE)) {
+ code = Integer.parseInt(parser.getAttributeValue(i));
+ }
+ }
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG_SYMBOLS)
+ && keymap != null) {
+ keymap.putKeySymbols(code, key);
+ }
+
+ // We got new attached symbols.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_ATTACHED_SYMBOLS)) {
+ attachedSymbols = new ArrayList<>();
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).equals(ATTR_FOR_SYMBOL)) {
+ forSymbol = parser.getAttributeValue(i);
+ }
+ }
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG_ATTACHED_SYMBOLS)
+ && keymap != null) {
+ keymap.putAttachedSymbols(forSymbol, attachedSymbols);
+ }
+
+ // We got a new attached symbol.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_SYMBOL)) {
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && attachedSymbols != null) {
+ attachedSymbols.add(parser.getAttributeValue(i));
+ }
+ }
+ }
+
+ // We encountered a base value.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_BASE)) {
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
+ key.addBase(parser.getAttributeValue(i));
+ }
+ }
+ }
+ // We encountered a shift value.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_SHIFT)) {
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
+ key.addShift(parser.getAttributeValue(i));
+ }
+ }
+ }
+ // We encountered an alt value.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_ALT)) {
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
+ key.addAlt(parser.getAttributeValue(i));
+ }
+ }
+ }
+
+ try {
+ parser.next();
+ } catch (XmlPullParserException | IOException e) {
+ break;
+ }
+ }
+ }
+
+ public static void loadDeviceKeymaps(HashMap keymaps,
+ XmlPullParser parser) {
+ Keymap keymap = null;
+ int code = 0;
+ String name = null;
+ KeySymbols key = null;
+
+ while (true) {
+ int eventType;
+
+ try {
+ eventType = parser.getEventType();
+ } catch (XmlPullParserException e) {
+ break;
+ }
+
+ if (eventType == XmlPullParser.END_DOCUMENT) break;
+
+ // We encounter a new keymap.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_DEVICE_KEYMAP)) {
+ keymap = new Keymap();
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).equals(ATTR_NAME)) {
+ name = parser.getAttributeValue(i);
+ }
+ }
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG_DEVICE_KEYMAP)
+ && keymap != null) {
+ keymaps.put(name, keymap);
+ }
+
+ // We got a new key.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_SYMBOLS)) {
+ key = new KeySymbols();
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).equals(ATTR_FOR_CODE)) {
+ code = Integer.parseInt(parser.getAttributeValue(i));
+ }
+ }
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG_SYMBOLS)
+ && keymap != null) {
+ keymap.putKeySymbols(code, key);
+ }
+
+ // We encountered a base value.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_BASE)) {
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
+ key.addBase(parser.getAttributeValue(i));
+ }
+ }
+ }
+ // We encountered a shift value.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_SHIFT)) {
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
+ key.addShift(parser.getAttributeValue(i));
+ }
+ }
+ }
+ // We encountered an alt value.
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TAG_ALT)) {
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; ++i) {
+ if (parser.getAttributeName(i).startsWith(ATTR_VALUE) && key != null) {
+ key.addAlt(parser.getAttributeValue(i));
+ }
+ }
+ }
+
+ try {
+ parser.next();
+ } catch (XmlPullParserException | IOException e) {
+ break;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapManager.java b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapManager.java
new file mode 100644
index 0000000..c87b40e
--- /dev/null
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/KeymapManager.java
@@ -0,0 +1,108 @@
+/*
+ * An input method for physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package eur.cfpifpsi.titanboard.keymapping;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import eur.cfpifpsi.titanboard.MetaKeyManager;
+import eur.cfpifpsi.titanboard.R;
+
+public class KeymapManager {
+ private final Context mContext;
+ private final HashMap mDeviceKeymaps;
+ private final HashMap mLanguageKeymaps;
+ private LanguageKeymap mCurrentLanguageKeymap;
+
+ public KeymapManager(Context context) {
+ mContext = context;
+ mDeviceKeymaps = new HashMap<>(2);
+ mLanguageKeymaps = new HashMap<>(context.getResources()
+ .getStringArray(R.array.preferences_languages).length);
+ KeymapLoader.loadDeviceKeymaps(mDeviceKeymaps,
+ context.getResources().getXml(R.xml.device_keymaps));
+ KeymapLoader.loadLanguageKeymaps(mLanguageKeymaps,
+ context.getResources().getXml(R.xml.language_keymaps));
+ }
+
+ public ArrayList getCurrentKeySymbolsMeta(int keyCode, MetaKeyManager metaKeyManager) {
+ Keymap deviceKeymap = mDeviceKeymaps.get("titan");
+ ArrayList deviceSymbols = deviceKeymap != null
+ ? deviceKeymap.getSymbolsMeta(keyCode, metaKeyManager) : new ArrayList<>();
+ ArrayList languageSymbols = mCurrentLanguageKeymap.getSymbolsMeta(keyCode, metaKeyManager);
+ ArrayList combinedSymbols = new ArrayList<>();
+
+ if (deviceSymbols != null) {
+ combinedSymbols.addAll(deviceSymbols);
+ }
+
+ if (languageSymbols != null) {
+ combinedSymbols.addAll(languageSymbols);
+ }
+
+ // We NEED to do this as not to iterate and modify combinedSymbols at the same time.
+ ArrayList finalSymbols = new ArrayList<>(combinedSymbols);
+ for (String symbol : combinedSymbols) {
+ ArrayList attachedSymbols = mCurrentLanguageKeymap.getAttachedSymbols(symbol);
+ if (attachedSymbols != null) {
+ finalSymbols.addAll(attachedSymbols);
+ }
+ }
+
+ return finalSymbols;
+ }
+
+ public void adjustCurrentKeymap(List selectedKeymaps) {
+ if (!selectedKeymaps.contains(mCurrentLanguageKeymap.getCode())) {
+ nextKeymap(selectedKeymaps);
+ }
+ }
+
+ public void nextKeymap(List selectedKeymaps) {
+ if (!selectedKeymaps.isEmpty() && mCurrentLanguageKeymap != null) {
+ int index = selectedKeymaps.indexOf(mCurrentLanguageKeymap.getCode());
+ index++;
+
+ if (index == selectedKeymaps.size()) {
+ index = 0;
+ }
+
+ mCurrentLanguageKeymap = mLanguageKeymaps.get(selectedKeymaps.get(index));
+ } else {
+ mCurrentLanguageKeymap =
+ mLanguageKeymaps.get(mContext.getResources().getString(R.string.language_default));
+ }
+ }
+
+ public String getCurrentCode() {
+ return mCurrentLanguageKeymap.getCode();
+ }
+
+ public String getCurrentLatchedCode() {
+ return mCurrentLanguageKeymap.getLatchedCode();
+ }
+
+ public String getCurrentLockedCode() {
+ return mCurrentLanguageKeymap.getLockedCode();
+ }
+
+}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/LanguageKeymap.java b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/LanguageKeymap.java
new file mode 100644
index 0000000..3f16988
--- /dev/null
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/keymapping/LanguageKeymap.java
@@ -0,0 +1,61 @@
+/*
+ * An input method for physical keyboard phones
+ * Copyright (C) 2024 Emil Kosz
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package eur.cfpifpsi.titanboard.keymapping;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public final class LanguageKeymap extends Keymap {
+ private final HashMap> mAttachedSymbols = new HashMap<>();
+ private String mCode;
+ private String mLatchedCode;
+ private String mLockedCode;
+
+ public String getCode() {
+ return mCode;
+ }
+
+ public String getLatchedCode() {
+ return mLatchedCode;
+ }
+
+ public String getLockedCode() {
+ return mLockedCode;
+ }
+
+ public void setCode(String value) {
+ mCode = value;
+ }
+
+ public void setLatchedCode(String value) {
+ mLatchedCode = value;
+ }
+
+ public void setLockedCode(String value) {
+ mLockedCode = value;
+ }
+
+ public ArrayList getAttachedSymbols(String key) {
+ return mAttachedSymbols.get(key);
+ }
+
+ public void putAttachedSymbols(String key, ArrayList value) {
+ mAttachedSymbols.put(key, value);
+ }
+}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/views/ClipboardView.java b/app/src/main/java/eur/cfpifpsi/titanboard/views/ClipboardView.java
index bdc3bd1..4c90534 100644
--- a/app/src/main/java/eur/cfpifpsi/titanboard/views/ClipboardView.java
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/views/ClipboardView.java
@@ -39,7 +39,7 @@ public class ClipboardView extends ScrollView {
private LinearLayout mClipList;
public ClipboardView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
+ super(context, attrs);
mTitanboardIME = (TitanboardIME) context;
mClipLayout = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -68,14 +68,21 @@ public class ClipboardView extends ScrollView {
Button pasteButton = clipView.findViewById(R.id.paste_button);
pasteButton.setText(item.getText());
- pasteButton.setOnClickListener(button -> mTitanboardIME.getCurrentInputConnection()
- .commitText(item.getText(), 1));
+ pasteButton.setOnClickListener(button -> pasteClip(item));
LinearLayout finalClipView = clipView;
- clipView.findViewById(R.id.button_delete).setOnClickListener(button -> {
- mClipList.removeView(finalClipView);
- mTitanboardIME.mClipManager.removeItem(item);
- });
+ clipView.findViewById(R.id.button_delete)
+ .setOnClickListener(button -> deleteClip(finalClipView, item));
}
}
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private void pasteClip(ClipData.Item item) {
+ mTitanboardIME.getCurrentInputConnection().commitText(item.getText(), 1);
+ }
+
+ private void deleteClip(LinearLayout clipView, ClipData.Item item) {
+ mClipList.removeView(clipView);
+ mTitanboardIME.mClipManager.removeItem(item);
+ }
}
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/views/InputView.java b/app/src/main/java/eur/cfpifpsi/titanboard/views/InputView.java
index dd215f6..e10e881 100644
--- a/app/src/main/java/eur/cfpifpsi/titanboard/views/InputView.java
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/views/InputView.java
@@ -24,6 +24,7 @@ import android.content.Intent;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.PopupWindow;
@@ -31,7 +32,7 @@ import android.widget.RelativeLayout;
import java.util.ArrayList;
-import eur.cfpifpsi.titanboard.KeymapManager;
+import eur.cfpifpsi.titanboard.keymapping.KeymapManager;
import eur.cfpifpsi.titanboard.MetaKeyManager;
import eur.cfpifpsi.titanboard.R;
import eur.cfpifpsi.titanboard.TitanboardIME;
@@ -40,7 +41,7 @@ import eur.cfpifpsi.titanboard.activities.SettingsActivity;
public final class InputView extends RelativeLayout {
private final int[] buttonStateToggled = {R.attr.state_toggled};
private final TitanboardIME mTitanboardIME;
- public PopupWindow mPopup;
+ private PopupWindow mPopup;
private Button mStateButton;
private ImageButton mClipboardButton;
@@ -51,7 +52,7 @@ public final class InputView extends RelativeLayout {
private TweaksView mTweaksView;
public InputView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
+ super(context, attrs);
mTitanboardIME = (TitanboardIME) context;
}
@@ -94,13 +95,8 @@ public final class InputView extends RelativeLayout {
return true;
});
- mClipboardButton.setOnClickListener(button -> {
- mPopup.setContentView(mClipboardView);
- mPopup.setHeight(getResources().getDimensionPixelSize(R.dimen.clipboard_popup_height));
- mPopup.showAsDropDown(mClipboardButton,
- -(mPopup.getWidth() - mClipboardButton.getWidth()) / 2, 0);
- mClipboardButton.setImageState(buttonStateToggled, true);
- });
+ mClipboardButton.setOnClickListener(button -> showPopup(mClipboardButton, mClipboardView,
+ R.dimen.clipboard_popup_height));
mClipboardButton.setOnLongClickListener(button -> {
// On a long click, we just send the latest clip (if any).
@@ -113,13 +109,22 @@ public final class InputView extends RelativeLayout {
return true;
});
- mTweaksButton.setOnClickListener(button -> {
- mPopup.setContentView(mTweaksView);
- mPopup.setHeight(getResources().getDimensionPixelSize(R.dimen.tweaks_popup_height));
- mPopup.showAsDropDown(mTweaksButton,
- -(mPopup.getWidth() - mTweaksButton.getWidth()) / 2, 0);
- mTweaksButton.setImageState(buttonStateToggled, true);
- });
+ mTweaksButton.setOnClickListener(button -> showPopup(mTweaksButton, mTweaksView,
+ R.dimen.tweaks_popup_height));
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mPopup.dismiss();
+ super.onDetachedFromWindow();
+ }
+
+ private void showPopup(ImageButton button, View contentView, int popupHeight) {
+ mPopup.setContentView(contentView);
+ mPopup.setHeight(getResources().getDimensionPixelSize(popupHeight));
+ mPopup.showAsDropDown(button,
+ -(mPopup.getWidth() - button.getWidth()) / 2, 0);
+ button.setImageState(buttonStateToggled, true);
}
public void updateStateText() {
@@ -143,16 +148,16 @@ public final class InputView extends RelativeLayout {
}
if (metaManager.getShift().isPressed() || metaManager.getShift().isLocked()) {
- mStateButton.setText(keymapManager.getCurrentLockedLabel());
+ mStateButton.setText(keymapManager.getCurrentLockedCode());
return;
}
if (metaManager.getShift().isLatched()) {
- mStateButton.setText(keymapManager.getCurrentLatchedLabel());
+ mStateButton.setText(keymapManager.getCurrentLatchedCode());
return;
}
- mStateButton.setText(keymapManager.getCurrentLabel());
+ mStateButton.setText(keymapManager.getCurrentCode());
}
public void updateTweaks() {
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/views/SymbolsView.java b/app/src/main/java/eur/cfpifpsi/titanboard/views/SymbolsView.java
index a544018..831e879 100644
--- a/app/src/main/java/eur/cfpifpsi/titanboard/views/SymbolsView.java
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/views/SymbolsView.java
@@ -34,7 +34,7 @@ public class SymbolsView extends HorizontalScrollView {
private LinearLayout mSymbolList;
public SymbolsView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
+ super(context, attrs);
mSymbolLayout = new LinearLayout.LayoutParams(
getResources().getDimensionPixelSize(R.dimen.symbol_width),
LinearLayout.LayoutParams.MATCH_PARENT, 0);
@@ -54,6 +54,11 @@ public class SymbolsView extends HorizontalScrollView {
* @noinspection MethodWithMultipleLoops
*/
public void setSymbols(ArrayList strings) {
+ // Once we set the symbols, we also scroll back to the very front. This is in case we have
+ // previously used a symbol that's far away, so that the user doesn't get a visual shock
+ // since setSelectedSymbol would smoothScroll to the beginning.
+ scrollTo(0, 0);
+
int size = strings.size();
for (int i = 0; i < size; ++i) {
TextView tv = (TextView) mSymbolList.getChildAt(i);
diff --git a/app/src/main/java/eur/cfpifpsi/titanboard/views/TweaksView.java b/app/src/main/java/eur/cfpifpsi/titanboard/views/TweaksView.java
index a7f5252..ca4a837 100644
--- a/app/src/main/java/eur/cfpifpsi/titanboard/views/TweaksView.java
+++ b/app/src/main/java/eur/cfpifpsi/titanboard/views/TweaksView.java
@@ -34,7 +34,7 @@ public class TweaksView extends LinearLayout {
private CheckBox mEnterCheckbox;
public TweaksView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
+ super(context, attrs);
mTitanboardIME = (TitanboardIME) context;
}
diff --git a/app/src/main/res/drawable/ic_faders.xml b/app/src/main/res/drawable/ic_faders.xml
new file mode 100644
index 0000000..ec7704a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_faders.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_tweaks.xml b/app/src/main/res/drawable/ic_tweaks.xml
deleted file mode 100644
index d85c0f6..0000000
--- a/app/src/main/res/drawable/ic_tweaks.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/input.xml b/app/src/main/res/layout/input.xml
index 60d961e..feda9f1 100644
--- a/app/src/main/res/layout/input.xml
+++ b/app/src/main/res/layout/input.xml
@@ -30,7 +30,9 @@
android:layout_height="match_parent"
android:layout_alignTop="@id/clipboard_button"
android:layout_alignBottom="@id/clipboard_button"
+ android:layout_toStartOf="@id/tweaks_button"
android:layout_toLeftOf="@id/tweaks_button"
+ android:contentDescription="@string/symbols_view_description"
android:scrollbarThumbHorizontal="@color/text_color">
+ android:contentDescription="@string/tweaks_button_description"
+ android:src="@drawable/ic_faders" />
Symbol cycling speed/timeoutClipboard size
- Languages
+ Device
+ LanguagesUser manual
@@ -54,13 +62,16 @@
Copyleft 🄯 Emil Kosz, 2024. License: GPLv3
+
cycle_manual
- selected_keymaps
+ selected_keymaps
+ devicecycle_speedclipboard_sizeAlternate composing method
- Enter always sends message
+ Enter always sendsRaw input modealternate_composing_for_
@@ -105,12 +116,54 @@
If you tried the above, you might have noticed that the Alt or Shift keys can be
- "locked" (or latched). This is the same behaviour like in regular visual
+ "locked" (or latched). This is the same behaviour like in regular virtual
keyboards Android. Simply put, pressing a modifier key once puts it into a latched
state, which means the next key pressed will be uppercased or be the alternative symbol.
You can also lock a modifier key by pressing it twice in a quick fashion. Otherwise,
modifier keys work as expected.
+
User Interface
+
+ The user interface contains four elements: the symbol list, the tweaks button (three
+ lines with circles mimicking fader controls), the clipboard button (a clipboard icon),
+ and the state/language button (the white square with letters). The first, if not
+ cycling, contains nothing and looks like a blank background. If you start typing in
+ extra symbols, then the available symbols and the currently selected one are shown.
+
+
+ The tweaks button, when pressed, will show a window with app-specific tweaks. These fix
+ often encountered problems that happen in interactions between apps and physical
+ keyboards. These settings can be set and are saved for every app. The "Alternate
+ composing method" tweak is useful for applications like a terminal, which do not
+ support composing characters. You'll know an application doesn't support it
+ when cycling symbols feels off, and extra characters are inputted other than the one you
+ desire. The "Enter always sends" tweak makes it so pressing the Enter key
+ always tries to send a message instead of inserting a new line.
+
+
+ The clipboard button, when pressed, shows the clipboard with all the recent copied
+ stuff. Each clip has a red trashcan next to it which deletes it from the list. Clicking
+ on the clip itself sends it to the application. Long pressing the clipboard button sends
+ the most recent clip to the application.
+
+
+ The language button shows the current language being used, in a two letter code
+ (English is en/En/EN, Arabic is ar/Ar/AR, etc.). It also shows the state of the modifier
+ keys: if there are no modifier keys latched, locked, or pressed, the language code is in
+ all lowercase — "en". If Shift is latched, the first letter is uppercased —
+ "En". If it is pressed, then the code is all uppercase — "EN". If it
+ is locked, then it is all uppercase and underlined. The case is similar for the Alt key;
+ if Alt is latched, the button displays "#?", if it is pressed, then
+ "#!", and if locked, then the same as pressed but underlined.
+
+
+ Pressing the language button switches to the next language. Long pressing the language
+ button opens the keyboard settings.
+
+
Miscellaneous
+
+ Regardless of the Enter key tweak, Shift+Enter will always input a new line.
+