Some major changes, keymap restructuring with device and language keymaps

This commit is contained in:
2024-11-25 21:14:45 +01:00
parent ec931a53a6
commit e47227ce6d
23 changed files with 2159 additions and 2002 deletions
-3
View File
@@ -29,9 +29,6 @@
android:roundIcon="@drawable/ic_app_round"
android:supportsRtl="true"
tools:targetApi="31">
<profileable
android:shell="true"
tools:targetApi="q" />
<service
android:name=".TitanboardIME"
android:directBootAware="true"
@@ -1,228 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<String, Keymap> 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<String> getSymbols(int keyCode, MetaKeyManager metaKeyManager) {
ArrayList<String> alt = mCurrentKeymap.getSymbolsAlt(keyCode);
ArrayList<String> 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<String> selectedKeymaps) {
if (!selectedKeymaps.contains(mCurrentKeymap.code)) {
nextKeymap(selectedKeymaps);
}
}
public void nextKeymap(List<String> 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<Integer, Key> mMap = new HashMap<>(INITIAL_KEYMAP_SIZE);
String code;
String latchedCode;
String lockedCode;
public ArrayList<String> getSymbolsBase(int keyCode) {
Key key = mMap.get(keyCode);
return key == null ? null : key.symbolsBase;
}
public ArrayList<String> getSymbolsAlt(int keyCode) {
Key key = mMap.get(keyCode);
return key == null ? null : key.symbolsAlt;
}
public ArrayList<String> 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<String> symbolsBase = new ArrayList<>(INITIAL_SIZE);
final ArrayList<String> symbolsShift = new ArrayList<>(INITIAL_SIZE);
final ArrayList<String> symbolsAlt = new ArrayList<>(INITIAL_SIZE);
}
}
@@ -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;
}
@@ -77,9 +77,9 @@ public class PreferenceHolder implements SharedPreferences.OnSharedPreferenceCha
@SuppressWarnings("unchecked") //NON-NLS
public ArrayList<String> getSelectedKeymaps() {
Set<String> selectedKeymaps = (Set<String>)
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);
}
@@ -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<String> symbols = mKeymapManager.getSymbols(keyCode, mMetaKeyManager);
ArrayList<String> 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
@@ -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 <https://www.gnu.org/licenses/>.
*/
package eur.cfpifpsi.titanboard.keymapping;
import java.util.ArrayList;
public final class KeySymbols {
private final static int INITIAL_SIZE = 6;
private final ArrayList<String> mBase = new ArrayList<>(INITIAL_SIZE);
private final ArrayList<String> mShift = new ArrayList<>(INITIAL_SIZE);
private final ArrayList<String> mAlt = new ArrayList<>(INITIAL_SIZE);
public ArrayList<String> getBase() {
return mBase;
}
public ArrayList<String> getShift() {
return mShift;
}
public ArrayList<String> 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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer, KeySymbols> mMap = new HashMap<>(INITIAL_KEYMAP_SIZE);
public ArrayList<String> 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<String> getSymbolsBase(int keyCode) {
KeySymbols key = mMap.get(keyCode);
return key == null ? null : key.getBase();
}
public ArrayList<String> getSymbolsAlt(int keyCode) {
KeySymbols key = mMap.get(keyCode);
return key == null ? null : key.getAlt();
}
public ArrayList<String> 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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, LanguageKeymap> keymaps,
XmlPullParser parser) {
LanguageKeymap keymap = null;
int code = 0;
KeySymbols key = null;
String forSymbol = null;
ArrayList<String> 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<String, Keymap> 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;
}
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Keymap> mDeviceKeymaps;
private final HashMap<String, LanguageKeymap> 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<String> getCurrentKeySymbolsMeta(int keyCode, MetaKeyManager metaKeyManager) {
Keymap deviceKeymap = mDeviceKeymaps.get("titan");
ArrayList<String> deviceSymbols = deviceKeymap != null
? deviceKeymap.getSymbolsMeta(keyCode, metaKeyManager) : new ArrayList<>();
ArrayList<String> languageSymbols = mCurrentLanguageKeymap.getSymbolsMeta(keyCode, metaKeyManager);
ArrayList<String> 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<String> finalSymbols = new ArrayList<>(combinedSymbols);
for (String symbol : combinedSymbols) {
ArrayList<String> attachedSymbols = mCurrentLanguageKeymap.getAttachedSymbols(symbol);
if (attachedSymbols != null) {
finalSymbols.addAll(attachedSymbols);
}
}
return finalSymbols;
}
public void adjustCurrentKeymap(List<String> selectedKeymaps) {
if (!selectedKeymaps.contains(mCurrentLanguageKeymap.getCode())) {
nextKeymap(selectedKeymaps);
}
}
public void nextKeymap(List<String> 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();
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
package eur.cfpifpsi.titanboard.keymapping;
import java.util.ArrayList;
import java.util.HashMap;
public final class LanguageKeymap extends Keymap {
private final HashMap<String, ArrayList<String>> 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<String> getAttachedSymbols(String key) {
return mAttachedSymbols.get(key);
}
public void putAttachedSymbols(String key, ArrayList<String> value) {
mAttachedSymbols.put(key, value);
}
}
@@ -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);
}
}
@@ -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() {
@@ -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<String> 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);
@@ -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;
}
+82
View File
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- <item android:top="11dp"-->
<!-- android:bottom="11dp"-->
<!-- android:right="13dp"-->
<!-- android:left="13dp">-->
<!-- <shape android:shape="rectangle">-->
<!-- <solid android:color="@android:color/darker_gray" />-->
<!-- </shape>-->
<!-- </item>-->
<item android:top="11dp"
android:bottom="11dp"
android:left="15dp"
android:right="31dp">
<shape android:shape="rectangle">
<solid android:color="@color/text_color" />
<corners android:radius="2dp" />
</shape>
</item>
<item android:top="21dp"
android:bottom="21dp"
android:left="13dp"
android:right="29dp">
<shape android:shape="oval">
<solid android:color="@color/text_color" />
</shape>
</item>
<item android:top="11dp"
android:bottom="11dp"
android:left="31dp"
android:right="15dp">
<shape android:shape="rectangle">
<solid android:color="@color/text_color" />
<corners android:radius="2dp" />
</shape>
</item>
<item android:top="27dp"
android:bottom="15dp"
android:left="29dp"
android:right="13dp">
<shape android:shape="oval">
<solid android:color="@color/text_color" />
</shape>
</item>
<item android:top="11dp"
android:bottom="11dp"
android:left="23dp"
android:right="23dp">
<shape android:shape="rectangle">
<solid android:color="@color/text_color" />
<corners android:radius="2dp" />
</shape>
</item>
<item android:top="15dp"
android:bottom="27dp"
android:left="21dp"
android:right="21dp">
<shape android:shape="oval">
<solid android:color="@color/text_color" />
</shape>
</item>
</layer-list>
-56
View File
@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="11dp"
android:bottom="11dp"
android:right="11dp"
android:left="11dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/darker_gray" />
</shape>
</item>
<item android:top="15dp"
android:bottom="15dp"
android:right="15dp"
android:left="15dp">
<shape android:shape="oval">
<stroke android:width="2dp"
android:color="@color/text_color" />
</shape>
</item>
<item android:top="11dp"
android:bottom="29dp"
android:left="20dp"
android:right="20dp">
<shape android:shape="oval">
<solid android:color="@color/text_color" />
</shape>
</item>
<item android:top="29dp"
android:bottom="11dp"
android:left="20dp"
android:right="20dp">
<shape android:shape="oval">
<solid android:color="@color/text_color" />
</shape>
</item>
</layer-list>
+7 -1
View File
@@ -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">
<LinearLayout
@@ -44,6 +46,7 @@
android:id="@+id/clipboard_button"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_toStartOf="@id/state_button"
android:layout_toLeftOf="@id/state_button"
android:background="@drawable/button"
android:contentDescription="@string/clipboard_button_description"
@@ -54,14 +57,17 @@
android:id="@+id/tweaks_button"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_toStartOf="@id/clipboard_button"
android:layout_toLeftOf="@id/clipboard_button"
android:background="@drawable/button"
android:src="@drawable/ic_tweaks" />
android:contentDescription="@string/tweaks_button_description"
android:src="@drawable/ic_faders" />
<Button
android:id="@+id/state_button"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="@drawable/button_state"
android:text="@string/state_button_default"
+13 -3
View File
@@ -17,7 +17,7 @@
-->
<resources>
<string-array name="preferences_keymap_labels">
<string-array name="preferences_language_labels">
<item>English</item>
<item>German</item>
<item>French</item>
@@ -29,7 +29,7 @@
<item>Arabic (Phonetic)</item>
</string-array>
<string-array name="preferences_keymaps">
<string-array name="preferences_languages">
<item>en</item>
<item>de</item>
<item>fr</item>
@@ -41,11 +41,21 @@
<item>ar</item>
</string-array>
<string-array name="preferences_keymap_defaults">
<string-array name="preferences_language_defaults">
<item>en</item>
<item>ru</item>
</string-array>
<string-array name="preferences_device_labels">
<item>Unihertz Titan</item>
<item>Unihertz Titan Pocket/Slim</item>
</string-array>
<string-array name="preferences_devices">
<item>titan</item>
<item>titan_pocket_slim</item>
</string-array>
<string-array name="preferences_cycle_speed_labels">
<item>200 ms</item>
<item>300 ms</item>
+58 -5
View File
@@ -21,7 +21,8 @@
<string name="preferences_name">Titanboard preferences</string>
<string name="manual_name">Titanboard manual</string>
<string name="keymap_default">en</string>
<string name="device_default">titan</string>
<string name="language_default">en</string>
<string name="cycle_speed_default">500</string>
<string name="clipboard_size_default">5</string>
@@ -33,6 +34,12 @@
<string name="clipboard_button_description">
Open clipboard or paste first item on long press
</string>
<string name="tweaks_button_description">
Open per-app tweaks window
</string>
<string name="symbols_view_description">
List visible when cycling through symbols
</string>
<string name="clip_default_text">Clip</string>
<string name="preferences_category_keyboard">Keyboard</string>
@@ -44,7 +51,8 @@
</string>
<string name="preferences_label_cycle_speed">Symbol cycling speed/timeout</string>
<string name="preferences_label_clipboard_size">Clipboard size</string>
<string name="preferences_label_keymaps">Languages</string>
<string name="preferences_label_device">Device</string>
<string name="preferences_label_languages">Languages</string>
<string name="preferences_label_user_manual">User manual</string>
<string name="preferences_summary_cycle_manual">
@@ -54,13 +62,16 @@
Copyleft 🄯 Emil Kosz, 2024. License: GPLv3
</string>
<!-- Even though some may not make sense, leave them be as not to break users' configurations
when updating -->
<string name="preferences_key_cycle_manual">cycle_manual</string>
<string name="preferences_key_keymaps">selected_keymaps</string>
<string name="preferences_key_languages">selected_keymaps</string>
<string name="preferences_key_device">device</string>
<string name="preferences_key_cycle_speed">cycle_speed</string>
<string name="preferences_key_clipboard_size">clipboard_size</string>
<string name="tweaks_label_alternate_composing">Alternate composing method</string>
<string name="tweaks_label_enter_always_sends">Enter always sends message</string>
<string name="tweaks_label_enter_always_sends">Enter always sends</string>
<string name="tweaks_label_raw_input_mode">Raw input mode</string>
<string name="tweaks_key_alternate_composing">alternate_composing_for_</string>
@@ -105,12 +116,54 @@
</p>
<p>
If you tried the above, you might have noticed that the Alt or Shift keys can be
&quot;locked&quot; (or latched). This is the same behaviour like in regular visual
&quot;locked&quot; (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.
</p>
<h1>User Interface</h1>
<p>
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.
</p>
<p>
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 &quot;Alternate
composing method&quot; tweak is useful for applications like a terminal, which do not
support composing characters. You&apos;ll know an application doesn&apos;t support it
when cycling symbols feels off, and extra characters are inputted other than the one you
desire. The &quot;Enter always sends&quot; tweak makes it so pressing the Enter key
always tries to send a message instead of inserting a new line.
</p>
<p>
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.
</p>
<p>
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 — &quot;en&quot;. If Shift is latched, the first letter is uppercased —
&quot;En&quot;. If it is pressed, then the code is all uppercase — &quot;EN&quot;. 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 &quot;#?&quot;, if it is pressed, then
&quot;#!&quot;, and if locked, then the same as pressed but underlined.
</p>
<p>
Pressing the language button switches to the next language. Long pressing the language
button opens the keyboard settings.
</p>
<h1>Miscellaneous</h1>
<p>
Regardless of the Enter key tweak, Shift+Enter will always input a new line.
</p>
]]>
</string>
</resources>
+123
View File
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
-->
<device-keymap-list>
<device-keymap name="titan">
<symbols for-code="45"> <!-- Q -->
<alt value=":" />
<alt value=";" />
</symbols>
<symbols for-code="51"> <!-- W -->
<alt value="/" />
<alt value="\\" />
<alt value="|" />
</symbols>
<symbols for-code="33"> <!-- E -->
<alt value="_" />
<alt value="~" />
</symbols>
<symbols for-code="46"> <!-- R -->
<alt value="-" />
<alt value="–" />
<alt value="—" />
</symbols>
<symbols for-code="48"> <!-- T -->
<alt value="(" />
<alt value="&lt;" />
<alt value="[" />
<alt value="{" />
</symbols>
<symbols for-code="53"> <!-- Y -->
<alt value=")" />
<alt value="&gt;" />
<alt value="]" />
<alt value="}" />
</symbols>
<symbols for-code="49"> <!-- U -->
<alt value="1" />
</symbols>
<symbols for-code="37"> <!-- I -->
<alt value="2" />
</symbols>
<symbols for-code="43"> <!-- O -->
<alt value="3" />
</symbols>
<symbols for-code="44"> <!-- P -->
<alt value="0" />
</symbols>
<symbols for-code="29"> <!-- A -->
<alt value="@" />
</symbols>
<symbols for-code="47"> <!-- S -->
<alt value="'" />
</symbols>
<symbols for-code="32"> <!-- D -->
<alt value="&quot;" />
</symbols>
<symbols for-code="34"> <!-- F -->
<alt value="+" />
<alt value="=" />
<alt value="%" />
</symbols>
<symbols for-code="35"> <!-- G -->
<alt value="*" />
<alt value="^" />
<alt value="`" />
</symbols>
<symbols for-code="36"> <!-- H -->
<alt value="#" />
<alt value="$" />
<alt value="€" />
</symbols>
<symbols for-code="38"> <!-- J -->
<alt value="4" />
</symbols>
<symbols for-code="39"> <!-- K -->
<alt value="5" />
</symbols>
<symbols for-code="40"> <!-- L -->
<alt value="6" />
</symbols>
<symbols for-code="54"> <!-- Z -->
<alt value="!" />
</symbols>
<symbols for-code="52"> <!-- X -->
<alt value="?" />
</symbols>
<symbols for-code="31"> <!-- C -->
<alt value="," />
<alt value="&amp;" />
</symbols>
<symbols for-code="50"> <!-- V -->
<alt value="." />
<alt value="…" />
</symbols>
<symbols for-code="30"> <!-- B -->
<alt value="7" />
</symbols>
<symbols for-code="42"> <!-- N -->
<alt value="8" />
</symbols>
<symbols for-code="41"> <!-- M -->
<alt value="9" />
</symbols>
<symbols for-code="62"> <!-- Space -->
<base value=" " />
</symbols>
</device-keymap>
</device-keymap-list>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11 -5
View File
@@ -21,12 +21,18 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/preferences_category_keyboard">
<ListPreference
android:defaultValue="@string/device_default"
android:entries="@array/preferences_device_labels"
android:entryValues="@array/preferences_devices"
android:key="@string/preferences_key_device"
android:title="@string/preferences_label_device" />
<MultiSelectListPreference
android:defaultValue="@array/preferences_keymap_defaults"
android:entries="@array/preferences_keymap_labels"
android:entryValues="@array/preferences_keymaps"
android:key="@string/preferences_key_keymaps"
android:title="@string/preferences_label_keymaps" />
android:defaultValue="@array/preferences_language_defaults"
android:entries="@array/preferences_language_labels"
android:entryValues="@array/preferences_languages"
android:key="@string/preferences_key_languages"
android:title="@string/preferences_label_languages" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences_category_functionality">