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" />