Some major changes, keymap restructuring with device and language keymaps
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
"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.
|
||||
</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 "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.
|
||||
</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 — "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.
|
||||
</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>
|
||||
@@ -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="<" />
|
||||
<alt value="[" />
|
||||
<alt value="{" />
|
||||
</symbols>
|
||||
<symbols for-code="53"> <!-- Y -->
|
||||
<alt value=")" />
|
||||
<alt value=">" />
|
||||
<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=""" />
|
||||
</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="&" />
|
||||
</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
@@ -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">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user