diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index 30282c7..0dd7f87 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -242,6 +242,7 @@ public final class KeyEventHandler case AUTOFILL: send_context_menu_action(android.R.id.autofill); break; case DELETE_WORD: send_key_down_up(KeyEvent.KEYCODE_DEL, KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON); break; case FORWARD_DELETE_WORD: send_key_down_up(KeyEvent.KEYCODE_FORWARD_DEL, KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON); break; + case SELECTION_CANCEL: cancel_selection(); break; } } @@ -259,15 +260,17 @@ public final class KeyEventHandler return conn.getExtractedText(_move_cursor_req, 0); } - /** [repeatition] might be negative, in which case the direction is reversed. */ - void handle_slider(KeyValue.Slider s, int repeatition) + /** [r] might be negative, in which case the direction is reversed. */ + void handle_slider(KeyValue.Slider s, int r) { switch (s) { - case Cursor_left: move_cursor(-repeatition); break; - case Cursor_right: move_cursor(repeatition); break; - case Cursor_up: move_cursor_vertical(-repeatition); break; - case Cursor_down: move_cursor_vertical(repeatition); break; + case Cursor_left: move_cursor(-r); break; + case Cursor_right: move_cursor(r); break; + case Cursor_up: move_cursor_vertical(-r); break; + case Cursor_down: move_cursor_vertical(r); break; + case Selection_cursor_left: move_cursor_sel(r, true); break; + case Selection_cursor_right: move_cursor_sel(r, false); break; } } @@ -281,12 +284,7 @@ public final class KeyEventHandler if (conn == null) return; ExtractedText et = get_cursor_pos(conn); - int system_mods = - KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_META_ON; - // Fallback to sending key events if system modifiers are activated or - // ExtractedText is not supported, for example on Termux. - if (!_move_cursor_force_fallback && et != null - && (_meta_state & system_mods) == 0) + if (et != null && can_set_selection(conn)) { int sel_start = et.selectionStart; int sel_end = et.selectionEnd; @@ -305,8 +303,45 @@ public final class KeyEventHandler sel_start = sel_end; } if (conn.setSelection(sel_start, sel_end)) - return; // [setSelection] succeeded, don't fallback to key events + return; // Fallback to sending key events if [setSelection] failed } + move_cursor_fallback(d); + } + + /** Move one of the two side of a selection. If [sel_left] is true, the left + position is moved, otherwise the right position is moved. */ + void move_cursor_sel(int d, boolean sel_left) + { + InputConnection conn = _recv.getCurrentInputConnection(); + if (conn == null) + return; + ExtractedText et = get_cursor_pos(conn); + if (et != null && can_set_selection(conn)) + { + int sel_start = et.selectionStart; + int sel_end = et.selectionEnd; + if (sel_left == (sel_start <= sel_end)) + sel_start += d; + else + sel_end += d; + if (conn.setSelection(sel_start, sel_end)) + return; // Fallback to sending key events if [setSelection] failed + } + move_cursor_fallback(d); + } + + /** Returns whether the selection can be set using [conn.setSelection()]. + This can happen on Termux or when system modifiers are activated for + example. */ + boolean can_set_selection(InputConnection conn) + { + final int system_mods = + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_META_ON; + return !_move_cursor_force_fallback && (_meta_state & system_mods) == 0; + } + + void move_cursor_fallback(int d) + { if (d < 0) send_key_down_up_repeat(KeyEvent.KEYCODE_DPAD_LEFT, -d); else @@ -400,11 +435,25 @@ public final class KeyEventHandler send_key_down_up(event_code); } + void cancel_selection() + { + InputConnection conn = _recv.getCurrentInputConnection(); + if (conn == null) + return; + ExtractedText et = get_cursor_pos(conn); + if (et == null) return; + final int curs = et.selectionStart; + // Notify the receiver as Android's [onUpdateSelection] is not triggered. + if (conn.setSelection(curs, curs)); + _recv.selection_state_changed(false); + } + public static interface IReceiver { public void handle_event_key(KeyValue.Event ev); public void set_shift_state(boolean state, boolean lock); public void set_compose_pending(boolean pending); + public void selection_state_changed(boolean selection_is_ongoing); public InputConnection getCurrentInputConnection(); public Handler getHandler(); } diff --git a/srcs/juloo.keyboard2/KeyModifier.java b/srcs/juloo.keyboard2/KeyModifier.java index bb33cca..e512521 100644 --- a/srcs/juloo.keyboard2/KeyModifier.java +++ b/srcs/juloo.keyboard2/KeyModifier.java @@ -82,6 +82,7 @@ public final class KeyModifier case HOOK_ABOVE: return apply_compose(k, ComposeKeyData.accent_hook_above); case DOUBLE_GRAVE: return apply_compose(k, ComposeKeyData.accent_double_grave); case ARROW_RIGHT: return apply_combining_char(k, "\u20D7"); + case SELECTION_MODE: return apply_selection_mode(k); default: return k; } } @@ -392,6 +393,34 @@ public final class KeyModifier return (name == null) ? k : KeyValue.getKeyByName(name); } + private static KeyValue apply_selection_mode(KeyValue k) + { + String name = null; + switch (k.getKind()) + { + case Char: + switch (k.getChar()) + { + case ' ': name = "selection_cancel"; break; + } + break; + case Slider: + switch (k.getSlider()) + { + case Cursor_left: name = "selection_cursor_left"; break; + case Cursor_right: name = "selection_cursor_right"; break; + } + break; + case Keyevent: + switch (k.getKeyevent()) + { + case KeyEvent.KEYCODE_ESCAPE: name = "selection_cancel"; break; + } + break; + } + return (name == null) ? k : KeyValue.getKeyByName(name); + } + /** Compose the precomposed initial with the medial [kv]. */ private static KeyValue combine_hangul_initial(KeyValue kv, int precomposed) { diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java index 413520d..ecfdd94 100644 --- a/srcs/juloo.keyboard2/KeyValue.java +++ b/srcs/juloo.keyboard2/KeyValue.java @@ -59,6 +59,7 @@ public final class KeyValue implements Comparable BREVE, BAR, FN, + SELECTION_MODE, } // Last is be applied first public static enum Editing @@ -77,6 +78,7 @@ public final class KeyValue implements Comparable AUTOFILL, DELETE_WORD, FORWARD_DELETE_WORD, + SELECTION_CANCEL, } public static enum Placeholder @@ -715,6 +717,9 @@ public final class KeyValue implements Comparable case "cursor_right": return sliderKey(Slider.Cursor_right, 1); case "cursor_up": return sliderKey(Slider.Cursor_up, 1); case "cursor_down": return sliderKey(Slider.Cursor_down, 1); + case "selection_cancel": return editingKey("Esc", Editing.SELECTION_CANCEL, FLAG_SMALLER_FONT); + case "selection_cursor_left": return sliderKey(Slider.Selection_cursor_left, -1); // Move the left side of the selection + case "selection_cursor_right": return sliderKey(Slider.Selection_cursor_right, 1); // These keys are not used case "replaceText": return editingKey("repl", Editing.REPLACE); case "textAssist": return editingKey(0xE038, Editing.ASSIST); @@ -764,6 +769,9 @@ public final class KeyValue implements Comparable case "௲": case "௳": return makeStringKey(name, FLAG_SMALLER_FONT); + /* Internal keys */ + case "selection_mode": return makeInternalModifier(Modifier.SELECTION_MODE); + default: return null; } } @@ -780,7 +788,9 @@ public final class KeyValue implements Comparable Cursor_left(0xE008), Cursor_right(0xE006), Cursor_up(0xE005), - Cursor_down(0xE007); + Cursor_down(0xE007), + Selection_cursor_left(0xE008), + Selection_cursor_right(0xE006); final String symbol; diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java index 02c062a..9e54aaf 100644 --- a/srcs/juloo.keyboard2/Keyboard2.java +++ b/srcs/juloo.keyboard2/Keyboard2.java @@ -362,6 +362,8 @@ public class Keyboard2 extends InputMethodService { super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); _keyeventhandler.selection_updated(oldSelStart, newSelStart); + if ((oldSelStart == oldSelEnd) != (newSelStart == newSelEnd)) + _keyboardView.set_selection_state(newSelStart != newSelEnd); } @Override @@ -480,6 +482,11 @@ public class Keyboard2 extends InputMethodService _keyboardView.set_compose_pending(pending); } + public void selection_state_changed(boolean selection_is_ongoing) + { + _keyboardView.set_selection_state(selection_is_ongoing); + } + public InputConnection getCurrentInputConnection() { return Keyboard2.this.getCurrentInputConnection(); diff --git a/srcs/juloo.keyboard2/Keyboard2View.java b/srcs/juloo.keyboard2/Keyboard2View.java index b561eb4..ab3e36b 100644 --- a/srcs/juloo.keyboard2/Keyboard2View.java +++ b/srcs/juloo.keyboard2/Keyboard2View.java @@ -139,6 +139,13 @@ public class Keyboard2View extends View set_fake_ptr_latched(_compose_key, _compose_kv, pending, false); } + /** Called from [Keybard2.onUpdateSelection]. */ + public void set_selection_state(boolean selection_state) + { + set_fake_ptr_latched(KeyboardData.Key.EMPTY, + KeyValue.getKeyByName("selection_mode"), selection_state, true); + } + public KeyValue modifyKey(KeyValue k, Pointers.Modifiers mods) { return KeyModifier.modify(k, mods); diff --git a/srcs/juloo.keyboard2/KeyboardData.java b/srcs/juloo.keyboard2/KeyboardData.java index 9450c9e..fd111bf 100644 --- a/srcs/juloo.keyboard2/KeyboardData.java +++ b/srcs/juloo.keyboard2/KeyboardData.java @@ -422,6 +422,8 @@ public final class KeyboardData indication = i; } + static final Key EMPTY = new Key(new KeyValue[9], null, 0, 1.f, 1.f, null); + /** 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. */