abouttreesummaryrefslogcommitdiff
path: root/srcs
diff options
context:
space:
mode:
authorJules Aguillon2025-12-28 19:02:45 +0100
committerGitHub2025-12-28 19:02:45 +0100
commit4d4c22b4e21505000d92fa3b6c1f400379c51cd4 (patch)
tree0d8d7373575c4fc98cb342a4ab73f1192f700f4e /srcs
parentdfaf4dbb5766bf134cbf97d0516493e2256d2e5a (diff)
parentbdb05bcef060ee2aefab7663bb5441ea1398dd95 (diff)
downloadunexpected-keyboard-4d4c22b4e21505000d92fa3b6c1f400379c51cd4.tar.gz
unexpected-keyboard-4d4c22b4e21505000d92fa3b6c1f400379c51cd4.zip
Merge pull request #1150 from Julow/candidates_view
Candidates view
Diffstat (limited to 'srcs')
-rw-r--r--srcs/juloo.keyboard2/CandidatesView.java104
-rw-r--r--srcs/juloo.keyboard2/Config.java3
-rw-r--r--srcs/juloo.keyboard2/CurrentlyTypedWord.java149
-rw-r--r--srcs/juloo.keyboard2/EditorConfig.java9
-rw-r--r--srcs/juloo.keyboard2/KeyEventHandler.java50
-rw-r--r--srcs/juloo.keyboard2/Keyboard2.java37
-rw-r--r--srcs/juloo.keyboard2/Keyboard2View.java2
-rw-r--r--srcs/juloo.keyboard2/Logs.java2
-rw-r--r--srcs/juloo.keyboard2/Suggestions.java36
9 files changed, 376 insertions, 16 deletions
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<String> 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<KeyValue, KeyboardData.PreferredPos> 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/CurrentlyTypedWord.java b/srcs/juloo.keyboard2/CurrentlyTypedWord.java
new file mode 100644
index 0000000..87a1cf1
--- /dev/null
+++ b/srcs/juloo.keyboard2/CurrentlyTypedWord.java
@@ -0,0 +1,149 @@
+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;
+
+/** Keep track of the word being typed. */
+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
+ position gets out of sync, the text before the cursor is queried again to
+ the editor. */
+ int _cursor;
+
+ public CurrentlyTypedWord(Handler h, Callback cb)
+ {
+ _handler = h;
+ _callback = cb;
+ }
+
+ public String get()
+ {
+ return _w.toString();
+ }
+
+ public void started(Config conf, InputConnection ic)
+ {
+ _ic = ic;
+ _enabled = true;
+ EditorConfig e = conf.editor_config;
+ _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();
+ }
+
+ public void selection_updated(int oldSelStart, int newSelStart, int newSelEnd)
+ {
+ // Avoid the expensive [refresh_current_word] call when [typed] was called
+ // before.
+ boolean new_has_sel = newSelStart != newSelEnd;
+ if (!_enabled || (newSelStart == _cursor && new_has_sel == _has_selection))
+ return;
+ _has_selection = new_has_sel;
+ _cursor = newSelStart;
+ refresh_current_word();
+ }
+
+ 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. */
+ 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);
+ }
+ }
+
+ /** Refresh the current word by immediately querying the editor. */
+ void refresh_current_word()
+ {
+ _refresh_pending = false;
+ if (_has_selection)
+ set_current_word("");
+ else
+ set_current_word(_ic.getTextBeforeCursor(10, 0));
+ }
+
+ /** Refresh the current word by immediately querying the editor. */
+ void set_current_word(CharSequence text_before_cursor)
+ {
+ if (text_before_cursor == null)
+ {
+ _enabled = false;
+ return;
+ }
+ _w.setLength(0);
+ int saved_cursor = _cursor;
+ type_chars(text_before_cursor.toString());
+ _cursor = saved_cursor;
+ 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/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 fc3a641..17c7d7b 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;
@@ -30,23 +33,29 @@ 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(handler, 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
@@ -111,11 +120,23 @@ public final class KeyEventHandler
}
@Override
+ public void suggestion_entered(String text)
+ {
+ replace_text_before_cursor(_typedword.get().length(), text + " ");
+ }
+
+ @Override
public void paste_from_clipboard_pane(String content)
{
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)
@@ -202,16 +223,31 @@ 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(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);
+ conn.commitText(text, 1);
+ }
+
+ 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();
}
/** See {!InputConnection.performContextMenuAction}. */
@@ -467,7 +503,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 1efe0f5..a2940d5 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<InputMethodSubtype> 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();
@@ -328,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);
}
@@ -463,6 +481,11 @@ public class Keyboard2 extends InputMethodService
{
return _handler;
}
+
+ public void set_suggestions(List<String> suggestions)
+ {
+ _candidates_view.set_candidates(suggestions);
+ }
}
private IBinder getConnectionToken()
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)
diff --git a/srcs/juloo.keyboard2/Suggestions.java b/srcs/juloo.keyboard2/Suggestions.java
new file mode 100644
index 0000000..4c0c97a
--- /dev/null
+++ b/srcs/juloo.keyboard2/Suggestions.java
@@ -0,0 +1,36 @@
+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)
+ {
+ if (word.equals(""))
+ {
+ _callback.set_suggestions(NO_SUGGESTIONS);
+ }
+ else
+ {
+ // TODO
+ _callback.set_suggestions(Arrays.asList(word));
+ }
+ }
+
+ static final List<String> NO_SUGGESTIONS = Arrays.asList();
+
+ public static interface Callback
+ {
+ public void set_suggestions(List<String> suggestions);
+ }
+}