mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2025-08-18 19:49:05 +02:00
Compare commits
11 Commits
bigger-sys
...
anticircle
Author | SHA1 | Date | |
---|---|---|---|
|
c5a6b8a3b2 | ||
|
0fadb4b9e6 | ||
|
04caf4309a | ||
|
bea2e6cd1f | ||
|
abf36e5d56 | ||
|
c9b6380ed0 | ||
|
4906f8105f | ||
|
96fc4003f1 | ||
|
a91332a903 | ||
|
53e04d5784 | ||
|
c7d33356bc |
@@ -14,7 +14,7 @@ Python 3 is required to update generated files but not to build the app.
|
||||
For Android Studio users, no more setup is needed.
|
||||
|
||||
For Nix users, the right environment can be obtained with `nix-shell ./shell.nix`.
|
||||
Instructions to install Nix are [here](https://nixos.wiki/wiki/Nix_Installation_Guide).
|
||||
Instructions to install Nix are [here](https://wiki.nixos.org/wiki/Nix_Installation_Guide).
|
||||
|
||||
If you don't use Android Studio or Nix, you have to inform Gradle about the
|
||||
location of your Android SDK by either:
|
||||
|
@@ -103,6 +103,14 @@ tasks.register('buildKeyboardFont') {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('genEmojis') {
|
||||
println "\nGenerating res/raw/emojis.txt"
|
||||
exec {
|
||||
workingDir = projectDir
|
||||
commandLine "python", "gen_emoji.py"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
dependsOn 'genLayoutsList'
|
||||
dependsOn 'checkKeyboardLayouts'
|
||||
|
@@ -55,7 +55,8 @@ Duplicate keys: ટ, ડ
|
||||
Layout doesn't define some important keys, missing: f11_placeholder, f12_placeholder
|
||||
2 warnings
|
||||
# hang_dubeolsik_kr
|
||||
0 warnings
|
||||
Layout doesn't define some important keys, missing: esc, f11_placeholder, f12_placeholder, tab
|
||||
1 warnings
|
||||
# hebr_1_il
|
||||
Layout includes some ASCII punctuation but not all, missing: (, ), <, >, [, ], {, }
|
||||
1 warnings
|
||||
|
113
doc/Custom-layouts.md
Normal file
113
doc/Custom-layouts.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Custom layouts
|
||||
|
||||
Unexpected Keyboard allows custom layouts to be defined, loaded, and used in the app. These layouts are defined in XML.
|
||||
|
||||
## Existing keyboard layouts
|
||||
|
||||
The XML data files for the keyboard layouts that come with the app can be seen [in `/srcs/layouts`](https://github.com/Julow/Unexpected-Keyboard/tree/master/srcs/layouts).
|
||||
|
||||
## Structure
|
||||
|
||||
A complete keyboard file with a single row containing a single Turkish "i" key is provided below:
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="Keyboard Name" script="latin">
|
||||
<modmap>
|
||||
<shift a="i" b="İ" />
|
||||
</modmap>
|
||||
<row>
|
||||
<key key0="i" />
|
||||
</row>
|
||||
</keyboard>
|
||||
|
||||
Shift assignments can be changed on a per-character basis.
|
||||
|
||||
### Crash course to XML
|
||||
|
||||
An XML document is made out of tags.
|
||||
|
||||
Paired tags start with `<` and end with `>`, and must be closed by another tag that starts with `</`. They can have other tags as children. Paired tags used in Unexpected Keyboard include `<row>`...`</row>` and `<keyboard>`...`</keyboard>`.
|
||||
|
||||
Auto-terminating tags start with `<` and end with `/>`, and can't have any children. Auto-terminating tags used in Unexpected Keyboard include `<key />` and `<shift />`.
|
||||
|
||||
An XML tag can have attributes, defined using an equals sign and a pair of ASCII double quotes.
|
||||
|
||||
If you do not like XML, you can also use [this third-party GUI editor](https://unexpected-keyboard-layout-editor.lixquid.com/) to create or edit a keyboard layout.
|
||||
|
||||
### XML declaration
|
||||
|
||||
Every keyboard XML starts with `<?xml version="1.0" encoding="utf-8"?>`.
|
||||
|
||||
### Keyboard metadata
|
||||
|
||||
The `<keyboard>` tag encloses the whole keyboard. The following properties may be used:
|
||||
|
||||
* `name`: The name of the keyboard as it appears in the settings menu. If not present, it will just be called “Custom layout”.
|
||||
* `bottom_row`: Whether or not to show the common bottom row. Accepts `true` or `false`, and defaults to `true`.
|
||||
* `script`: The (main) writing system that it supports. Possible values are `arabic`, `armenian`, `bengali`, `cyrillic`, `devanagari`, `gujarati`, `hangul`, `hebrew`, `latin`, `persian`, `shavian`, and `urdu`. Defaults to `latin`.
|
||||
* `numpad_script`: The script to use for the numpad. This is useful for scripts where a different, non-ASCII set of numerals is used, like Devanagari and Arabic. Defaults to the same as `script`.
|
||||
|
||||
### Modmap
|
||||
|
||||
The `<modmap>` tag encloses custom mappings for the Shift and Fn key’s behavior.
|
||||
|
||||
Each entry contains two mandatory properties: `a` for the base character before the modifier is applied, and `b` for the modified character.
|
||||
|
||||
For example, to make the `I` key behave as in Turkish:
|
||||
|
||||
<modmap>
|
||||
<shift a="i" b="İ" />
|
||||
</modmap>
|
||||
|
||||
There can be as many of these tags inside `<modmap>` as needed.
|
||||
|
||||
Shift and Fn modmaps also affect the clockwise circle and the roundtrip gestures.
|
||||
|
||||
### Row
|
||||
|
||||
The `<row>` tag encloses one row on the keyboard. It requires no properties, and supports the following:
|
||||
|
||||
* `height`: The height of the row. Defaults to 1, and accepts a positive floating point value.
|
||||
|
||||
The total height of the keyboard is defined in Settings as a percentage of the total height of the screen, which can be different between portrait and landscape. The height of a row is relative to the other ones, and are scaled to keep the height of the keyboard constant.
|
||||
|
||||
### Key
|
||||
|
||||
The `<key />` tag defines a key on the keyboard. It requires at least one of the following properties:
|
||||
|
||||
* `key0`: What the key should do when it is tapped.
|
||||
* `nw`, `ne`, `sw`, `se`, `w`, `e`, `n`, `s`: What the key should do when it is swiped. They are based on cardinal directions, and uses the convention that North is up. These are the new set of keywords, and should not be used with the other set of keywords.
|
||||
|
||||
nw | n | ne
|
||||
:-: | :--: | :-:
|
||||
w | key0 | e
|
||||
sw | s | se
|
||||
|
||||
* `key1` through `key8`: The older set of keywords for what the key should do when it is swiped. The directions are ordered as follows:
|
||||
|
||||
key1 | key7 | key2
|
||||
:--: | :--: | :--:
|
||||
key5 | key0 | key6
|
||||
key3 | key8 | key4
|
||||
|
||||
The following properties are optionally supported:
|
||||
|
||||
* `width`: The width of the key relative to the normal width. Defaults to `1` and accepts a positive floating point value.
|
||||
* `shift`: How much empty space to add to the left of this key. Defaults to `0` and accepts a non-negative floating point value.
|
||||
* `indication`: An extra label to show under the main label, intended to be used as a legend for 2A typing (e.g. `<key key0="2" indication="ABC" />`). Caution: if you have `key8` defined, it overlaps!
|
||||
* `slider`: If set to `true`, the keys `w` and `e` are sent repeatedly when the key is being slid on. Intended to be used on the space bar, and in fact used on the default space bar.
|
||||
* `anticircle`: The key value to send when doing an anti-clockwise circle gesture on the key. The clockwise circle and round-trip gestures are not configurable that way.
|
||||
|
||||
## Possible key values
|
||||
|
||||
`key0` and `nw` through `se` (`key1` through `key8`) take arbitrary strings of characters, and if they don't match any of the special values, it is printed verbatim. (This is intended behavior.)
|
||||
|
||||
Special values for the keys are documented in [this page](Possible-key-values).
|
||||
|
||||
### `loc ` prefix
|
||||
|
||||
Keys prefixed with `loc ` do not appear by default, and are only visible when they are enabled through the "Add keys to keyboard" option in the settings menu, or the language installed on the device is detected to require it.
|
||||
|
||||
## Portrait vs. landscape
|
||||
|
||||
Unexpected Keyboard remembers *separately* which layout has last been used in portrait and landscape orientation. That is to say, you may have one custom layout for portrait orientation, but another custom layout for landscape orientation, and Unexpected Keyboard will switch between them without your intervention.
|
145
doc/Possible-key-values.md
Normal file
145
doc/Possible-key-values.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Key values
|
||||
|
||||
This is an exhaustive list of special values accepted for the `key0` through `key8` or `nw` through `se` attributes on a key. Any string that does not exactly match these will be printed verbatim.
|
||||
|
||||
## Escape codes
|
||||
Value | Escape code for
|
||||
:---- | :------
|
||||
`\?` | `?`
|
||||
`\#` | `#`
|
||||
`\@` | `@`
|
||||
`\n` | Literal newline character. This is different from `enter` and `action` in certain apps.
|
||||
`\t` | Literal tab character. This is different from `tab` in certain apps.
|
||||
`\\` | `\`
|
||||
|
||||
XML escape codes also work, including:
|
||||
|
||||
Value | Escape code for
|
||||
:------- | :------
|
||||
`&` | `&`
|
||||
`<` | `<`
|
||||
`>` | `>`
|
||||
`"` | `"`
|
||||
|
||||
## Modifiers
|
||||
System modifiers are sent to the app, which is free to do whatever they want in response.
|
||||
The other modifiers only exist within the keyboard.
|
||||
Value | Meaning
|
||||
:---------- | :------
|
||||
`shift` | System modifier.
|
||||
`ctrl` | System modifier.
|
||||
`alt` | System modifier.
|
||||
`meta` | System modifier. Equivalent to the Windows key.
|
||||
`fn` | Activates Fn mode, which assigns letters and symbols to special characters. e.g. `fn` `!` = `¡`
|
||||
`compose` | Compose key. Enables composing characters using Linux-like shortcuts; e.g. `Compose` `A` `single quote` types `Á` (A with acute accent).
|
||||
`capslock` | Actives and locks Shift
|
||||
|
||||
## Special keys
|
||||
These keys are sent to apps, which are free to ignore them. The keyboard do not perform editing in response to these keys.
|
||||
|
||||
`esc`, `enter`,
|
||||
`up`, `right`,
|
||||
`down`, `left`,
|
||||
`page_up`, `page_down`,
|
||||
`home`, `end`,
|
||||
`backspace`, `delete`,
|
||||
`insert`, `f1`-`f12`,
|
||||
`tab`, `copy`,
|
||||
`paste`, `cut`,
|
||||
`selectAll`, `pasteAsPlainText`,
|
||||
`undo`, `redo`
|
||||
|
||||
## Whitespace
|
||||
Value | Meaning
|
||||
:------ | :------
|
||||
`space` | Space bar.
|
||||
`nbsp` | No-break space. Note: To input the narrow no-break space recommended for French, use `\u202F`.
|
||||
`zwj` | Zero-width joiner.
|
||||
`zwnj` | Zero-width non-joiner.
|
||||
|
||||
## Keyboard editing actions
|
||||
These keys perform editing on the text without sending keys that the app can interpret differently or ignore.
|
||||
Value | Meaning
|
||||
:----------------- | :------
|
||||
`cursor_left` | Moves the cursor position to the left directly, without sending a `left` key event.
|
||||
`cursor_right` | Moves the cursor position to the right directly, without sending a `right` key event.
|
||||
|
||||
## Other modifiers and diacritics
|
||||
Value | Meaning
|
||||
:------------------- | :------
|
||||
`accent_aigu` | Acute accent. `á`
|
||||
`accent_caron` | Háček. `č`
|
||||
`accent_cedille` | Cedilla. `ç`
|
||||
`accent_circonflexe` | Circumflex. `â`
|
||||
`accent_grave` | Grave accent. `à`
|
||||
`accent_macron` | Macron. `ā`
|
||||
`accent_ring` | Ring accent. `å`
|
||||
`accent_tilde` | Tilde. `ã`
|
||||
`accent_trema` | Dieresis/umlaut. `ä`
|
||||
`accent_ogonek` | Ogonek. `ą`
|
||||
`accent_dot_above` | Dot accent. `ż` If applied to the lowercase `i`, removes the dot instead for Turkish. `ı`
|
||||
`accent_double_aigu` | Double acute accent. `ő`
|
||||
`accent_slash` | Slash through. `ø`
|
||||
`accent_arrow_right` | Right arrow above, used to denote a vector. `a⃗`
|
||||
`accent_breve` | Breve. `ă`
|
||||
`accent_bar` | Bar/strikethrough. `ᵢ`
|
||||
`accent_dot_below` | Dot below. `ạ`
|
||||
`accent_horn` | Horn accent. `ơ`
|
||||
`accent_hook_above` | Hook accent. `ả`
|
||||
`superscript` | Superscript. `ᵃ`
|
||||
`subscript` | Subscript. `ₐ`
|
||||
`ordinal` | Turns `a` and `o` into `ª` and `º`.
|
||||
`arrows` | Turns `1`-`4` and `6`-`9` into arrows.
|
||||
`box` | Turns `1`-`9`, `0`, and `.` into single-line, thin box-drawing characters.
|
||||
|
||||
## Bidirectional
|
||||
Value | Meaning
|
||||
:------ | :------
|
||||
`lrm` | Left-to-right mark.
|
||||
`rlm` | Right-to-left mark.
|
||||
`b(`, `b)`, `b[`, `b]`, `b{`, `b}`, `blt`, `bgt` | Sends the bracket characters, but with mirrored key legends for right-to-left languages. (`blt` and `bgt` print `<` and `>` respectively.)
|
||||
|
||||
## Hebrew
|
||||
Keys ending in `_placeholder` are normally hidden unless the Fn key is pressed.
|
||||
|
||||
`qamats`, `patah`,
|
||||
`sheva`, `dagesh`,
|
||||
`hiriq`, `segol`,
|
||||
`tsere`, `holam`,
|
||||
`qubuts`, `hataf_patah`,
|
||||
`hataf_qamats`, `hataf_segol`,
|
||||
`shindot`, `shindot_placeholder`,
|
||||
`sindot`, `sindot_placeholder`,
|
||||
`geresh`, `gershayim`,
|
||||
`maqaf`, `rafe`,
|
||||
`ole`, `ole_placeholder`,
|
||||
`meteg`, `meteg_placeholder`
|
||||
|
||||
## Unexpected Keyboard specific
|
||||
Value | Meaning
|
||||
:--------------------- | :------
|
||||
`config` | Gear icon; opens Unexpected Keyboard settings.
|
||||
`switch_text` | Switch to the text layer (main layer).
|
||||
`switch_numeric` | Switch to the numeric layer.
|
||||
`switch_emoji` | Switch to the emoji layer.
|
||||
`switch_back_emoji` | Switch to the text layer from the emoji layer.
|
||||
`switch_forward` | Change the keyboard layout, as long as Unexpected Keyboard has multiple keyboard layouts enabled in the settings.
|
||||
`switch_backward` | Change the keyboard layout to the previous one in the list.
|
||||
`switch_greekmath` | Switch to the Greek & Math Symbols layer.
|
||||
`change_method` | Open the input method picker dialog.
|
||||
`change_method_prev` | Switch to the previously used input method.
|
||||
`action` | Performs a special context-sensitive operation related to the Enter key. For example, in the Twitter (X) app, `enter` adds a new line, while `action` posts.
|
||||
`voice_typing` | Begin voice typing.
|
||||
`voice_typing_chooser` | Shows a menu where you can choose which voice typing provider to use, then begins voice typing when you make a selection.
|
||||
`shareText` | Emit a share Intent for the selected text. **Oddity:** This is in CamelCase.
|
||||
|
||||
## Unused
|
||||
These keys are known to do nothing.
|
||||
|
||||
`replaceText`, `textAssist`,
|
||||
`autofill`, `removed`
|
||||
|
||||
## Placeholders
|
||||
These keys are normally hidden unless the Fn modifier is activated.
|
||||
|
||||
`f11_placeholder` | `f12_placeholder`
|
38
gen_emoji.py
Normal file
38
gen_emoji.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import urllib.request
|
||||
import os.path
|
||||
|
||||
EMOJIS_PATH = 'res/raw/emojis.txt'
|
||||
EMOJI_TEST_PATH = 'emoji-test.txt'
|
||||
EMOJI_TEST_URL = 'https://unicode.org/Public/emoji/latest/emoji-test.txt'
|
||||
|
||||
def rawEmojiFromCodes(codes):
|
||||
return ''.join([chr(int(c, 16)) for c in codes])
|
||||
|
||||
def getEmojiTestContents():
|
||||
if os.path.exists(EMOJI_TEST_PATH):
|
||||
print(f'Using existing {EMOJI_TEST_PATH}')
|
||||
else:
|
||||
print(f'Downloading {EMOJI_TEST_URL}')
|
||||
urllib.request.urlretrieve(EMOJI_TEST_URL, EMOJI_TEST_PATH)
|
||||
return open(EMOJI_TEST_PATH, mode='r', encoding='UTF-8').read()
|
||||
|
||||
|
||||
emoji_list = []
|
||||
group_indices = []
|
||||
for line in getEmojiTestContents().splitlines():
|
||||
if line.startswith('# group:'):
|
||||
if len(group_indices) == 0 or len(emoji_list) > group_indices[-1]:
|
||||
group_indices.append(len(emoji_list))
|
||||
elif not line.startswith('#') and 'fully-qualified' in line:
|
||||
codes = line.split(';')[0].split()
|
||||
emoji_list.append(rawEmojiFromCodes(codes))
|
||||
|
||||
with open(EMOJIS_PATH, 'w', encoding='UTF-8') as emojis:
|
||||
for e in emoji_list:
|
||||
emojis.write(f'{e}\n')
|
||||
emojis.write('\n')
|
||||
|
||||
emojis.write(' '.join([str(g) for g in group_indices]))
|
||||
emojis.write('\n')
|
||||
|
||||
print(f'Parsed {len(emoji_list)} emojis in {len(group_indices)}')
|
1
res/drawable/cog_outline.xml
Normal file
1
res/drawable/cog_outline.xml
Normal file
@@ -0,0 +1 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#999999" android:pathData="M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z" /></vector>
|
10
res/menu/launcher_menu.xml
Normal file
10
res/menu/launcher_menu.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".LauncherActivity">
|
||||
|
||||
<item
|
||||
android:id="@+id/btnLaunchSettingsActivity"
|
||||
android:title=""
|
||||
android:icon="@drawable/cog_outline"
|
||||
android:showAsAction="always" />
|
||||
</menu>
|
7567
res/raw/emojis.txt
7567
res/raw/emojis.txt
File diff suppressed because it is too large
Load Diff
@@ -7,26 +7,15 @@ import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class Emoji
|
||||
{
|
||||
private final String _name;
|
||||
private final KeyValue _kv;
|
||||
private final String _desc;
|
||||
|
||||
private static HashMap<String, Emoji> emojis_by_name = new HashMap<String, Emoji>();
|
||||
|
||||
protected Emoji(String name, String bytecode, String desc)
|
||||
protected Emoji(String bytecode)
|
||||
{
|
||||
_name = name;
|
||||
_kv = new KeyValue(bytecode, KeyValue.Kind.String, 0, 0);
|
||||
_desc = desc;
|
||||
emojis_by_name.put(name, this);
|
||||
}
|
||||
|
||||
public String name()
|
||||
{
|
||||
return _name;
|
||||
this._kv = new KeyValue(bytecode, KeyValue.Kind.String, 0, 0);
|
||||
}
|
||||
|
||||
public KeyValue kv()
|
||||
@@ -34,54 +23,766 @@ public class Emoji
|
||||
return _kv;
|
||||
}
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return (_desc);
|
||||
}
|
||||
|
||||
public static int num_groups = 0;
|
||||
private final static List<Emoji> _all = new ArrayList<>();
|
||||
private final static List<List<Emoji>> _groups = new ArrayList<>();
|
||||
private final static HashMap<String, Emoji> _stringMap = new HashMap<>();
|
||||
|
||||
private static Emoji[][] emojis_by_group = new Emoji[][]{};
|
||||
|
||||
public static Emoji getEmojiByName(String name)
|
||||
{
|
||||
return emojis_by_name.get(name);
|
||||
}
|
||||
|
||||
public static Emoji[] getEmojisByGroup(int group_id)
|
||||
{
|
||||
return (emojis_by_group[group_id]);
|
||||
}
|
||||
|
||||
/* Read the list of emojis from a raw file. Will initialize only once. */
|
||||
public static void init(Resources res)
|
||||
{
|
||||
if (num_groups > 0)
|
||||
if (!_all.isEmpty())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
ArrayList<Emoji[]> groups = new ArrayList<Emoji[]>();
|
||||
InputStream inputStream = res.openRawResource(R.raw.emojis);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String line;
|
||||
while (true)
|
||||
|
||||
// Read emoji (until empty line)
|
||||
while (!(line = reader.readLine()).isEmpty())
|
||||
{
|
||||
line = reader.readLine();
|
||||
if (line == null)
|
||||
break;
|
||||
int group_len = Integer.parseInt(line);
|
||||
Emoji[] grp = new Emoji[group_len];
|
||||
for (int i = 0; i < group_len; i++)
|
||||
{
|
||||
line = reader.readLine();
|
||||
String[] f = line.split(" ", 3);
|
||||
grp[i] = new Emoji(f[0], f[1], f[2]);
|
||||
}
|
||||
groups.add(grp);
|
||||
Emoji e = new Emoji(line);
|
||||
_all.add(e);
|
||||
_stringMap.put(line, e);
|
||||
}
|
||||
|
||||
// Read group indices
|
||||
if ((line = reader.readLine()) != null)
|
||||
{
|
||||
String[] tokens = line.split(" ");
|
||||
for (int i = 0; i < tokens.length-1; i++)
|
||||
_groups.add(_all.subList(Integer.parseInt(tokens[i]), Integer.parseInt(tokens[i+1])));
|
||||
}
|
||||
num_groups = groups.size();
|
||||
emojis_by_group = groups.toArray(new Emoji[0][]);
|
||||
}
|
||||
catch (IOException e) {}
|
||||
catch (IOException e) { Logs.exn("Emoji.init() failed", e); }
|
||||
}
|
||||
|
||||
public static int getNumGroups()
|
||||
{
|
||||
return _groups.size();
|
||||
}
|
||||
|
||||
public static List<Emoji> getEmojisByGroup(int groupIndex)
|
||||
{
|
||||
return _groups.get(groupIndex);
|
||||
}
|
||||
|
||||
public static Emoji getEmojiByString(String value)
|
||||
{
|
||||
return _stringMap.get(value);
|
||||
}
|
||||
|
||||
public static String mapOldNameToValue(String name) throws IllegalArgumentException
|
||||
{
|
||||
if (name.matches(":(u[a-fA-F0-9]{4,5})+:"))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (String code : name.replace(":", "").substring(1).split("u"))
|
||||
{
|
||||
try
|
||||
{
|
||||
sb.append(Character.toChars(Integer.decode("0X" + code)));
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
throw new IllegalArgumentException("Failed to parse codepoint '" + code + "' in name '" + name + "'", e);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case ":grinning:": return "😀";
|
||||
case ":smiley:": return "😃";
|
||||
case ":smile:": return "😄";
|
||||
case ":grin:": return "😁";
|
||||
case ":satisfied:": return "😆";
|
||||
case ":sweat_smile:": return "😅";
|
||||
case ":joy:": return "😂";
|
||||
case ":wink:": return "😉";
|
||||
case ":blush:": return "😊";
|
||||
case ":innocent:": return "😇";
|
||||
case ":heart_eyes:": return "😍";
|
||||
case ":kissing_heart:": return "😘";
|
||||
case ":kissing:": return "😗";
|
||||
case ":kissing_closed_eyes:": return "😚";
|
||||
case ":kissing_smiling_eyes:": return "😙";
|
||||
case ":yum:": return "😋";
|
||||
case ":stuck_out_tongue:": return "😛";
|
||||
case ":stuck_out_tongue_winking_eye:": return "😜";
|
||||
case ":stuck_out_tongue_closed_eyes:": return "😝";
|
||||
case ":neutral_face:": return "😐";
|
||||
case ":expressionless:": return "😑";
|
||||
case ":no_mouth:": return "😶";
|
||||
case ":smirk:": return "😏";
|
||||
case ":unamused:": return "😒";
|
||||
case ":grimacing:": return "😬";
|
||||
case ":relieved:": return "😌";
|
||||
case ":pensive:": return "😔";
|
||||
case ":sleepy:": return "😪";
|
||||
case ":sleeping:": return "😴";
|
||||
case ":mask:": return "😷";
|
||||
case ":dizzy_face:": return "😵";
|
||||
case ":sunglasses:": return "😎";
|
||||
case ":confused:": return "😕";
|
||||
case ":worried:": return "😟";
|
||||
case ":open_mouth:": return "😮";
|
||||
case ":hushed:": return "😯";
|
||||
case ":astonished:": return "😲";
|
||||
case ":flushed:": return "😳";
|
||||
case ":frowning:": return "😦";
|
||||
case ":anguished:": return "😧";
|
||||
case ":fearful:": return "😨";
|
||||
case ":cold_sweat:": return "😰";
|
||||
case ":disappointed_relieved:": return "😥";
|
||||
case ":cry:": return "😢";
|
||||
case ":sob:": return "😭";
|
||||
case ":scream:": return "😱";
|
||||
case ":confounded:": return "😖";
|
||||
case ":persevere:": return "😣";
|
||||
case ":disappointed:": return "😞";
|
||||
case ":sweat:": return "😓";
|
||||
case ":weary:": return "😩";
|
||||
case ":tired_face:": return "😫";
|
||||
case ":triumph:": return "😤";
|
||||
case ":rage:": return "😡";
|
||||
case ":angry:": return "😠";
|
||||
case ":smiling_imp:": return "😈";
|
||||
case ":imp:": return "👿";
|
||||
case ":skull:": return "💀";
|
||||
case ":shit:": return "💩";
|
||||
case ":japanese_ogre:": return "👹";
|
||||
case ":japanese_goblin:": return "👺";
|
||||
case ":ghost:": return "👻";
|
||||
case ":alien:": return "👽";
|
||||
case ":space_invader:": return "👾";
|
||||
case ":smiley_cat:": return "😺";
|
||||
case ":smile_cat:": return "😸";
|
||||
case ":joy_cat:": return "😹";
|
||||
case ":heart_eyes_cat:": return "😻";
|
||||
case ":smirk_cat:": return "😼";
|
||||
case ":kissing_cat:": return "😽";
|
||||
case ":scream_cat:": return "🙀";
|
||||
case ":crying_cat_face:": return "😿";
|
||||
case ":pouting_cat:": return "😾";
|
||||
case ":see_no_evil:": return "🙈";
|
||||
case ":hear_no_evil:": return "🙉";
|
||||
case ":speak_no_evil:": return "🙊";
|
||||
case ":kiss:": return "💋";
|
||||
case ":love_letter:": return "💌";
|
||||
case ":cupid:": return "💘";
|
||||
case ":gift_heart:": return "💝";
|
||||
case ":sparkling_heart:": return "💖";
|
||||
case ":heartpulse:": return "💗";
|
||||
case ":heartbeat:": return "💓";
|
||||
case ":revolving_hearts:": return "💞";
|
||||
case ":two_hearts:": return "💕";
|
||||
case ":heart_decoration:": return "💟";
|
||||
case ":broken_heart:": return "💔";
|
||||
case ":yellow_heart:": return "💛";
|
||||
case ":green_heart:": return "💚";
|
||||
case ":blue_heart:": return "💙";
|
||||
case ":purple_heart:": return "💜";
|
||||
case ":100:": return "💯";
|
||||
case ":anger:": return "💢";
|
||||
case ":collision:": return "💥";
|
||||
case ":dizzy:": return "💫";
|
||||
case ":sweat_drops:": return "💦";
|
||||
case ":dash:": return "💨";
|
||||
case ":bomb:": return "💣";
|
||||
case ":speech_balloon:": return "💬";
|
||||
case ":thought_balloon:": return "💭";
|
||||
case ":zzz:": return "💤";
|
||||
case ":wave:": return "👋";
|
||||
case ":ok_hand:": return "👌";
|
||||
case ":point_left:": return "👈";
|
||||
case ":point_right:": return "👉";
|
||||
case ":point_up_2:": return "👆";
|
||||
case ":point_down:": return "👇";
|
||||
case ":thumbsup:": return "👍";
|
||||
case ":thumbsdown:": return "👎";
|
||||
case ":punch:": return "👊";
|
||||
case ":clap:": return "👏";
|
||||
case ":raised_hands:": return "🙌";
|
||||
case ":open_hands:": return "👐";
|
||||
case ":pray:": return "🙏";
|
||||
case ":nail_care:": return "💅";
|
||||
case ":muscle:": return "💪";
|
||||
case ":ear:": return "👂";
|
||||
case ":nose:": return "👃";
|
||||
case ":eyes:": return "👀";
|
||||
case ":tongue:": return "👅";
|
||||
case ":lips:": return "👄";
|
||||
case ":baby:": return "👶";
|
||||
case ":boy:": return "👦";
|
||||
case ":girl:": return "👧";
|
||||
case ":person_with_blond_hair:": return "👱";
|
||||
case ":man:": return "👨";
|
||||
case ":woman:": return "👩";
|
||||
case ":older_man:": return "👴";
|
||||
case ":older_woman:": return "👵";
|
||||
case ":person_frowning:": return "🙍";
|
||||
case ":person_with_pouting_face:": return "🙎";
|
||||
case ":no_good:": return "🙅";
|
||||
case ":ok_woman:": return "🙆";
|
||||
case ":information_desk_person:": return "💁";
|
||||
case ":raising_hand:": return "🙋";
|
||||
case ":bow:": return "🙇";
|
||||
case ":cop:": return "👮";
|
||||
case ":guardsman:": return "💂";
|
||||
case ":construction_worker:": return "👷";
|
||||
case ":princess:": return "👸";
|
||||
case ":man_with_turban:": return "👳";
|
||||
case ":man_with_gua_pi_mao:": return "👲";
|
||||
case ":bride_with_veil:": return "👰";
|
||||
case ":angel:": return "👼";
|
||||
case ":santa:": return "🎅";
|
||||
case ":massage:": return "💆";
|
||||
case ":haircut:": return "💇";
|
||||
case ":walking:": return "🚶";
|
||||
case ":running:": return "🏃";
|
||||
case ":dancer:": return "💃";
|
||||
case ":dancers:": return "👯";
|
||||
case ":horse_racing:": return "🏇";
|
||||
case ":snowboarder:": return "🏂";
|
||||
case ":surfer:": return "🏄";
|
||||
case ":rowboat:": return "🚣";
|
||||
case ":swimmer:": return "🏊";
|
||||
case ":bicyclist:": return "🚴";
|
||||
case ":mountain_bicyclist:": return "🚵";
|
||||
case ":bath:": return "🛀";
|
||||
case ":two_women_holding_hands:": return "👭";
|
||||
case ":couple:": return "👫";
|
||||
case ":two_men_holding_hands:": return "👬";
|
||||
case ":couplekiss:": return "💏";
|
||||
case ":couple_with_heart:": return "💑";
|
||||
case ":family:": return "👪";
|
||||
case ":bust_in_silhouette:": return "👤";
|
||||
case ":busts_in_silhouette:": return "👥";
|
||||
case ":footprints:": return "👣";
|
||||
case ":monkey_face:": return "🐵";
|
||||
case ":monkey:": return "🐒";
|
||||
case ":dog:": return "🐶";
|
||||
case ":dog2:": return "🐕";
|
||||
case ":poodle:": return "🐩";
|
||||
case ":wolf:": return "🐺";
|
||||
case ":cat:": return "🐱";
|
||||
case ":cat2:": return "🐈";
|
||||
case ":tiger:": return "🐯";
|
||||
case ":tiger2:": return "🐅";
|
||||
case ":leopard:": return "🐆";
|
||||
case ":horse:": return "🐴";
|
||||
case ":racehorse:": return "🐎";
|
||||
case ":cow:": return "🐮";
|
||||
case ":ox:": return "🐂";
|
||||
case ":water_buffalo:": return "🐃";
|
||||
case ":cow2:": return "🐄";
|
||||
case ":pig:": return "🐷";
|
||||
case ":pig2:": return "🐖";
|
||||
case ":boar:": return "🐗";
|
||||
case ":pig_nose:": return "🐽";
|
||||
case ":ram:": return "🐏";
|
||||
case ":sheep:": return "🐑";
|
||||
case ":goat:": return "🐐";
|
||||
case ":dromedary_camel:": return "🐪";
|
||||
case ":camel:": return "🐫";
|
||||
case ":elephant:": return "🐘";
|
||||
case ":mouse:": return "🐭";
|
||||
case ":mouse2:": return "🐁";
|
||||
case ":rat:": return "🐀";
|
||||
case ":hamster:": return "🐹";
|
||||
case ":rabbit:": return "🐰";
|
||||
case ":rabbit2:": return "🐇";
|
||||
case ":bear:": return "🐻";
|
||||
case ":koala:": return "🐨";
|
||||
case ":panda_face:": return "🐼";
|
||||
case ":paw_prints:": return "🐾";
|
||||
case ":chicken:": return "🐔";
|
||||
case ":rooster:": return "🐓";
|
||||
case ":hatching_chick:": return "🐣";
|
||||
case ":baby_chick:": return "🐤";
|
||||
case ":hatched_chick:": return "🐥";
|
||||
case ":bird:": return "🐦";
|
||||
case ":penguin:": return "🐧";
|
||||
case ":frog:": return "🐸";
|
||||
case ":crocodile:": return "🐊";
|
||||
case ":turtle:": return "🐢";
|
||||
case ":snake:": return "🐍";
|
||||
case ":dragon_face:": return "🐲";
|
||||
case ":dragon:": return "🐉";
|
||||
case ":whale:": return "🐳";
|
||||
case ":whale2:": return "🐋";
|
||||
case ":flipper:": return "🐬";
|
||||
case ":fish:": return "🐟";
|
||||
case ":tropical_fish:": return "🐠";
|
||||
case ":blowfish:": return "🐡";
|
||||
case ":octopus:": return "🐙";
|
||||
case ":shell:": return "🐚";
|
||||
case ":snail:": return "🐌";
|
||||
case ":bug:": return "🐛";
|
||||
case ":ant:": return "🐜";
|
||||
case ":honeybee:": return "🐝";
|
||||
case ":beetle:": return "🐞";
|
||||
case ":bouquet:": return "💐";
|
||||
case ":cherry_blossom:": return "🌸";
|
||||
case ":white_flower:": return "💮";
|
||||
case ":rose:": return "🌹";
|
||||
case ":hibiscus:": return "🌺";
|
||||
case ":sunflower:": return "🌻";
|
||||
case ":blossom:": return "🌼";
|
||||
case ":tulip:": return "🌷";
|
||||
case ":seedling:": return "🌱";
|
||||
case ":evergreen_tree:": return "🌲";
|
||||
case ":deciduous_tree:": return "🌳";
|
||||
case ":palm_tree:": return "🌴";
|
||||
case ":cactus:": return "🌵";
|
||||
case ":ear_of_rice:": return "🌾";
|
||||
case ":herb:": return "🌿";
|
||||
case ":four_leaf_clover:": return "🍀";
|
||||
case ":maple_leaf:": return "🍁";
|
||||
case ":fallen_leaf:": return "🍂";
|
||||
case ":leaves:": return "🍃";
|
||||
case ":grapes:": return "🍇";
|
||||
case ":melon:": return "🍈";
|
||||
case ":watermelon:": return "🍉";
|
||||
case ":tangerine:": return "🍊";
|
||||
case ":lemon:": return "🍋";
|
||||
case ":banana:": return "🍌";
|
||||
case ":pineapple:": return "🍍";
|
||||
case ":apple:": return "🍎";
|
||||
case ":green_apple:": return "🍏";
|
||||
case ":pear:": return "🍐";
|
||||
case ":peach:": return "🍑";
|
||||
case ":cherries:": return "🍒";
|
||||
case ":strawberry:": return "🍓";
|
||||
case ":tomato:": return "🍅";
|
||||
case ":eggplant:": return "🍆";
|
||||
case ":corn:": return "🌽";
|
||||
case ":mushroom:": return "🍄";
|
||||
case ":chestnut:": return "🌰";
|
||||
case ":bread:": return "🍞";
|
||||
case ":meat_on_bone:": return "🍖";
|
||||
case ":poultry_leg:": return "🍗";
|
||||
case ":hamburger:": return "🍔";
|
||||
case ":fries:": return "🍟";
|
||||
case ":pizza:": return "🍕";
|
||||
case ":egg:": return "🍳";
|
||||
case ":stew:": return "🍲";
|
||||
case ":bento:": return "🍱";
|
||||
case ":rice_cracker:": return "🍘";
|
||||
case ":rice_ball:": return "🍙";
|
||||
case ":rice:": return "🍚";
|
||||
case ":curry:": return "🍛";
|
||||
case ":ramen:": return "🍜";
|
||||
case ":spaghetti:": return "🍝";
|
||||
case ":sweet_potato:": return "🍠";
|
||||
case ":oden:": return "🍢";
|
||||
case ":sushi:": return "🍣";
|
||||
case ":fried_shrimp:": return "🍤";
|
||||
case ":fish_cake:": return "🍥";
|
||||
case ":dango:": return "🍡";
|
||||
case ":icecream:": return "🍦";
|
||||
case ":shaved_ice:": return "🍧";
|
||||
case ":ice_cream:": return "🍨";
|
||||
case ":doughnut:": return "🍩";
|
||||
case ":cookie:": return "🍪";
|
||||
case ":birthday:": return "🎂";
|
||||
case ":cake:": return "🍰";
|
||||
case ":chocolate_bar:": return "🍫";
|
||||
case ":candy:": return "🍬";
|
||||
case ":lollipop:": return "🍭";
|
||||
case ":custard:": return "🍮";
|
||||
case ":honey_pot:": return "🍯";
|
||||
case ":baby_bottle:": return "🍼";
|
||||
case ":tea:": return "🍵";
|
||||
case ":sake:": return "🍶";
|
||||
case ":wine_glass:": return "🍷";
|
||||
case ":cocktail:": return "🍸";
|
||||
case ":tropical_drink:": return "🍹";
|
||||
case ":beer:": return "🍺";
|
||||
case ":beers:": return "🍻";
|
||||
case ":fork_and_knife:": return "🍴";
|
||||
case ":hocho:": return "🔪";
|
||||
case ":earth_africa:": return "🌍";
|
||||
case ":earth_americas:": return "🌎";
|
||||
case ":earth_asia:": return "🌏";
|
||||
case ":globe_with_meridians:": return "🌐";
|
||||
case ":japan:": return "🗾";
|
||||
case ":volcano:": return "🌋";
|
||||
case ":mount_fuji:": return "🗻";
|
||||
case ":house:": return "🏠";
|
||||
case ":house_with_garden:": return "🏡";
|
||||
case ":office:": return "🏢";
|
||||
case ":post_office:": return "🏣";
|
||||
case ":european_post_office:": return "🏤";
|
||||
case ":hospital:": return "🏥";
|
||||
case ":bank:": return "🏦";
|
||||
case ":hotel:": return "🏨";
|
||||
case ":love_hotel:": return "🏩";
|
||||
case ":convenience_store:": return "🏪";
|
||||
case ":school:": return "🏫";
|
||||
case ":department_store:": return "🏬";
|
||||
case ":factory:": return "🏭";
|
||||
case ":japanese_castle:": return "🏯";
|
||||
case ":european_castle:": return "🏰";
|
||||
case ":wedding:": return "💒";
|
||||
case ":tokyo_tower:": return "🗼";
|
||||
case ":statue_of_liberty:": return "🗽";
|
||||
case ":foggy:": return "🌁";
|
||||
case ":stars:": return "🌃";
|
||||
case ":sunrise_over_mountains:": return "🌄";
|
||||
case ":sunrise:": return "🌅";
|
||||
case ":city_sunset:": return "🌆";
|
||||
case ":city_sunrise:": return "🌇";
|
||||
case ":bridge_at_night:": return "🌉";
|
||||
case ":carousel_horse:": return "🎠";
|
||||
case ":ferris_wheel:": return "🎡";
|
||||
case ":roller_coaster:": return "🎢";
|
||||
case ":barber:": return "💈";
|
||||
case ":circus_tent:": return "🎪";
|
||||
case ":steam_locomotive:": return "🚂";
|
||||
case ":train:": return "🚃";
|
||||
case ":bullettrain_side:": return "🚄";
|
||||
case ":bullettrain_front:": return "🚅";
|
||||
case ":train2:": return "🚆";
|
||||
case ":metro:": return "🚇";
|
||||
case ":light_rail:": return "🚈";
|
||||
case ":station:": return "🚉";
|
||||
case ":tram:": return "🚊";
|
||||
case ":monorail:": return "🚝";
|
||||
case ":mountain_railway:": return "🚞";
|
||||
case ":bus:": return "🚌";
|
||||
case ":oncoming_bus:": return "🚍";
|
||||
case ":trolleybus:": return "🚎";
|
||||
case ":minibus:": return "🚐";
|
||||
case ":ambulance:": return "🚑";
|
||||
case ":fire_engine:": return "🚒";
|
||||
case ":police_car:": return "🚓";
|
||||
case ":oncoming_police_car:": return "🚔";
|
||||
case ":taxi:": return "🚕";
|
||||
case ":oncoming_taxi:": return "🚖";
|
||||
case ":red_car:": return "🚗";
|
||||
case ":oncoming_automobile:": return "🚘";
|
||||
case ":blue_car:": return "🚙";
|
||||
case ":truck:": return "🚚";
|
||||
case ":articulated_lorry:": return "🚛";
|
||||
case ":tractor:": return "🚜";
|
||||
case ":bike:": return "🚲";
|
||||
case ":busstop:": return "🚏";
|
||||
case ":rotating_light:": return "🚨";
|
||||
case ":traffic_light:": return "🚥";
|
||||
case ":vertical_traffic_light:": return "🚦";
|
||||
case ":construction:": return "🚧";
|
||||
case ":speedboat:": return "🚤";
|
||||
case ":ship:": return "🚢";
|
||||
case ":seat:": return "💺";
|
||||
case ":helicopter:": return "🚁";
|
||||
case ":suspension_railway:": return "🚟";
|
||||
case ":mountain_cableway:": return "🚠";
|
||||
case ":aerial_tramway:": return "🚡";
|
||||
case ":rocket:": return "🚀";
|
||||
case ":clock12:": return "🕛";
|
||||
case ":clock1230:": return "🕧";
|
||||
case ":clock1:": return "🕐";
|
||||
case ":clock130:": return "🕜";
|
||||
case ":clock2:": return "🕑";
|
||||
case ":clock230:": return "🕝";
|
||||
case ":clock3:": return "🕒";
|
||||
case ":clock330:": return "🕞";
|
||||
case ":clock4:": return "🕓";
|
||||
case ":clock430:": return "🕟";
|
||||
case ":clock5:": return "🕔";
|
||||
case ":clock530:": return "🕠";
|
||||
case ":clock6:": return "🕕";
|
||||
case ":clock630:": return "🕡";
|
||||
case ":clock7:": return "🕖";
|
||||
case ":clock730:": return "🕢";
|
||||
case ":clock8:": return "🕗";
|
||||
case ":clock830:": return "🕣";
|
||||
case ":clock9:": return "🕘";
|
||||
case ":clock930:": return "🕤";
|
||||
case ":clock10:": return "🕙";
|
||||
case ":clock1030:": return "🕥";
|
||||
case ":clock11:": return "🕚";
|
||||
case ":clock1130:": return "🕦";
|
||||
case ":new_moon:": return "🌑";
|
||||
case ":waxing_crescent_moon:": return "🌒";
|
||||
case ":first_quarter_moon:": return "🌓";
|
||||
case ":waxing_gibbous_moon:": return "🌔";
|
||||
case ":full_moon:": return "🌕";
|
||||
case ":waning_gibbous_moon:": return "🌖";
|
||||
case ":last_quarter_moon:": return "🌗";
|
||||
case ":waning_crescent_moon:": return "🌘";
|
||||
case ":crescent_moon:": return "🌙";
|
||||
case ":new_moon_with_face:": return "🌚";
|
||||
case ":first_quarter_moon_with_face:": return "🌛";
|
||||
case ":last_quarter_moon_with_face:": return "🌜";
|
||||
case ":full_moon_with_face:": return "🌝";
|
||||
case ":sun_with_face:": return "🌞";
|
||||
case ":star2:": return "🌟";
|
||||
case ":milky_way:": return "🌌";
|
||||
case ":cyclone:": return "🌀";
|
||||
case ":rainbow:": return "🌈";
|
||||
case ":closed_umbrella:": return "🌂";
|
||||
case ":fire:": return "🔥";
|
||||
case ":droplet:": return "💧";
|
||||
case ":ocean:": return "🌊";
|
||||
case ":jack_o_lantern:": return "🎃";
|
||||
case ":christmas_tree:": return "🎄";
|
||||
case ":fireworks:": return "🎆";
|
||||
case ":sparkler:": return "🎇";
|
||||
case ":balloon:": return "🎈";
|
||||
case ":tada:": return "🎉";
|
||||
case ":confetti_ball:": return "🎊";
|
||||
case ":tanabata_tree:": return "🎋";
|
||||
case ":bamboo:": return "🎍";
|
||||
case ":dolls:": return "🎎";
|
||||
case ":flags:": return "🎏";
|
||||
case ":wind_chime:": return "🎐";
|
||||
case ":rice_scene:": return "🎑";
|
||||
case ":ribbon:": return "🎀";
|
||||
case ":gift:": return "🎁";
|
||||
case ":ticket:": return "🎫";
|
||||
case ":trophy:": return "🏆";
|
||||
case ":basketball:": return "🏀";
|
||||
case ":football:": return "🏈";
|
||||
case ":rugby_football:": return "🏉";
|
||||
case ":tennis:": return "🎾";
|
||||
case ":bowling:": return "🎳";
|
||||
case ":fishing_pole_and_fish:": return "🎣";
|
||||
case ":running_shirt_with_sash:": return "🎽";
|
||||
case ":ski:": return "🎿";
|
||||
case ":dart:": return "🎯";
|
||||
case ":8ball:": return "🎱";
|
||||
case ":crystal_ball:": return "🔮";
|
||||
case ":video_game:": return "🎮";
|
||||
case ":slot_machine:": return "🎰";
|
||||
case ":game_die:": return "🎲";
|
||||
case ":black_joker:": return "🃏";
|
||||
case ":mahjong:": return "🀄";
|
||||
case ":flower_playing_cards:": return "🎴";
|
||||
case ":performing_arts:": return "🎭";
|
||||
case ":art:": return "🎨";
|
||||
case ":eyeglasses:": return "👓";
|
||||
case ":necktie:": return "👔";
|
||||
case ":tshirt:": return "👕";
|
||||
case ":jeans:": return "👖";
|
||||
case ":dress:": return "👗";
|
||||
case ":kimono:": return "👘";
|
||||
case ":bikini:": return "👙";
|
||||
case ":womans_clothes:": return "👚";
|
||||
case ":purse:": return "👛";
|
||||
case ":handbag:": return "👜";
|
||||
case ":pouch:": return "👝";
|
||||
case ":school_satchel:": return "🎒";
|
||||
case ":shoe:": return "👞";
|
||||
case ":athletic_shoe:": return "👟";
|
||||
case ":high_heel:": return "👠";
|
||||
case ":sandal:": return "👡";
|
||||
case ":boot:": return "👢";
|
||||
case ":crown:": return "👑";
|
||||
case ":womans_hat:": return "👒";
|
||||
case ":tophat:": return "🎩";
|
||||
case ":mortar_board:": return "🎓";
|
||||
case ":lipstick:": return "💄";
|
||||
case ":ring:": return "💍";
|
||||
case ":gem:": return "💎";
|
||||
case ":mute:": return "🔇";
|
||||
case ":sound:": return "🔉";
|
||||
case ":speaker:": return "🔊";
|
||||
case ":loudspeaker:": return "📢";
|
||||
case ":mega:": return "📣";
|
||||
case ":postal_horn:": return "📯";
|
||||
case ":bell:": return "🔔";
|
||||
case ":no_bell:": return "🔕";
|
||||
case ":musical_score:": return "🎼";
|
||||
case ":musical_note:": return "🎵";
|
||||
case ":notes:": return "🎶";
|
||||
case ":microphone:": return "🎤";
|
||||
case ":headphones:": return "🎧";
|
||||
case ":radio:": return "📻";
|
||||
case ":saxophone:": return "🎷";
|
||||
case ":guitar:": return "🎸";
|
||||
case ":musical_keyboard:": return "🎹";
|
||||
case ":trumpet:": return "🎺";
|
||||
case ":violin:": return "🎻";
|
||||
case ":iphone:": return "📱";
|
||||
case ":calling:": return "📲";
|
||||
case ":telephone_receiver:": return "📞";
|
||||
case ":pager:": return "📟";
|
||||
case ":fax:": return "📠";
|
||||
case ":battery:": return "🔋";
|
||||
case ":electric_plug:": return "🔌";
|
||||
case ":computer:": return "💻";
|
||||
case ":minidisc:": return "💽";
|
||||
case ":floppy_disk:": return "💾";
|
||||
case ":cd:": return "💿";
|
||||
case ":dvd:": return "📀";
|
||||
case ":movie_camera:": return "🎥";
|
||||
case ":clapper:": return "🎬";
|
||||
case ":tv:": return "📺";
|
||||
case ":camera:": return "📷";
|
||||
case ":video_camera:": return "📹";
|
||||
case ":vhs:": return "📼";
|
||||
case ":mag:": return "🔍";
|
||||
case ":mag_right:": return "🔎";
|
||||
case ":bulb:": return "💡";
|
||||
case ":flashlight:": return "🔦";
|
||||
case ":lantern:": return "🏮";
|
||||
case ":notebook_with_decorative_cover:": return "📔";
|
||||
case ":closed_book:": return "📕";
|
||||
case ":open_book:": return "📖";
|
||||
case ":green_book:": return "📗";
|
||||
case ":blue_book:": return "📘";
|
||||
case ":orange_book:": return "📙";
|
||||
case ":books:": return "📚";
|
||||
case ":notebook:": return "📓";
|
||||
case ":ledger:": return "📒";
|
||||
case ":page_with_curl:": return "📃";
|
||||
case ":scroll:": return "📜";
|
||||
case ":page_facing_up:": return "📄";
|
||||
case ":newspaper:": return "📰";
|
||||
case ":bookmark_tabs:": return "📑";
|
||||
case ":bookmark:": return "🔖";
|
||||
case ":moneybag:": return "💰";
|
||||
case ":yen:": return "💴";
|
||||
case ":dollar:": return "💵";
|
||||
case ":euro:": return "💶";
|
||||
case ":pound:": return "💷";
|
||||
case ":money_with_wings:": return "💸";
|
||||
case ":credit_card:": return "💳";
|
||||
case ":chart:": return "💹";
|
||||
case ":e-mail:": return "📧";
|
||||
case ":incoming_envelope:": return "📨";
|
||||
case ":envelope_with_arrow:": return "📩";
|
||||
case ":outbox_tray:": return "📤";
|
||||
case ":inbox_tray:": return "📥";
|
||||
case ":package:": return "📦";
|
||||
case ":mailbox:": return "📫";
|
||||
case ":mailbox_closed:": return "📪";
|
||||
case ":mailbox_with_mail:": return "📬";
|
||||
case ":mailbox_with_no_mail:": return "📭";
|
||||
case ":postbox:": return "📮";
|
||||
case ":pencil:": return "📝";
|
||||
case ":briefcase:": return "💼";
|
||||
case ":file_folder:": return "📁";
|
||||
case ":open_file_folder:": return "📂";
|
||||
case ":date:": return "📅";
|
||||
case ":calendar:": return "📆";
|
||||
case ":card_index:": return "📇";
|
||||
case ":chart_with_upwards_trend:": return "📈";
|
||||
case ":chart_with_downwards_trend:": return "📉";
|
||||
case ":bar_chart:": return "📊";
|
||||
case ":clipboard:": return "📋";
|
||||
case ":pushpin:": return "📌";
|
||||
case ":round_pushpin:": return "📍";
|
||||
case ":paperclip:": return "📎";
|
||||
case ":straight_ruler:": return "📏";
|
||||
case ":triangular_ruler:": return "📐";
|
||||
case ":lock:": return "🔒";
|
||||
case ":lock_with_ink_pen:": return "🔏";
|
||||
case ":closed_lock_with_key:": return "🔐";
|
||||
case ":key:": return "🔑";
|
||||
case ":hammer:": return "🔨";
|
||||
case ":gun:": return "🔫";
|
||||
case ":wrench:": return "🔧";
|
||||
case ":nut_and_bolt:": return "🔩";
|
||||
case ":link:": return "🔗";
|
||||
case ":microscope:": return "🔬";
|
||||
case ":telescope:": return "🔭";
|
||||
case ":satellite:": return "📡";
|
||||
case ":syringe:": return "💉";
|
||||
case ":pill:": return "💊";
|
||||
case ":door:": return "🚪";
|
||||
case ":toilet:": return "🚽";
|
||||
case ":shower:": return "🚿";
|
||||
case ":bathtub:": return "🛁";
|
||||
case ":smoking:": return "🚬";
|
||||
case ":moyai:": return "🗿";
|
||||
case ":atm:": return "🏧";
|
||||
case ":put_litter_in_its_place:": return "🚮";
|
||||
case ":potable_water:": return "🚰";
|
||||
case ":mens:": return "🚹";
|
||||
case ":womens:": return "🚺";
|
||||
case ":restroom:": return "🚻";
|
||||
case ":baby_symbol:": return "🚼";
|
||||
case ":wc:": return "🚾";
|
||||
case ":passport_control:": return "🛂";
|
||||
case ":customs:": return "🛃";
|
||||
case ":baggage_claim:": return "🛄";
|
||||
case ":left_luggage:": return "🛅";
|
||||
case ":children_crossing:": return "🚸";
|
||||
case ":no_entry_sign:": return "🚫";
|
||||
case ":no_bicycles:": return "🚳";
|
||||
case ":no_smoking:": return "🚭";
|
||||
case ":do_not_litter:": return "🚯";
|
||||
case ":non-potable_water:": return "🚱";
|
||||
case ":no_pedestrians:": return "🚷";
|
||||
case ":no_mobile_phones:": return "📵";
|
||||
case ":underage:": return "🔞";
|
||||
case ":arrows_clockwise:": return "🔃";
|
||||
case ":arrows_counterclockwise:": return "🔄";
|
||||
case ":back:": return "🔙";
|
||||
case ":end:": return "🔚";
|
||||
case ":on:": return "🔛";
|
||||
case ":soon:": return "🔜";
|
||||
case ":top:": return "🔝";
|
||||
case ":six_pointed_star:": return "🔯";
|
||||
case ":twisted_rightwards_arrows:": return "🔀";
|
||||
case ":repeat:": return "🔁";
|
||||
case ":repeat_one:": return "🔂";
|
||||
case ":arrow_up_small:": return "🔼";
|
||||
case ":arrow_down_small:": return "🔽";
|
||||
case ":cinema:": return "🎦";
|
||||
case ":low_brightness:": return "🔅";
|
||||
case ":high_brightness:": return "🔆";
|
||||
case ":signal_strength:": return "📶";
|
||||
case ":vibration_mode:": return "📳";
|
||||
case ":mobile_phone_off:": return "📴";
|
||||
case ":currency_exchange:": return "💱";
|
||||
case ":heavy_dollar_sign:": return "💲";
|
||||
case ":trident:": return "🔱";
|
||||
case ":name_badge:": return "📛";
|
||||
case ":beginner:": return "🔰";
|
||||
case ":keycap_ten:": return "🔟";
|
||||
case ":capital_abcd:": return "🔠";
|
||||
case ":abcd:": return "🔡";
|
||||
case ":1234:": return "🔢";
|
||||
case ":symbols:": return "🔣";
|
||||
case ":abc:": return "🔤";
|
||||
case ":ab:": return "🆎";
|
||||
case ":cl:": return "🆑";
|
||||
case ":cool:": return "🆒";
|
||||
case ":free:": return "🆓";
|
||||
case ":id:": return "🆔";
|
||||
case ":new:": return "🆕";
|
||||
case ":ng:": return "🆖";
|
||||
case ":ok:": return "🆗";
|
||||
case ":sos:": return "🆘";
|
||||
case ":up:": return "🆙";
|
||||
case ":vs:": return "🆚";
|
||||
case ":koko:": return "🈁";
|
||||
case ":ideograph_advantage:": return "🉐";
|
||||
case ":accept:": return "🉑";
|
||||
case ":red_circle:": return "🔴";
|
||||
case ":large_blue_circle:": return "🔵";
|
||||
case ":large_orange_diamond:": return "🔶";
|
||||
case ":large_blue_diamond:": return "🔷";
|
||||
case ":small_orange_diamond:": return "🔸";
|
||||
case ":small_blue_diamond:": return "🔹";
|
||||
case ":small_red_triangle:": return "🔺";
|
||||
case ":small_red_triangle_down:": return "🔻";
|
||||
case ":diamond_shape_with_a_dot_inside:": return "💠";
|
||||
case ":radio_button:": return "🔘";
|
||||
case ":white_square_button:": return "🔳";
|
||||
case ":black_square_button:": return "🔲";
|
||||
case ":checkered_flag:": return "🏁";
|
||||
case ":triangular_flag_on_post:": return "🚩";
|
||||
case ":crossed_flags:": return "🎌";
|
||||
}
|
||||
throw new IllegalArgumentException("'" + name + "' is not a valid name");
|
||||
}
|
||||
}
|
||||
|
@@ -10,10 +10,12 @@ import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.GridView;
|
||||
import android.widget.TextView;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class EmojiGridView extends GridView
|
||||
@@ -23,7 +25,7 @@ public class EmojiGridView extends GridView
|
||||
|
||||
private static final String LAST_USE_PREF = "emoji_last_use";
|
||||
|
||||
private Emoji[] _emojiArray;
|
||||
private List<Emoji> _emojiArray;
|
||||
private HashMap<Emoji, Integer> _lastUsed;
|
||||
|
||||
/*
|
||||
@@ -34,6 +36,7 @@ public class EmojiGridView extends GridView
|
||||
{
|
||||
super(context, attrs);
|
||||
Emoji.init(context.getResources());
|
||||
migrateOldPrefs(); // TODO: Remove at some point in future
|
||||
setOnItemClickListener(this);
|
||||
loadLastUsed();
|
||||
setEmojiGroup((_lastUsed.size() == 0) ? 0 : GROUP_LAST_USE);
|
||||
@@ -48,26 +51,23 @@ public class EmojiGridView extends GridView
|
||||
public void onItemClick(AdapterView<?> parent, View v, int pos, long id)
|
||||
{
|
||||
Config config = Config.globalConfig();
|
||||
Integer used = _lastUsed.get(_emojiArray[pos]);
|
||||
_lastUsed.put(_emojiArray[pos], (used == null) ? 1 : used.intValue() + 1);
|
||||
config.handler.key_up(_emojiArray[pos].kv(), Pointers.Modifiers.EMPTY);
|
||||
Integer used = _lastUsed.get(_emojiArray.get(pos));
|
||||
_lastUsed.put(_emojiArray.get(pos), (used == null) ? 1 : used.intValue() + 1);
|
||||
config.handler.key_up(_emojiArray.get(pos).kv(), Pointers.Modifiers.EMPTY);
|
||||
saveLastUsed(); // TODO: opti
|
||||
}
|
||||
|
||||
private Emoji[] getLastEmojis()
|
||||
private List<Emoji> getLastEmojis()
|
||||
{
|
||||
final HashMap<Emoji, Integer> map = _lastUsed;
|
||||
Emoji[] array = new Emoji[map.size()];
|
||||
|
||||
map.keySet().toArray(array);
|
||||
Arrays.sort(array, 0, array.length, new Comparator<Emoji>()
|
||||
List<Emoji> list = new ArrayList<>(_lastUsed.keySet());
|
||||
Collections.sort(list, new Comparator<Emoji>()
|
||||
{
|
||||
public int compare(Emoji a, Emoji b)
|
||||
{
|
||||
return (map.get(b).intValue() - map.get(a).intValue());
|
||||
return _lastUsed.get(b) - _lastUsed.get(a);
|
||||
}
|
||||
});
|
||||
return (array);
|
||||
return list;
|
||||
}
|
||||
|
||||
private void saveLastUsed()
|
||||
@@ -77,7 +77,7 @@ public class EmojiGridView extends GridView
|
||||
catch (Exception _e) { return; }
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
for (Emoji emoji : _lastUsed.keySet())
|
||||
set.add(String.valueOf(_lastUsed.get(emoji)) + "-" + emoji.name());
|
||||
set.add(String.valueOf(_lastUsed.get(emoji)) + "-" + emoji.kv().getString());
|
||||
edit.putStringSet(LAST_USE_PREF, set);
|
||||
edit.apply();
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public class EmojiGridView extends GridView
|
||||
Emoji emoji;
|
||||
if (data.length != 2)
|
||||
continue ;
|
||||
emoji = Emoji.getEmojiByName(data[1]);
|
||||
emoji = Emoji.getEmojiByString(data[1]);
|
||||
if (emoji == null)
|
||||
continue ;
|
||||
_lastUsed.put(emoji, Integer.valueOf(data[0]));
|
||||
@@ -110,6 +110,37 @@ public class EmojiGridView extends GridView
|
||||
return getContext().getSharedPreferences("emoji_last_use", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private void migrateOldPrefs()
|
||||
{
|
||||
final String MIGRATION_CHECK_KEY = "MIGRATION_COMPLETE";
|
||||
|
||||
SharedPreferences prefs;
|
||||
try { prefs = emojiSharedPreferences(); }
|
||||
catch (Exception e) { return; }
|
||||
|
||||
Set<String> lastUsed = prefs.getStringSet(LAST_USE_PREF, null);
|
||||
if (lastUsed != null && !prefs.getBoolean(MIGRATION_CHECK_KEY, false))
|
||||
{
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.clear();
|
||||
|
||||
Set<String> lastUsedNew = new HashSet<>();
|
||||
for (String entry : lastUsed)
|
||||
{
|
||||
String[] data = entry.split("-", 2);
|
||||
try
|
||||
{
|
||||
lastUsedNew.add(Integer.parseInt(data[0]) + "-" + Emoji.mapOldNameToValue(data[1]));
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {}
|
||||
}
|
||||
edit.putStringSet(LAST_USE_PREF, lastUsedNew);
|
||||
|
||||
edit.putBoolean(MIGRATION_CHECK_KEY, true);
|
||||
edit.apply();
|
||||
}
|
||||
}
|
||||
|
||||
static class EmojiView extends TextView
|
||||
{
|
||||
public EmojiView(Context context)
|
||||
@@ -127,9 +158,9 @@ public class EmojiGridView extends GridView
|
||||
{
|
||||
Context _button_context;
|
||||
|
||||
Emoji[] _emojiArray;
|
||||
List<Emoji> _emojiArray;
|
||||
|
||||
public EmojiViewAdpater(Context context, Emoji[] emojiArray)
|
||||
public EmojiViewAdpater(Context context, List<Emoji> emojiArray)
|
||||
{
|
||||
_button_context = new ContextThemeWrapper(context, R.style.emojiGridButton);
|
||||
_emojiArray = emojiArray;
|
||||
@@ -139,12 +170,12 @@ public class EmojiGridView extends GridView
|
||||
{
|
||||
if (_emojiArray == null)
|
||||
return (0);
|
||||
return (_emojiArray.length);
|
||||
return (_emojiArray.size());
|
||||
}
|
||||
|
||||
public Object getItem(int pos)
|
||||
{
|
||||
return (_emojiArray[pos]);
|
||||
return (_emojiArray.get(pos));
|
||||
}
|
||||
|
||||
public long getItemId(int pos)
|
||||
@@ -158,7 +189,7 @@ public class EmojiGridView extends GridView
|
||||
|
||||
if (view == null)
|
||||
view = new EmojiView(_button_context);
|
||||
view.setEmoji(_emojiArray[pos]);
|
||||
view.setEmoji(_emojiArray.get(pos));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
@@ -19,9 +19,9 @@ public class EmojiGroupButtonsBar extends LinearLayout
|
||||
super(context, attrs);
|
||||
Emoji.init(context.getResources());
|
||||
add_group(EmojiGridView.GROUP_LAST_USE, "\uD83D\uDD59");
|
||||
for (int i = 0; i < Emoji.num_groups; i++)
|
||||
for (int i = 0; i < Emoji.getNumGroups(); i++)
|
||||
{
|
||||
Emoji first = Emoji.getEmojisByGroup(i)[0];
|
||||
Emoji first = Emoji.getEmojisByGroup(i).get(0);
|
||||
add_group(i, first.kv().getString());
|
||||
}
|
||||
}
|
||||
|
141
srcs/juloo.keyboard2/Gesture.java
Normal file
141
srcs/juloo.keyboard2/Gesture.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package juloo.keyboard2;
|
||||
|
||||
public final class Gesture
|
||||
{
|
||||
/** The pointer direction that caused the last state change.
|
||||
Integer from 0 to 15 (included). */
|
||||
int current_dir;
|
||||
|
||||
State state;
|
||||
|
||||
public Gesture(int starting_direction)
|
||||
{
|
||||
current_dir = starting_direction;
|
||||
state = State.Swiped;
|
||||
}
|
||||
|
||||
enum State
|
||||
{
|
||||
Cancelled,
|
||||
Swiped,
|
||||
Rotating_clockwise,
|
||||
Rotating_anticlockwise,
|
||||
Ended_swipe,
|
||||
Ended_center,
|
||||
Ended_clockwise,
|
||||
Ended_anticlockwise
|
||||
}
|
||||
|
||||
enum Name
|
||||
{
|
||||
None,
|
||||
Swipe,
|
||||
Roundtrip,
|
||||
Circle,
|
||||
Anticircle
|
||||
}
|
||||
|
||||
/** Angle to travel before a rotation gesture starts. A threshold too low
|
||||
would be too easy to reach while doing back and forth gestures, as the
|
||||
quadrants are very small. In the same unit as [current_dir] */
|
||||
static final int ROTATION_THRESHOLD = 2;
|
||||
|
||||
/** Return the currently recognized gesture. Return [null] if no gesture is
|
||||
recognized. Might change everytime [changed_direction] return [true]. */
|
||||
public Name get_gesture()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Cancelled:
|
||||
return Name.None;
|
||||
case Swiped:
|
||||
case Ended_swipe:
|
||||
return Name.Swipe;
|
||||
case Ended_center:
|
||||
return Name.Roundtrip;
|
||||
case Rotating_clockwise:
|
||||
case Ended_clockwise:
|
||||
return Name.Circle;
|
||||
case Rotating_anticlockwise:
|
||||
case Ended_anticlockwise:
|
||||
return Name.Anticircle;
|
||||
}
|
||||
return Name.None; // Unreachable
|
||||
}
|
||||
|
||||
public boolean is_in_progress()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Swiped:
|
||||
case Rotating_clockwise:
|
||||
case Rotating_anticlockwise:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int current_direction() { return current_dir; }
|
||||
|
||||
/** The pointer changed direction. Return [true] if the gesture changed
|
||||
state and [get_gesture] return a different value. */
|
||||
public boolean changed_direction(int direction)
|
||||
{
|
||||
int d = dir_diff(current_dir, direction);
|
||||
boolean clockwise = d > 0;
|
||||
switch (state)
|
||||
{
|
||||
case Swiped:
|
||||
if (Math.abs(d) < ROTATION_THRESHOLD)
|
||||
return false;
|
||||
// Start a rotation
|
||||
state = (clockwise) ?
|
||||
State.Rotating_clockwise : State.Rotating_anticlockwise;
|
||||
current_dir = direction;
|
||||
return true;
|
||||
// Check that rotation is not reversing
|
||||
case Rotating_clockwise:
|
||||
case Rotating_anticlockwise:
|
||||
current_dir = direction;
|
||||
if ((state == State.Rotating_clockwise) == clockwise)
|
||||
return false;
|
||||
state = State.Cancelled;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Return [true] if [get_gesture] will return a different value. */
|
||||
public boolean moved_to_center()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Swiped: state = State.Ended_center; return true;
|
||||
case Rotating_clockwise: state = State.Ended_clockwise; return false;
|
||||
case Rotating_anticlockwise: state = State.Ended_anticlockwise; return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Will not change the gesture state. */
|
||||
public void pointer_up()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Swiped: state = State.Ended_swipe; break;
|
||||
case Rotating_clockwise: state = State.Ended_clockwise; break;
|
||||
case Rotating_anticlockwise: state = State.Ended_anticlockwise; break;
|
||||
}
|
||||
}
|
||||
|
||||
static int dir_diff(int d1, int d2)
|
||||
{
|
||||
final int n = 16;
|
||||
// Shortest-path in modulo arithmetic
|
||||
if (d1 == d2)
|
||||
return 0;
|
||||
int left = (d1 - d2 + n) % n;
|
||||
int right = (d2 - d1 + n) % n;
|
||||
return (left < right) ? -left : right;
|
||||
}
|
||||
}
|
@@ -33,7 +33,6 @@ public final class KeyModifier
|
||||
if (r == null)
|
||||
{
|
||||
r = k;
|
||||
/* Order: Fn, Shift, accents */
|
||||
for (int i = 0; i < n_mods; i++)
|
||||
r = modify(r, mods.get(i));
|
||||
ks.put(mods, r);
|
||||
@@ -70,6 +69,7 @@ public final class KeyModifier
|
||||
case ALT:
|
||||
case META: return turn_into_keyevent(k);
|
||||
case FN: return apply_fn(k);
|
||||
case GESTURE: return apply_gesture(k);
|
||||
case SHIFT: return apply_shift(k);
|
||||
case GRAVE: return apply_map_char(k, map_char_grave);
|
||||
case AIGU: return apply_map_char(k, map_char_aigu);
|
||||
@@ -139,11 +139,10 @@ public final class KeyModifier
|
||||
case Char:
|
||||
char kc = k.getChar();
|
||||
String modified = map.apply(kc);
|
||||
if (modified == null)
|
||||
return k;
|
||||
return KeyValue.makeStringKey(modified, k.getFlags());
|
||||
default: return k;
|
||||
if (modified != null)
|
||||
return KeyValue.makeStringKey(modified, k.getFlags());
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
private static KeyValue apply_shift(KeyValue k)
|
||||
@@ -482,6 +481,15 @@ public final class KeyModifier
|
||||
return k.withKeyevent(e);
|
||||
}
|
||||
|
||||
/** Modify a key affected by a round-trip or a clockwise circle gesture. */
|
||||
private static KeyValue apply_gesture(KeyValue k)
|
||||
{
|
||||
KeyValue shifted = apply_shift(k);
|
||||
if (shifted == null || shifted.equals(k))
|
||||
return apply_fn(k);
|
||||
return shifted;
|
||||
}
|
||||
|
||||
/* Lookup the cache entry for a key. Create it needed. */
|
||||
private static HashMap<Pointers.Modifiers, KeyValue> cacheEntry(KeyValue k)
|
||||
{
|
||||
|
@@ -27,6 +27,7 @@ public final class KeyValue implements Comparable<KeyValue>
|
||||
public static enum Modifier
|
||||
{
|
||||
SHIFT,
|
||||
GESTURE,
|
||||
CTRL,
|
||||
ALT,
|
||||
META,
|
||||
@@ -54,8 +55,8 @@ public final class KeyValue implements Comparable<KeyValue>
|
||||
ARROW_RIGHT,
|
||||
BREVE,
|
||||
BAR,
|
||||
FN, // Must be placed last to be applied first
|
||||
}
|
||||
FN,
|
||||
} // Last is be applied first
|
||||
|
||||
public static enum Editing
|
||||
{
|
||||
@@ -404,6 +405,12 @@ public final class KeyValue implements Comparable<KeyValue>
|
||||
return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
|
||||
}
|
||||
|
||||
/** Make a modifier key for passing to [KeyModifier]. */
|
||||
public static KeyValue makeInternalModifier(Modifier mod)
|
||||
{
|
||||
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
|
||||
}
|
||||
|
||||
public static KeyValue getKeyByName(String name)
|
||||
{
|
||||
switch (name)
|
||||
|
@@ -268,8 +268,11 @@ public class Keyboard2View extends View
|
||||
if (VERSION.SDK_INT >= 29)
|
||||
{
|
||||
// Disable the back-gesture on the keyboard area
|
||||
Rect keyboard_area = new Rect(left,
|
||||
top + (int)_config.marginTop, right, bottom);
|
||||
Rect keyboard_area = new Rect(
|
||||
left + (int)_config.horizontal_margin,
|
||||
top + (int)_config.marginTop,
|
||||
right - (int)_config.horizontal_margin,
|
||||
bottom - (int)_config.margin_bottom);
|
||||
setSystemGestureExclusionRects(Arrays.asList(keyboard_area));
|
||||
}
|
||||
}
|
||||
@@ -319,10 +322,7 @@ public class Keyboard2View extends View
|
||||
if (k.keys[i] != null)
|
||||
drawSubLabel(canvas, k.keys[i], x, y, keyW, keyH, i, isKeyDown);
|
||||
}
|
||||
if (k.indication != null)
|
||||
{
|
||||
drawIndication(canvas, k.indication, keyW / 2f + x, y, keyH);
|
||||
}
|
||||
drawIndication(canvas, k, x, y, keyW, keyH);
|
||||
x += _keyWidth * k.width;
|
||||
}
|
||||
y += row.height * _config.keyHeight;
|
||||
@@ -440,15 +440,33 @@ public class Keyboard2View extends View
|
||||
canvas.drawText(label, 0, label_len, x, y, p);
|
||||
}
|
||||
|
||||
private void drawIndication(Canvas canvas, String indication, float x,
|
||||
float y, float keyH)
|
||||
private void drawIndication(Canvas canvas, KeyboardData.Key k, float x,
|
||||
float y, float keyW, float keyH)
|
||||
{
|
||||
float textSize = keyH * _config.sublabelTextSize * _config.characterSize;
|
||||
Paint p = _theme.indicationPaint();
|
||||
boolean special_font = false;
|
||||
String indic;
|
||||
float text_size;
|
||||
if (k.indication != null)
|
||||
{
|
||||
indic = k.indication;
|
||||
text_size = keyH * _config.sublabelTextSize * _config.characterSize;
|
||||
}
|
||||
else if (k.anticircle != null)
|
||||
{
|
||||
indic = k.anticircle.getString();
|
||||
special_font = k.anticircle.hasFlagsAny(KeyValue.FLAG_KEY_FONT);
|
||||
text_size = scaleTextSize(k.anticircle, _config.sublabelTextSize, keyH);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
Paint p = _theme.indicationPaint(special_font);
|
||||
p.setColor(_theme.subLabelColor);
|
||||
p.setTextSize(textSize);
|
||||
canvas.drawText(indication, x,
|
||||
(keyH - p.ascent() - p.descent()) * 4/5 + y, p);
|
||||
p.setTextSize(text_size);
|
||||
// Limit indication length to 3 characters
|
||||
canvas.drawText(indic, 0, Math.min(indic.length(), 3),
|
||||
x + keyW / 2f, (keyH - p.ascent() - p.descent()) * 4/5 + y, p);
|
||||
}
|
||||
|
||||
private float scaleTextSize(KeyValue k, float rel_size, float keyH)
|
||||
|
@@ -373,6 +373,8 @@ public final class KeyboardData
|
||||
* 3 8 4
|
||||
*/
|
||||
public final KeyValue[] keys;
|
||||
/** Key accessed by the anti-clockwise circle gesture. */
|
||||
public final KeyValue anticircle;
|
||||
/** Pack flags for every key values. Flags are: [F_LOC]. */
|
||||
private final int keysflags;
|
||||
/** Key width in relative unit. */
|
||||
@@ -389,9 +391,10 @@ public final class KeyboardData
|
||||
public static final int F_LOC = 1;
|
||||
public static final int ALL_FLAGS = F_LOC;
|
||||
|
||||
protected Key(KeyValue[] ks, int f, float w, float s, boolean sl, String i)
|
||||
protected Key(KeyValue[] ks, KeyValue antic, int f, float w, float s, boolean sl, String i)
|
||||
{
|
||||
keys = ks;
|
||||
anticircle = antic;
|
||||
keysflags = f;
|
||||
width = w;
|
||||
shift = s;
|
||||
@@ -399,27 +402,48 @@ public final class KeyboardData
|
||||
indication = i;
|
||||
}
|
||||
|
||||
/** Write the parsed key into [ks] at [index]. Doesn't write if the
|
||||
attribute is not present. Return flags that can be aggregated into the
|
||||
value for [keysflags]. */
|
||||
static int parse_key_attr(XmlPullParser parser, String attr, KeyValue[] ks,
|
||||
/** Read a key value attribute that have a synonym. Having both synonyms
|
||||
present at the same time is an error.
|
||||
Returns [null] if the attributes are not present. */
|
||||
static String get_key_attr(XmlPullParser parser, String syn1, String syn2)
|
||||
throws Exception
|
||||
{
|
||||
String name1 = parser.getAttributeValue(null, syn1);
|
||||
String name2 = parser.getAttributeValue(null, syn2);
|
||||
if (name1 != null && name2 != null)
|
||||
throw error(parser,
|
||||
"'"+syn1+"' and '"+syn2+"' are synonyms and cannot be passed at the same time.");
|
||||
return (name1 == null) ? name2 : name1;
|
||||
}
|
||||
|
||||
/** Parse the key description [key_attr] and write into [ks] at [index].
|
||||
Returns flags that can be aggregated into the value for [keysflags].
|
||||
[key_attr] can be [null] for convenience. */
|
||||
static int parse_key_attr(XmlPullParser parser, String key_val, KeyValue[] ks,
|
||||
int index)
|
||||
throws Exception
|
||||
{
|
||||
String name = parser.getAttributeValue(null, attr);
|
||||
int flags = 0;
|
||||
if (name == null)
|
||||
if (key_val == null)
|
||||
return 0;
|
||||
String name_loc = stripPrefix(name, "loc ");
|
||||
int flags = 0;
|
||||
String name_loc = stripPrefix(key_val, "loc ");
|
||||
if (name_loc != null)
|
||||
{
|
||||
flags |= F_LOC;
|
||||
name = name_loc;
|
||||
key_val = name_loc;
|
||||
}
|
||||
ks[index] = KeyValue.getKeyByName(name);
|
||||
ks[index] = KeyValue.getKeyByName(key_val);
|
||||
return (flags << index);
|
||||
}
|
||||
|
||||
static KeyValue parse_nonloc_key_attr(XmlPullParser parser, String attr_name) throws Exception
|
||||
{
|
||||
String name = parser.getAttributeValue(null, attr_name);
|
||||
if (name == null)
|
||||
return null;
|
||||
return KeyValue.getKeyByName(name);
|
||||
}
|
||||
|
||||
static String stripPrefix(String s, String prefix)
|
||||
{
|
||||
return s.startsWith(prefix) ? s.substring(prefix.length()) : null;
|
||||
@@ -429,22 +453,25 @@ public final class KeyboardData
|
||||
{
|
||||
KeyValue[] ks = new KeyValue[9];
|
||||
int keysflags = 0;
|
||||
keysflags |= parse_key_attr(parser, "key0", ks, 0);
|
||||
keysflags |= parse_key_attr(parser, "key1", ks, 1);
|
||||
keysflags |= parse_key_attr(parser, "key2", ks, 2);
|
||||
keysflags |= parse_key_attr(parser, "key3", ks, 3);
|
||||
keysflags |= parse_key_attr(parser, "key4", ks, 4);
|
||||
keysflags |= parse_key_attr(parser, "key5", ks, 5);
|
||||
keysflags |= parse_key_attr(parser, "key6", ks, 6);
|
||||
keysflags |= parse_key_attr(parser, "key7", ks, 7);
|
||||
keysflags |= parse_key_attr(parser, "key8", ks, 8);
|
||||
keysflags |= parse_key_attr(parser, parser.getAttributeValue(null, "key0"), ks, 0);
|
||||
/* Swipe gestures (key1-key8 diagram above), with compass-point synonyms. */
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key1", "nw"), ks, 1);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key2", "ne"), ks, 2);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key3", "sw"), ks, 3);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key4", "se"), ks, 4);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key5", "w"), ks, 5);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key6", "e"), ks, 6);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key7", "n"), ks, 7);
|
||||
keysflags |= parse_key_attr(parser, get_key_attr(parser, "key8", "s"), ks, 8);
|
||||
/* Other key attributes */
|
||||
KeyValue anticircle = parse_nonloc_key_attr(parser, "anticircle");
|
||||
float width = attribute_float(parser, "width", 1f);
|
||||
float shift = attribute_float(parser, "shift", 0.f);
|
||||
boolean slider = attribute_bool(parser, "slider", false);
|
||||
String indication = parser.getAttributeValue(null, "indication");
|
||||
while (parser.next() != XmlPullParser.END_TAG)
|
||||
continue;
|
||||
return new Key(ks, keysflags, width, shift, slider, indication);
|
||||
return new Key(ks, anticircle, keysflags, width, shift, slider, indication);
|
||||
}
|
||||
|
||||
/** Whether key at [index] as [flag]. */
|
||||
@@ -456,7 +483,8 @@ public final class KeyboardData
|
||||
/** New key with the width multiplied by 's'. */
|
||||
public Key scaleWidth(float s)
|
||||
{
|
||||
return new Key(keys, keysflags, width * s, shift, slider, indication);
|
||||
return new Key(keys, anticircle, keysflags, width * s, shift, slider,
|
||||
indication);
|
||||
}
|
||||
|
||||
public void getKeys(Map<KeyValue, KeyPos> dst, int row, int col)
|
||||
@@ -477,12 +505,12 @@ public final class KeyboardData
|
||||
for (int j = 0; j < keys.length; j++) ks[j] = keys[j];
|
||||
ks[i] = kv;
|
||||
int flags = (keysflags & ~(ALL_FLAGS << i));
|
||||
return new Key(ks, flags, width, shift, slider, indication);
|
||||
return new Key(ks, anticircle, flags, width, shift, slider, indication);
|
||||
}
|
||||
|
||||
public Key withShift(float s)
|
||||
{
|
||||
return new Key(keys, keysflags, width, s, slider, indication);
|
||||
return new Key(keys, anticircle, keysflags, width, s, slider, indication);
|
||||
}
|
||||
|
||||
public boolean hasValue(KeyValue kv)
|
||||
@@ -508,7 +536,7 @@ public final class KeyboardData
|
||||
for (int i = 0; i < ks.length; i++)
|
||||
if (k.keys[i] != null)
|
||||
ks[i] = apply(k.keys[i], k.keyHasFlag(i, Key.F_LOC));
|
||||
return new Key(ks, k.keysflags, k.width, k.shift, k.slider, k.indication);
|
||||
return new Key(ks, k.anticircle, k.keysflags, k.width, k.shift, k.slider, k.indication);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,8 @@ import android.os.Build.VERSION;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
@@ -37,7 +39,21 @@ public class LauncherActivity extends Activity
|
||||
this.new Tryhere_OnUnhandledKeyEventListener());
|
||||
setup_intro_video(_intro_video);
|
||||
}
|
||||
@Override
|
||||
public final boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.launcher_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.btnLaunchSettingsActivity) {
|
||||
Intent intent = new Intent(LauncherActivity.this, SettingsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
public void launch_imesettings(View _btn)
|
||||
{
|
||||
startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS));
|
||||
|
@@ -143,6 +143,12 @@ public final class Pointers implements Handler.Callback
|
||||
return;
|
||||
}
|
||||
stopKeyRepeat(ptr);
|
||||
KeyValue ptr_value = ptr.value;
|
||||
if (ptr.gesture != null && ptr.gesture.is_in_progress())
|
||||
{
|
||||
// A gesture was in progress
|
||||
ptr.gesture.pointer_up();
|
||||
}
|
||||
Pointer latched = getLatched(ptr);
|
||||
if (latched != null) // Already latched
|
||||
{
|
||||
@@ -152,7 +158,7 @@ public final class Pointers implements Handler.Callback
|
||||
else // Otherwise, unlatch
|
||||
{
|
||||
removePtr(latched);
|
||||
_handler.onPointerUp(ptr.value, ptr.modifiers);
|
||||
_handler.onPointerUp(ptr_value, ptr.modifiers);
|
||||
}
|
||||
}
|
||||
else if ((ptr.flags & FLAG_P_LATCHABLE) != 0)
|
||||
@@ -168,7 +174,7 @@ public final class Pointers implements Handler.Callback
|
||||
{
|
||||
clearLatched();
|
||||
removePtr(ptr);
|
||||
_handler.onPointerUp(ptr.value, ptr.modifiers);
|
||||
_handler.onPointerUp(ptr_value, ptr.modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,18 +223,15 @@ public final class Pointers implements Handler.Callback
|
||||
return k.keys[DIRECTION_TO_INDEX[direction]];
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the KeyValue at the given direction. In case of swipe (direction !=
|
||||
* null), get the nearest KeyValue that is not key0.
|
||||
* Take care of applying [_handler.onPointerSwipe] to the selected key, this
|
||||
* must be done at the same time to be sure to treat removed keys correctly.
|
||||
* Return [null] if no key could be found in the given direction or if the
|
||||
* selected key didn't change.
|
||||
/**
|
||||
* Get the key nearest to [direction] that is not key0. Take care
|
||||
* of applying [_handler.modifyKey] to the selected key in the same
|
||||
* operation to be sure to treat removed keys correctly.
|
||||
* Return [null] if no key could be found in the given direction or
|
||||
* if the selected key didn't change.
|
||||
*/
|
||||
private KeyValue getNearestKeyAtDirection(Pointer ptr, Integer direction)
|
||||
private KeyValue getNearestKeyAtDirection(Pointer ptr, int direction)
|
||||
{
|
||||
if (direction == null)
|
||||
return _handler.modifyKey(ptr.key.keys[0], ptr.modifiers);
|
||||
KeyValue k;
|
||||
// [i] is [0, -1, 1, -2, 2, ...]
|
||||
for (int i = 0; i > -4; i = (~i>>31) - i)
|
||||
@@ -261,37 +264,59 @@ public final class Pointers implements Handler.Callback
|
||||
float dy = y - ptr.downY;
|
||||
|
||||
float dist = Math.abs(dx) + Math.abs(dy);
|
||||
Integer direction;
|
||||
if (dist < _config.swipe_dist_px)
|
||||
{
|
||||
direction = null;
|
||||
// Pointer is still on the center.
|
||||
if (ptr.gesture == null || !ptr.gesture.is_in_progress())
|
||||
return;
|
||||
// Gesture ended
|
||||
ptr.gesture.moved_to_center();
|
||||
ptr.value = apply_gesture(ptr, ptr.gesture.get_gesture());
|
||||
ptr.flags = 0;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
{ // Pointer is on a quadrant.
|
||||
// See [getKeyAtDirection()] for the meaning. The starting point on the
|
||||
// circle is the top direction.
|
||||
double a = Math.atan2(dy, dx) + Math.PI;
|
||||
// a is between 0 and 2pi, 0 is pointing to the left
|
||||
// add 12 to align 0 to the top
|
||||
direction = ((int)(a * 8 / Math.PI) + 12) % 16;
|
||||
}
|
||||
int direction = ((int)(a * 8 / Math.PI) + 12) % 16;
|
||||
if (ptr.gesture == null)
|
||||
{ // Gesture starts
|
||||
|
||||
if (direction != ptr.selected_direction)
|
||||
{
|
||||
ptr.selected_direction = direction;
|
||||
KeyValue newValue = getNearestKeyAtDirection(ptr, direction);
|
||||
if (newValue != null && !newValue.equals(ptr.value))
|
||||
{
|
||||
ptr.value = newValue;
|
||||
ptr.flags = pointer_flags_of_kv(newValue);
|
||||
// Sliding mode is entered when key5 or key6 is down on a slider key.
|
||||
if (ptr.key.slider &&
|
||||
(newValue.equals(ptr.key.getKeyValue(5))
|
||||
|| newValue.equals(ptr.key.getKeyValue(6))))
|
||||
{
|
||||
startSliding(ptr, x);
|
||||
ptr.gesture = new Gesture(direction);
|
||||
KeyValue new_value = getNearestKeyAtDirection(ptr, direction);
|
||||
if (new_value != null)
|
||||
{ // Pointer is swiping into a side key.
|
||||
|
||||
ptr.value = new_value;
|
||||
ptr.flags = pointer_flags_of_kv(new_value);
|
||||
// Sliding mode is entered when key5 or key6 is down on a slider key.
|
||||
if (ptr.key.slider &&
|
||||
(new_value.equals(ptr.key.getKeyValue(5))
|
||||
|| new_value.equals(ptr.key.getKeyValue(6))))
|
||||
{
|
||||
startSliding(ptr, x);
|
||||
}
|
||||
_handler.onPointerDown(new_value, true);
|
||||
}
|
||||
|
||||
}
|
||||
else if (ptr.gesture.changed_direction(direction))
|
||||
{ // Gesture changed state
|
||||
if (!ptr.gesture.is_in_progress())
|
||||
{ // Gesture ended
|
||||
stopKeyRepeat(ptr);
|
||||
_handler.onPointerFlagsChanged(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr.value = apply_gesture(ptr, ptr.gesture.get_gesture());
|
||||
restartKeyRepeat(ptr);
|
||||
ptr.flags = 0; // Special behaviors are ignored during a gesture.
|
||||
}
|
||||
_handler.onPointerDown(newValue, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,6 +420,12 @@ public final class Pointers implements Handler.Callback
|
||||
}
|
||||
}
|
||||
|
||||
private void restartKeyRepeat(Pointer ptr)
|
||||
{
|
||||
stopKeyRepeat(ptr);
|
||||
startKeyRepeat(ptr);
|
||||
}
|
||||
|
||||
/** A pointer is repeating. Returns [true] if repeat should continue. */
|
||||
private boolean handleKeyRepeat(Pointer ptr)
|
||||
{
|
||||
@@ -447,14 +478,51 @@ public final class Pointers implements Handler.Callback
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Gestures
|
||||
|
||||
/** Apply a gesture to the current key. */
|
||||
KeyValue apply_gesture(Pointer ptr, Gesture.Name gesture)
|
||||
{
|
||||
switch (gesture)
|
||||
{
|
||||
case None:
|
||||
return ptr.value;
|
||||
case Swipe:
|
||||
return ptr.value;
|
||||
case Roundtrip:
|
||||
return
|
||||
modify_key_with_extra_modifier(
|
||||
ptr,
|
||||
getNearestKeyAtDirection(ptr, ptr.gesture.current_direction()),
|
||||
KeyValue.Modifier.GESTURE);
|
||||
case Circle:
|
||||
return
|
||||
modify_key_with_extra_modifier(ptr, ptr.key.keys[0],
|
||||
KeyValue.Modifier.GESTURE);
|
||||
case Anticircle:
|
||||
return _handler.modifyKey(ptr.key.anticircle, ptr.modifiers);
|
||||
}
|
||||
return ptr.value; // Unreachable
|
||||
}
|
||||
|
||||
KeyValue modify_key_with_extra_modifier(Pointer ptr, KeyValue kv,
|
||||
KeyValue.Modifier extra_mod)
|
||||
{
|
||||
return
|
||||
_handler.modifyKey(kv,
|
||||
ptr.modifiers.with_extra_mod(KeyValue.makeInternalModifier(extra_mod)));
|
||||
}
|
||||
|
||||
// Pointers
|
||||
|
||||
private static final class Pointer
|
||||
{
|
||||
/** -1 when latched. */
|
||||
public int pointerId;
|
||||
/** The Key pressed by this Pointer */
|
||||
public final KeyboardData.Key key;
|
||||
/** Current direction. [null] means not swiping. */
|
||||
public Integer selected_direction;
|
||||
/** Gesture state, see [Gesture]. [null] means the pointer has not moved out of the center region. */
|
||||
public Gesture gesture;
|
||||
/** Selected value with [modifiers] applied. */
|
||||
public KeyValue value;
|
||||
public float downX;
|
||||
@@ -472,7 +540,7 @@ public final class Pointers implements Handler.Callback
|
||||
{
|
||||
pointerId = p;
|
||||
key = k;
|
||||
selected_direction = null;
|
||||
gesture = null;
|
||||
value = v;
|
||||
downX = x;
|
||||
downY = y;
|
||||
@@ -602,6 +670,14 @@ public final class Pointers implements Handler.Callback
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Return a copy of this object with an extra modifier added. */
|
||||
public Modifiers with_extra_mod(KeyValue m)
|
||||
{
|
||||
KeyValue[] newmods = Arrays.copyOf(_mods, _size + 1);
|
||||
newmods[_size] = m;
|
||||
return ofArray(newmods, newmods.length);
|
||||
}
|
||||
|
||||
/** Returns the activated modifiers that are not in [m2]. */
|
||||
public Iterator<KeyValue> diff(Modifiers m2)
|
||||
{
|
||||
|
@@ -35,6 +35,7 @@ public class Theme
|
||||
private final Paint _keySubLabelPaint;
|
||||
private final Paint _specialKeySubLabelPaint;
|
||||
private final Paint _indicationPaint;
|
||||
private final Paint _specialIndicationPaint;
|
||||
|
||||
public Theme(Context context, AttributeSet attrs)
|
||||
{
|
||||
@@ -68,6 +69,7 @@ public class Theme
|
||||
_specialKeyLabelPaint = initLabelPaint(Paint.Align.CENTER, specialKeyFont);
|
||||
_specialKeySubLabelPaint = initLabelPaint(Paint.Align.LEFT, specialKeyFont);
|
||||
_indicationPaint = initLabelPaint(Paint.Align.CENTER, null);
|
||||
_specialIndicationPaint = initLabelPaint(Paint.Align.CENTER, specialKeyFont);
|
||||
}
|
||||
|
||||
public Paint labelPaint(boolean special_font)
|
||||
@@ -83,9 +85,9 @@ public class Theme
|
||||
return p;
|
||||
}
|
||||
|
||||
public Paint indicationPaint()
|
||||
public Paint indicationPaint(boolean special_font)
|
||||
{
|
||||
return _indicationPaint;
|
||||
return special_font ? _specialIndicationPaint : _indicationPaint;
|
||||
}
|
||||
|
||||
/** Interpolate the 'value' component toward its opposite by 'alpha'. */
|
||||
|
@@ -1,48 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<keyboard name="두벌식 (Korean)" script="hangul">
|
||||
<modmap>
|
||||
<!-- The hangul character don't have shifted variants, this is specific to
|
||||
the layout. -->
|
||||
<shift a="ㅂ" b="ㅃ"/>
|
||||
<shift a="ㅈ" b="ㅉ"/>
|
||||
<shift a="ㄷ" b="ㄸ"/>
|
||||
<shift a="ㄱ" b="ㄲ"/>
|
||||
<shift a="ㅅ" b="ㅆ"/>
|
||||
<shift a="ㅐ" b="ㅒ"/>
|
||||
<shift a="ㅔ" b="ㅖ"/>
|
||||
</modmap>
|
||||
<row>
|
||||
<key key0="ㅂ" key2="1" key4="esc"/>
|
||||
<key key0="ㅈ" key1="~" key2="2" key3="\@"/>
|
||||
<key key0="ㄷ" key1="!" key2="3" key3="\#"/>
|
||||
<key key0="ㄱ" key2="4" key3="$"/>
|
||||
<key key0="ㅅ" key2="5" key3="%"/>
|
||||
<key key0="ㅛ" key2="6" key3="^"/>
|
||||
<key key0="ㅕ" key2="7" key3="&"/>
|
||||
<key key0="ㅑ" key2="8" key3="*"/>
|
||||
<key key0="ㅐ" key2="9" key3="(" key4=")"/>
|
||||
<key key0="ㅔ" key2="0" key3="f11_placeholder" key4="f12_placeholder"/>
|
||||
<key key0="ㅂ" key2="1" key3="ㅄ" key4="ㅃ"/>
|
||||
<key key0="ㅈ" key1="~" key2="2" key3="ㄵ" key4="ㅉ"/>
|
||||
<key key0="ㄷ" key1="!" key2="3" key4="ㄸ"/>
|
||||
<key key0="ㄱ" key1="\@" key2="4" key3="ㄺ" key4="ㄲ"/>
|
||||
<key key0="ㅅ" key1="\#" key2="5" key3="ㄳ" key4="ㅆ"/>
|
||||
<key key0="ㅛ" key1="$" key2="6"/>
|
||||
<key key0="ㅕ" key1="%" key2="7" key3="ㅖ"/>
|
||||
<key key0="ㅑ" key1="^" key2="8"/>
|
||||
<key key0="ㅐ" key1="&" key2="9" key3="ㅒ"/>
|
||||
<key key0="ㅔ" key1="*" key2="0" key3="ㅙ"/>
|
||||
</row>
|
||||
<row>
|
||||
<key shift="0.5" key0="ㅁ" key1="tab" key2="`"/>
|
||||
<key key0="ㄴ"/>
|
||||
<key key0="ㅇ"/>
|
||||
<key key0="ㄹ"/>
|
||||
<key key0="ㅎ" key2="-" key3="_"/>
|
||||
<key key0="ㅗ" key2="=" key3="+"/>
|
||||
<key key0="ㅓ" key4="}" key3="{"/>
|
||||
<key key0="ㅏ" key3="[" key4="]"/>
|
||||
<key shift="0.5" key0="ㅁ" key2="`"/>
|
||||
<key key0="ㄴ" key3="ㄼ"/>
|
||||
<key key0="ㅇ" key3="ㄻ"/>
|
||||
<key key0="ㄹ" key1="-" key2="_" key3="ㄽ"/>
|
||||
<key key0="ㅎ" key1="=" key2="+" key3="ㅀ" key4="ㄶ"/>
|
||||
<key key0="ㅗ" key1="{" key2="}" key3="ㅚ"/>
|
||||
<key key0="ㅓ" key1="[" key2="]" key3="ㅘ"/>
|
||||
<key key0="ㅏ" key1="(" key2=")"/>
|
||||
<key key0="ㅣ" key2="|" key3="\\"/>
|
||||
</row>
|
||||
<row>
|
||||
<key width="1.5" key0="shift" key2="loc capslock"/>
|
||||
<key key0="ㅋ"/>
|
||||
<key key0="ㅌ"/>
|
||||
<key key0="ㅊ" key2="<" key3="."/>
|
||||
<key key0="ㅍ" key2=">" key3=","/>
|
||||
<key key0="ㅠ" key2="\?" key3="/"/>
|
||||
<key key0="ㅜ" key2=":" key3=";"/>
|
||||
<key key0="ㅡ" key2=""" key3="'"/>
|
||||
<key key0="ㅌ" key1="ㄾ" key3="<" key4=">"/>
|
||||
<key key0="ㅊ" key3="."/>
|
||||
<key key0="ㅍ" key1="ㄿ" key3=","/>
|
||||
<key key0="ㅠ" key1="ㅞ" key2="ㅝ" key3="/" key4="\?"/>
|
||||
<key key0="ㅜ" key2="ㅟ" key3=";" key4=":"/>
|
||||
<key key0="ㅡ" key2="ㅢ" key3="'" key4="""/>
|
||||
<key width="1.5" key0="backspace" key2="delete"/>
|
||||
</row>
|
||||
</keyboard>
|
||||
|
Reference in New Issue
Block a user