From 98c1b8db82c0da8f49eb12d18c9001a57009eca5 Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Sun, 27 Jul 2025 22:09:45 +0200 Subject: Candidates view The `CandidatesView` is implemented as a `LinearLayout` that is divided horizontally with up to 3 `TextView`. It might in the future contain buttons on the sides. The candidate view is nested into the input view rather than using Android's `setCandidatesView` and callbacks as the API is unreliable and complicated. The first suggestion goes in the middle to be more accessible. The second suggestion goes on the right to be more accessible to the right-handed, because it must go somewhere. --- srcs/juloo.keyboard2/CandidatesView.java | 104 ++++++++++++++++++++++++++++++ srcs/juloo.keyboard2/Config.java | 3 + srcs/juloo.keyboard2/KeyEventHandler.java | 6 ++ srcs/juloo.keyboard2/Keyboard2.java | 30 +++++++-- srcs/juloo.keyboard2/Keyboard2View.java | 2 - srcs/juloo.keyboard2/Logs.java | 2 + 6 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 srcs/juloo.keyboard2/CandidatesView.java (limited to 'srcs/juloo.keyboard2') diff --git a/srcs/juloo.keyboard2/CandidatesView.java b/srcs/juloo.keyboard2/CandidatesView.java new file mode 100644 index 0000000..232ed23 --- /dev/null +++ b/srcs/juloo.keyboard2/CandidatesView.java @@ -0,0 +1,104 @@ +package juloo.keyboard2; + +import android.content.Context; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.LinearLayout; +import android.widget.TextView; +import java.util.ArrayList; +import java.util.List; + +public class CandidatesView extends LinearLayout +{ + static final int NUM_CANDIDATES = 3; + + Config _config; + + /** Candidates currently visible. Entries can be [null] when there are less + than [NUM_CANDIDATES] suggestions. */ + String[] _items = new String[NUM_CANDIDATES]; + + /** Text views showing the candidates in [_items]. Text views visibility is + set to [GONE] when there are less than [NUM_CANDIDATES] suggestions. */ + TextView[] _item_views = new TextView[NUM_CANDIDATES]; + + public CandidatesView(Context context, AttributeSet attrs) + { + super(context, attrs); + _config = Config.globalConfig(); + } + + @Override + protected void onFinishInflate() + { + super.onFinishInflate(); + setup_item_view(0, R.id.candidates_middle); + setup_item_view(1, R.id.candidates_right); + setup_item_view(2, R.id.candidates_left); + } + + public void set_candidates(List suggestions) + { + int s_count = suggestions.size(); + for (int i = 0; i < _item_views.length; i++) + { + TextView v = _item_views[i]; + if (i < s_count) + { + String it = suggestions.get(i); + _items[i] = it; + v.setText(it); + v.setVisibility(View.VISIBLE); + } + else + { + _items[i] = null; + v.setVisibility(View.GONE); + } + } + } + + private void setup_item_view(final int item_index, int item_id) + { + TextView v = (TextView)findViewById(item_id); + v.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View _v) + { + String it = _items[item_index]; + if (it != null) + _config.handler.suggestion_entered(it); + } + }); + v.setVisibility(View.GONE); + _item_views[item_index] = v; + } + + public static boolean should_show(EditorInfo info) + { + int variation = info.inputType & InputType.TYPE_MASK_VARIATION; + int flags = info.inputType & InputType.TYPE_MASK_FLAGS; + switch (info.inputType & InputType.TYPE_MASK_CLASS) + { + case InputType.TYPE_CLASS_TEXT: + switch (variation) + { + case InputType.TYPE_TEXT_VARIATION_PASSWORD: + case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD: + case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD: + return false; + default: + if ((flags & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) + return false; // Editor requested that we don't show suggestions + return true; + } + case InputType.TYPE_CLASS_NUMBER: + // Beware of TYPE_NUMBER_VARIATION_PASSWORD + return false; + default: return false; + } + } +} diff --git a/srcs/juloo.keyboard2/Config.java b/srcs/juloo.keyboard2/Config.java index 64e2d87..775c206 100644 --- a/srcs/juloo.keyboard2/Config.java +++ b/srcs/juloo.keyboard2/Config.java @@ -75,6 +75,7 @@ public final class Config // Dynamically set /** Configuration options implied by the connected editor. */ public EditorConfig editor_config; + public boolean should_show_candidates_view; public boolean shouldOfferVoiceTyping; public ExtraKeys extra_keys_subtype; public Map extra_keys_param; @@ -101,6 +102,7 @@ public final class Config // from prefs refresh(res, foldableUnfolded); // initialized later + should_show_candidates_view = false; shouldOfferVoiceTyping = false; extra_keys_subtype = null; handler = h; @@ -291,6 +293,7 @@ public final class Config public void key_down(KeyValue value, boolean is_swipe); public void key_up(KeyValue value, Pointers.Modifiers mods); public void mods_changed(Pointers.Modifiers mods); + public void suggestion_entered(String text); } /** Config migrations. */ diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index fc3a641..145acbe 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -110,6 +110,12 @@ public final class KeyEventHandler update_meta_state(mods); } + @Override + public void suggestion_entered(String text) + { + // TODO + } + @Override public void paste_from_clipboard_pane(String content) { diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java index 1efe0f5..c7d7e3d 100644 --- a/srcs/juloo.keyboard2/Keyboard2.java +++ b/srcs/juloo.keyboard2/Keyboard2.java @@ -29,7 +29,10 @@ import juloo.keyboard2.prefs.LayoutsPreference; public class Keyboard2 extends InputMethodService implements SharedPreferences.OnSharedPreferenceChangeListener { + /** The view containing the keyboard and candidates view. */ + private ViewGroup _container_view; private Keyboard2View _keyboardView; + private CandidatesView _candidates_view; private KeyEventHandler _keyeventhandler; /** If not 'null', the layout to use instead of [_config.current_layout]. */ private KeyboardData _currentSpecialLayout; @@ -115,9 +118,8 @@ public class Keyboard2 extends InputMethodService Config.initGlobalConfig(prefs, getResources(), _keyeventhandler, _foldStateTracker.isUnfolded()); prefs.registerOnSharedPreferenceChangeListener(this); _config = Config.globalConfig(); - _keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard); - _keyboardView.reset(); Logs.set_debug_logs(getResources().getBoolean(R.bool.debug_logs)); + create_keyboard_view(); ClipboardHistoryService.on_startup(this, _keyeventhandler); _foldStateTracker.setChangedCallback(() -> { refresh_config(); }); } @@ -129,6 +131,13 @@ public class Keyboard2 extends InputMethodService _foldStateTracker.close(); } + private void create_keyboard_view() + { + _container_view = (ViewGroup)inflate_view(R.layout.keyboard); + _keyboardView = (Keyboard2View)_container_view.findViewById(R.id.keyboard_view); + _candidates_view = (CandidatesView)_container_view.findViewById(R.id.candidates_view); + } + private List getEnabledSubtypes(InputMethodManager imm) { String pkg = getPackageName(); @@ -198,6 +207,13 @@ public class Keyboard2 extends InputMethodService _localeTextLayout = default_layout; } + private void refresh_candidates_view(EditorInfo info) + { + boolean should_show = CandidatesView.should_show(info); + _config.should_show_candidates_view = should_show; + _candidates_view.setVisibility(should_show ? View.VISIBLE : View.GONE); + } + /** Might re-create the keyboard view. [_keyboardView.setKeyboard()] and [setInputView()] must be called soon after. */ private void refresh_config() @@ -208,11 +224,13 @@ public class Keyboard2 extends InputMethodService // Refreshing the theme config requires re-creating the views if (prev_theme != _config.theme) { - _keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard); + create_keyboard_view(); _emojiPane = null; _clipboard_pane = null; - setInputView(_keyboardView); + setInputView(_container_view); } + // Set keyboard background opacity + _container_view.getBackground().setAlpha(_config.keyboardOpacity); _keyboardView.reset(); } @@ -234,10 +252,11 @@ public class Keyboard2 extends InputMethodService { _config.editor_config.refresh(info, getResources()); refresh_config(); + refresh_candidates_view(info); _currentSpecialLayout = refresh_special_layout(); _keyboardView.setKeyboard(current_layout()); _keyeventhandler.started(_config); - setInputView(_keyboardView); + setInputView(_container_view); Logs.debug_startup_input_view(info, _config); } @@ -252,7 +271,6 @@ public class Keyboard2 extends InputMethodService v.requestApplyInsets(); } - @Override public void updateFullscreenMode() { super.updateFullscreenMode(); diff --git a/srcs/juloo.keyboard2/Keyboard2View.java b/srcs/juloo.keyboard2/Keyboard2View.java index 58c689d..068ef67 100644 --- a/srcs/juloo.keyboard2/Keyboard2View.java +++ b/srcs/juloo.keyboard2/Keyboard2View.java @@ -344,8 +344,6 @@ public class Keyboard2View extends View @Override protected void onDraw(Canvas canvas) { - // Set keyboard background opacity - getBackground().setAlpha(_config.keyboardOpacity); float y = _tc.margin_top; for (KeyboardData.Row row : _keyboard.rows) { diff --git a/srcs/juloo.keyboard2/Logs.java b/srcs/juloo.keyboard2/Logs.java index 1bef51c..a956305 100644 --- a/srcs/juloo.keyboard2/Logs.java +++ b/srcs/juloo.keyboard2/Logs.java @@ -26,6 +26,8 @@ public final class Logs _debug_logs.println("swapEnterActionKey: " +conf.editor_config.swapEnterActionKey); _debug_logs.println("actionLabel: "+conf.editor_config.actionLabel); + _debug_logs.println("should_show_candidates_view: " + +conf.should_show_candidates_view); } public static void debug_config_migration(int from_version, int to_version) -- cgit v1.2.3 From f082fcdebc4f129cd262ee4a0a6b83d91fde72bb Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Sat, 16 Aug 2025 18:48:00 +0200 Subject: Track the currently typed word The `CurrentlyTypedWord` class tracks the word that is being typed. It's implemented on the same model as Autocapitalisation and avoid expensive IPC calls when possible. The `Suggestions` class is where the suggestion lookup should go. It currently just echoes the current word. --- srcs/juloo.keyboard2/CurrentlyTypedWord.java | 99 ++++++++++++++++++++++++++++ srcs/juloo.keyboard2/EditorConfig.java | 9 +++ srcs/juloo.keyboard2/KeyEventHandler.java | 25 +++++-- srcs/juloo.keyboard2/Keyboard2.java | 7 +- srcs/juloo.keyboard2/Suggestions.java | 27 ++++++++ 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 srcs/juloo.keyboard2/CurrentlyTypedWord.java create mode 100644 srcs/juloo.keyboard2/Suggestions.java (limited to 'srcs/juloo.keyboard2') diff --git a/srcs/juloo.keyboard2/CurrentlyTypedWord.java b/srcs/juloo.keyboard2/CurrentlyTypedWord.java new file mode 100644 index 0000000..eba0bb0 --- /dev/null +++ b/srcs/juloo.keyboard2/CurrentlyTypedWord.java @@ -0,0 +1,99 @@ +package juloo.keyboard2; + +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import java.util.List; + +/** Keep track of the word being typed. */ +public final class CurrentlyTypedWord +{ + InputConnection _ic = null; + Callback _callback; + + StringBuilder _w = new StringBuilder(); + boolean _enabled = false; + + /** The estimated cursor position. Used to avoid expensive IPC calls when the + typed word can be estimated locally with [typed]. When the cursor + position gets out of sync, the text before the cursor is queried again to + the editor. */ + int _cursor; + + int refresh_count = 0; + + public CurrentlyTypedWord(Callback cb) + { + _callback = cb; + } + + public void started(Config conf, InputConnection ic) + { + _ic = ic; + EditorConfig e = conf.editor_config; + refresh_current_word(e.initial_text_before_cursor, + e.initial_sel_start != e.initial_sel_end); + _cursor = e.initial_sel_start; + } + + public void typed(String s) + { + if (!_enabled) + return; + type_chars(s); + callback(); + } + + public void selection_updated(int oldSelStart, int newSelStart, int newSelEnd) + { + // Avoid the expensive [refresh_current_word] call when [typed] was called + // before. + if (!_enabled || newSelStart == _cursor) + return; + refresh_current_word(_ic.getTextBeforeCursor(10, 0), + newSelStart != newSelEnd); + _cursor = newSelStart; + } + + private void callback() + { + _callback.currently_typed_word(_w.toString() + refresh_count); + } + + /** Estimate the currently typed word after [chars] has been typed. */ + private void type_chars(String s) + { + int len = s.length(); + for (int i = 0; i < len;) + { + int c = s.codePointAt(i); + if (Character.isLetter(c)) + _w.appendCodePoint(c); + else + _w.setLength(0); + _cursor++; + i += Character.charCount(c); + } + } + + /** Set [_enabled]. */ + private void refresh_current_word(CharSequence text_before_cursor, boolean has_selection) + { + _w.setLength(0); + if (_ic == null || text_before_cursor == null) + { + _enabled = false; + return; + } + _enabled = true; + if (has_selection) + return; + refresh_count++; + type_chars(text_before_cursor.toString()); + callback(); + } + + public static interface Callback + { + public void currently_typed_word(String word); + } +} diff --git a/srcs/juloo.keyboard2/EditorConfig.java b/srcs/juloo.keyboard2/EditorConfig.java index d9f1ae9..bdfab3c 100644 --- a/srcs/juloo.keyboard2/EditorConfig.java +++ b/srcs/juloo.keyboard2/EditorConfig.java @@ -26,6 +26,11 @@ public final class EditorConfig // Whether caps state should be updated right away. public boolean caps_initially_updated = false; + /** CurrentlyTypedWord. */ + public CharSequence initial_text_before_cursor = null; + public int initial_sel_start; + public int initial_sel_end; + public EditorConfig() {} public void refresh(EditorInfo info, Resources res) @@ -66,6 +71,10 @@ public final class EditorConfig caps_mode = info.inputType & TextUtils.CAP_MODE_SENTENCES; caps_initially_enabled = (info.initialCapsMode != 0); caps_initially_updated = caps_should_update_state(info); + /* CurrentlyTypedWord */ + initial_text_before_cursor = info.getInitialTextBeforeCursor(10, 0); + initial_sel_start = info.initialSelStart; + initial_sel_end = info.initialSelEnd; } String actionLabel_of_imeAction(int action, Resources res) diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index 145acbe..336398c 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -12,10 +12,13 @@ import java.util.Iterator; public final class KeyEventHandler implements Config.IKeyEventHandler, - ClipboardHistoryService.ClipboardPasteCallback + ClipboardHistoryService.ClipboardPasteCallback, + CurrentlyTypedWord.Callback { IReceiver _recv; Autocapitalisation _autocap; + Suggestions _suggestions; + CurrentlyTypedWord _typedword; /** State of the system modifiers. It is updated whether a modifier is down or up and a corresponding key event is sent. */ Pointers.Modifiers _mods; @@ -33,20 +36,25 @@ public final class KeyEventHandler _autocap = new Autocapitalisation(recv.getHandler(), this.new Autocapitalisation_callback()); _mods = Pointers.Modifiers.EMPTY; + _suggestions = new Suggestions(recv); + _typedword = new CurrentlyTypedWord(this); } /** Editing just started. */ public void started(Config conf) { - _autocap.started(conf, _recv.getCurrentInputConnection()); + InputConnection ic = _recv.getCurrentInputConnection(); + _autocap.started(conf, ic); + _typedword.started(conf, ic); _move_cursor_force_fallback = conf.editor_config.should_move_cursor_force_fallback; } /** Selection has been updated. */ - public void selection_updated(int oldSelStart, int newSelStart) + public void selection_updated(int oldSelStart, int newSelStart, int newSelEnd) { _autocap.selection_updated(oldSelStart, newSelStart); + _typedword.selection_updated(oldSelStart, newSelStart, newSelEnd); } /** A key is being pressed. There will not necessarily be a corresponding @@ -122,6 +130,12 @@ public final class KeyEventHandler send_text(content); } + @Override + public void currently_typed_word(String word) + { + _suggestions.currently_typed_word(word); + } + /** Update [_mods] to be consistent with the [mods], sending key events if needed. */ void update_meta_state(Pointers.Modifiers mods) @@ -211,13 +225,14 @@ public final class KeyEventHandler _autocap.event_sent(eventCode, metaState); } - void send_text(CharSequence text) + void send_text(String text) { InputConnection conn = _recv.getCurrentInputConnection(); if (conn == null) return; conn.commitText(text, 1); _autocap.typed(text); + _typedword.typed(text); } /** See {!InputConnection.performContextMenuAction}. */ @@ -473,7 +488,7 @@ public final class KeyEventHandler return (conn.getSelectedText(0) != null); } - public static interface IReceiver + public static interface IReceiver extends Suggestions.Callback { public void handle_event_key(KeyValue.Event ev); public void set_shift_state(boolean state, boolean lock); diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java index c7d7e3d..a2940d5 100644 --- a/srcs/juloo.keyboard2/Keyboard2.java +++ b/srcs/juloo.keyboard2/Keyboard2.java @@ -346,7 +346,7 @@ public class Keyboard2 extends InputMethodService public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); - _keyeventhandler.selection_updated(oldSelStart, newSelStart); + _keyeventhandler.selection_updated(oldSelStart, newSelStart, newSelEnd); if ((oldSelStart == oldSelEnd) != (newSelStart == newSelEnd)) _keyboardView.set_selection_state(newSelStart != newSelEnd); } @@ -481,6 +481,11 @@ public class Keyboard2 extends InputMethodService { return _handler; } + + public void set_suggestions(List suggestions) + { + _candidates_view.set_candidates(suggestions); + } } private IBinder getConnectionToken() diff --git a/srcs/juloo.keyboard2/Suggestions.java b/srcs/juloo.keyboard2/Suggestions.java new file mode 100644 index 0000000..2009d9a --- /dev/null +++ b/srcs/juloo.keyboard2/Suggestions.java @@ -0,0 +1,27 @@ +package juloo.keyboard2; + +import java.util.List; +import java.util.Arrays; + +/** Keep track of the word being typed and provide suggestions for + [CandidatesView]. */ +public final class Suggestions +{ + Callback _callback; + + public Suggestions(Callback c) + { + _callback = c; + } + + public void currently_typed_word(String word) + { + // TODO + _callback.set_suggestions(Arrays.asList(word)); + } + + public static interface Callback + { + public void set_suggestions(List suggestions); + } +} -- cgit v1.2.3 From 2b60f94ead0f7ac6c1df0b8c60a8c2facd385167 Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Mon, 25 Aug 2025 01:40:21 +0200 Subject: Enter the suggestion when it's pressed The current word is replaced by the pressed suggestion. --- srcs/juloo.keyboard2/CurrentlyTypedWord.java | 10 ++++++---- srcs/juloo.keyboard2/KeyEventHandler.java | 15 ++++++++++++++- srcs/juloo.keyboard2/Suggestions.java | 13 +++++++++++-- 3 files changed, 31 insertions(+), 7 deletions(-) (limited to 'srcs/juloo.keyboard2') diff --git a/srcs/juloo.keyboard2/CurrentlyTypedWord.java b/srcs/juloo.keyboard2/CurrentlyTypedWord.java index eba0bb0..15ffd6c 100644 --- a/srcs/juloo.keyboard2/CurrentlyTypedWord.java +++ b/srcs/juloo.keyboard2/CurrentlyTypedWord.java @@ -19,13 +19,16 @@ public final class CurrentlyTypedWord the editor. */ int _cursor; - int refresh_count = 0; - public CurrentlyTypedWord(Callback cb) { _callback = cb; } + public String get() + { + return _w.toString(); + } + public void started(Config conf, InputConnection ic) { _ic = ic; @@ -56,7 +59,7 @@ public final class CurrentlyTypedWord private void callback() { - _callback.currently_typed_word(_w.toString() + refresh_count); + _callback.currently_typed_word(_w.toString()); } /** Estimate the currently typed word after [chars] has been typed. */ @@ -87,7 +90,6 @@ public final class CurrentlyTypedWord _enabled = true; if (has_selection) return; - refresh_count++; type_chars(text_before_cursor.toString()); callback(); } diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index 336398c..c6b1730 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -121,7 +121,7 @@ public final class KeyEventHandler @Override public void suggestion_entered(String text) { - // TODO + replace_text_before_cursor(_typedword.get().length(), text + " "); } @Override @@ -235,6 +235,19 @@ public final class KeyEventHandler _typedword.typed(text); } + void replace_text_before_cursor(int remove_length, String new_text) + { + InputConnection conn = _recv.getCurrentInputConnection(); + if (conn == null) + return; + conn.beginBatchEdit(); + conn.deleteSurroundingText(remove_length, 0); + conn.commitText(new_text, 1); + conn.endBatchEdit(); + _autocap.typed(new_text); + _typedword.typed(new_text); + } + /** See {!InputConnection.performContextMenuAction}. */ void send_context_menu_action(int id) { diff --git a/srcs/juloo.keyboard2/Suggestions.java b/srcs/juloo.keyboard2/Suggestions.java index 2009d9a..4c0c97a 100644 --- a/srcs/juloo.keyboard2/Suggestions.java +++ b/srcs/juloo.keyboard2/Suggestions.java @@ -16,10 +16,19 @@ public final class Suggestions public void currently_typed_word(String word) { - // TODO - _callback.set_suggestions(Arrays.asList(word)); + if (word.equals("")) + { + _callback.set_suggestions(NO_SUGGESTIONS); + } + else + { + // TODO + _callback.set_suggestions(Arrays.asList(word)); + } } + static final List NO_SUGGESTIONS = Arrays.asList(); + public static interface Callback { public void set_suggestions(List suggestions); -- cgit v1.2.3 From c4d802d873ee404863fa7376651ef59212d57a9f Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Thu, 2 Oct 2025 01:35:32 +0200 Subject: CurrentlyTypedWord: Handle selection When selection is entered, the current word is cleared. --- srcs/juloo.keyboard2/CurrentlyTypedWord.java | 35 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 12 deletions(-) (limited to 'srcs/juloo.keyboard2') diff --git a/srcs/juloo.keyboard2/CurrentlyTypedWord.java b/srcs/juloo.keyboard2/CurrentlyTypedWord.java index 15ffd6c..df7882b 100644 --- a/srcs/juloo.keyboard2/CurrentlyTypedWord.java +++ b/srcs/juloo.keyboard2/CurrentlyTypedWord.java @@ -12,6 +12,7 @@ public final class CurrentlyTypedWord StringBuilder _w = new StringBuilder(); boolean _enabled = false; + boolean _has_selection = false; /** The estimated cursor position. Used to avoid expensive IPC calls when the typed word can be estimated locally with [typed]. When the cursor @@ -32,16 +33,19 @@ public final class CurrentlyTypedWord public void started(Config conf, InputConnection ic) { _ic = ic; + _enabled = true; EditorConfig e = conf.editor_config; - refresh_current_word(e.initial_text_before_cursor, - e.initial_sel_start != e.initial_sel_end); + _has_selection = e.initial_sel_start != e.initial_sel_end; _cursor = e.initial_sel_start; + if (!_has_selection) + set_current_word(e.initial_text_before_cursor); } public void typed(String s) { if (!_enabled) return; + _has_selection = false; type_chars(s); callback(); } @@ -50,10 +54,11 @@ public final class CurrentlyTypedWord { // Avoid the expensive [refresh_current_word] call when [typed] was called // before. - if (!_enabled || newSelStart == _cursor) + boolean new_has_sel = newSelStart != newSelEnd; + if (!_enabled || (newSelStart == _cursor && new_has_sel == _has_selection)) return; - refresh_current_word(_ic.getTextBeforeCursor(10, 0), - newSelStart != newSelEnd); + _has_selection = new_has_sel; + refresh_current_word(); _cursor = newSelStart; } @@ -78,18 +83,24 @@ public final class CurrentlyTypedWord } } - /** Set [_enabled]. */ - private void refresh_current_word(CharSequence text_before_cursor, boolean has_selection) + /** Refresh the current word by immediately querying the editor. */ + private void refresh_current_word() { - _w.setLength(0); - if (_ic == null || text_before_cursor == null) + if (_has_selection) + set_current_word(""); + else + set_current_word(_ic.getTextBeforeCursor(10, 0)); + } + + /** Refresh the current word by immediately querying the editor. */ + private void set_current_word(CharSequence text_before_cursor) + { + if (text_before_cursor == null) { _enabled = false; return; } - _enabled = true; - if (has_selection) - return; + _w.setLength(0); type_chars(text_before_cursor.toString()); callback(); } -- cgit v1.2.3 From 2208079e489ef7652e28294130825d7451e01571 Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Thu, 2 Oct 2025 01:59:07 +0200 Subject: CurrentlyTypedWord: Handle key events Currently, refreshing the current word on each key event. Refreshing is done after a short delay, to ensure the editor has handled the event. --- srcs/juloo.keyboard2/CurrentlyTypedWord.java | 45 ++++++++++++++++++++++++---- srcs/juloo.keyboard2/KeyEventHandler.java | 8 +++-- 2 files changed, 46 insertions(+), 7 deletions(-) (limited to 'srcs/juloo.keyboard2') diff --git a/srcs/juloo.keyboard2/CurrentlyTypedWord.java b/srcs/juloo.keyboard2/CurrentlyTypedWord.java index df7882b..5a0733b 100644 --- a/srcs/juloo.keyboard2/CurrentlyTypedWord.java +++ b/srcs/juloo.keyboard2/CurrentlyTypedWord.java @@ -1,5 +1,7 @@ package juloo.keyboard2; +import android.os.Handler; +import android.view.KeyEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import java.util.List; @@ -8,11 +10,18 @@ import java.util.List; public final class CurrentlyTypedWord { InputConnection _ic = null; + Handler _handler; Callback _callback; + /** The currently typed word. */ StringBuilder _w = new StringBuilder(); + /** This can be disabled if the editor doesn't support looking at the text + before the cursor. */ boolean _enabled = false; + /** The current word is empty while the selection is ongoing. */ boolean _has_selection = false; + /** Used to avoid concurrent refreshes in [delayed_refresh()]. */ + boolean _refresh_pending = false; /** The estimated cursor position. Used to avoid expensive IPC calls when the typed word can be estimated locally with [typed]. When the cursor @@ -20,8 +29,9 @@ public final class CurrentlyTypedWord the editor. */ int _cursor; - public CurrentlyTypedWord(Callback cb) + public CurrentlyTypedWord(Handler h, Callback cb) { + _handler = h; _callback = cb; } @@ -62,13 +72,20 @@ public final class CurrentlyTypedWord _cursor = newSelStart; } - private void callback() + public void event_sent(int code, int meta) + { + if (!_enabled) + return; + delayed_refresh(); + } + + void callback() { _callback.currently_typed_word(_w.toString()); } /** Estimate the currently typed word after [chars] has been typed. */ - private void type_chars(String s) + void type_chars(String s) { int len = s.length(); for (int i = 0; i < len;) @@ -84,8 +101,9 @@ public final class CurrentlyTypedWord } /** Refresh the current word by immediately querying the editor. */ - private void refresh_current_word() + void refresh_current_word() { + _refresh_pending = false; if (_has_selection) set_current_word(""); else @@ -93,7 +111,7 @@ public final class CurrentlyTypedWord } /** Refresh the current word by immediately querying the editor. */ - private void set_current_word(CharSequence text_before_cursor) + void set_current_word(CharSequence text_before_cursor) { if (text_before_cursor == null) { @@ -105,6 +123,23 @@ public final class CurrentlyTypedWord callback(); } + /** Wait some time to let the editor finishes reacting to changes and call + [refresh_current_word]. */ + void delayed_refresh() + { + _refresh_pending = true; + _handler.postDelayed(delayed_refresh_run, 50); + } + + Runnable delayed_refresh_run = new Runnable() + { + public void run() + { + if (_refresh_pending) + refresh_current_word(); + } + }; + public static interface Callback { public void currently_typed_word(String word); diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index c6b1730..057033b 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -33,11 +33,12 @@ public final class KeyEventHandler public KeyEventHandler(IReceiver recv) { _recv = recv; - _autocap = new Autocapitalisation(recv.getHandler(), + Handler handler = recv.getHandler(); + _autocap = new Autocapitalisation(handler, this.new Autocapitalisation_callback()); _mods = Pointers.Modifiers.EMPTY; _suggestions = new Suggestions(recv); - _typedword = new CurrentlyTypedWord(this); + _typedword = new CurrentlyTypedWord(handler, this); } /** Editing just started. */ @@ -222,7 +223,10 @@ public final class KeyEventHandler metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); if (eventAction == KeyEvent.ACTION_UP) + { _autocap.event_sent(eventCode, metaState); + _typedword.event_sent(eventCode, metaState); + } } void send_text(String text) -- cgit v1.2.3 From bdb05bcef060ee2aefab7663bb5441ea1398dd95 Mon Sep 17 00:00:00 2001 From: Jules Aguillon Date: Sun, 23 Nov 2025 17:54:56 +0100 Subject: Fix CurrentlyTypedWord counting the first letter twice --- srcs/juloo.keyboard2/CurrentlyTypedWord.java | 4 +++- srcs/juloo.keyboard2/KeyEventHandler.java | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'srcs/juloo.keyboard2') diff --git a/srcs/juloo.keyboard2/CurrentlyTypedWord.java b/srcs/juloo.keyboard2/CurrentlyTypedWord.java index 5a0733b..87a1cf1 100644 --- a/srcs/juloo.keyboard2/CurrentlyTypedWord.java +++ b/srcs/juloo.keyboard2/CurrentlyTypedWord.java @@ -68,8 +68,8 @@ public final class CurrentlyTypedWord if (!_enabled || (newSelStart == _cursor && new_has_sel == _has_selection)) return; _has_selection = new_has_sel; - refresh_current_word(); _cursor = newSelStart; + refresh_current_word(); } public void event_sent(int code, int meta) @@ -119,7 +119,9 @@ public final class CurrentlyTypedWord return; } _w.setLength(0); + int saved_cursor = _cursor; type_chars(text_before_cursor.toString()); + _cursor = saved_cursor; callback(); } diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java index 057033b..17c7d7b 100644 --- a/srcs/juloo.keyboard2/KeyEventHandler.java +++ b/srcs/juloo.keyboard2/KeyEventHandler.java @@ -234,9 +234,9 @@ public final class KeyEventHandler InputConnection conn = _recv.getCurrentInputConnection(); if (conn == null) return; - conn.commitText(text, 1); _autocap.typed(text); _typedword.typed(text); + conn.commitText(text, 1); } void replace_text_before_cursor(int remove_length, String new_text) @@ -248,8 +248,6 @@ public final class KeyEventHandler conn.deleteSurroundingText(remove_length, 0); conn.commitText(new_text, 1); conn.endBatchEdit(); - _autocap.typed(new_text); - _typedword.typed(new_text); } /** See {!InputConnection.performContextMenuAction}. */ -- cgit v1.2.3