mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2025-06-22 18:53:23 +02:00
Selection mode (#913)
* Selection mode: Space to cancel the selection This adds the "selection mode", which is activated when text is selected in the text box. The selection mode is exited when the selection is cleared. While the selection mode is activated, the Space and Esc keys are modified into the "selection cancel" key, which remove the selection without changing the text. The space bar is otherwise easy to type by accident during a selection and causes the selected text to be deleted. * Selection mode: Move each ends of selection separately with slider When the selection mode is activated, the space bar sliders change how they affect the selection: - The left side of the slider moves the left position of the selection. To shrink the selection from the left side, the slider must be activated by sliding to the left, extending the selection temporarilly, then by sliding to the right. - The right side of the slider affects the right position if the selection.
This commit is contained in:
parent
9cfeb0f0c2
commit
60b01927ab
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -59,6 +59,7 @@ public final class KeyValue implements Comparable<KeyValue>
|
||||
BREVE,
|
||||
BAR,
|
||||
FN,
|
||||
SELECTION_MODE,
|
||||
} // Last is be applied first
|
||||
|
||||
public static enum Editing
|
||||
@ -77,6 +78,7 @@ public final class KeyValue implements Comparable<KeyValue>
|
||||
AUTOFILL,
|
||||
DELETE_WORD,
|
||||
FORWARD_DELETE_WORD,
|
||||
SELECTION_CANCEL,
|
||||
}
|
||||
|
||||
public static enum Placeholder
|
||||
@ -715,6 +717,9 @@ public final class KeyValue implements Comparable<KeyValue>
|
||||
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<KeyValue>
|
||||
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<KeyValue>
|
||||
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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user