Compare commits

...

7 Commits

Author SHA1 Message Date
Jules Aguillon
506912be60 Fix other modifiers not applied
This adds a "GESTURE" modifier in KeyValue that materialize the gesture
into [Modifiers] and allow it to be applied at the same time as the
other modifiers, in the right order.

This moves the key modifying code back into Pointer to allow more
modifier manipulations.
2024-05-25 02:19:55 +02:00
Jules Aguillon
d03afb6e8f Apply Shift then fallback to Fn
This changes both the round trip and the clockwise circle gestures to
first apply Shift and if that didn't change the key, fallback to Fn.

This allows to have letters in the corner of keys while being able to
shift them. Clockwise circle modify keys the same way as round trip for
the center key.

The anti-clockwise gesture now do nothing. It's intended to make it
configurable per-layout.
2024-05-25 01:20:15 +02:00
Jules Aguillon
8021a626c5 Circle and round trip gestures
This implements clockwise/anticlockwise circle and round trip gestures
inspired by Messagease.

The circle gestures start after a small threshold to avoid making the
regular swipe too hard to aim.

The gestures do:

- clockwise circle: Shift variant of the center symbol
- anticlockwise circle: Fn variant of the center symbol
- round trip: Fn variant of the targetted side symbol

The Gesture class now keep track of what the pointer is doing while it
moves on a key. It replaces the 'selected_direction' integer.
2024-05-23 21:05:05 +02:00
NACAMURA Mitsuhiro
96fc4003f1 Update NixOS wiki link (#633) 2024-05-12 10:53:04 +02:00
Quinn Cypher
a91332a903 Pull the emoji list from unicode.org (#612)
- Removing unused information (names and descriptions) from the Emoji class
- Creating a Gradle task that generates a more efficient res/raw/emojis.txt file from the most recent Unicode standard
- Saving recently used emoji preferences as emoji values rather than names
- Migrating old user preferences to the new system
2024-05-08 13:02:19 +02:00
Spike
53e04d5784 Compass-point synonyms for edge keys in layouts (#628) 2024-05-08 12:51:11 +02:00
alotbsol555
c7d33356bc Add settings button to launcher app (#629) 2024-05-05 11:22:34 +02:00
15 changed files with 4961 additions and 3926 deletions

View File

@@ -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:

View File

@@ -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'

38
gen_emoji.py Normal file
View 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)}')

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View 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;
}
}

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -399,24 +399,37 @@ 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);
}
@@ -429,15 +442,17 @@ 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 */
float width = attribute_float(parser, "width", 1f);
float shift = attribute_float(parser, "shift", 0.f);
boolean slider = attribute_bool(parser, "slider", false);

View File

@@ -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));

View File

@@ -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.keys[0], 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)
{