[gnome-shell/wip/carlosg/osk-updates: 19/35] keyboard: Add 'delete' OSK key action
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/carlosg/osk-updates: 19/35] keyboard: Add 'delete' OSK key action
- Date: Wed, 29 Jun 2022 14:02:47 +0000 (UTC)
commit f60ba0f5fb9a8a0edbe8e44d347ebdef402a9cfa
Author: Carlos Garnacho <carlosg gnome org>
Date: Wed Apr 20 23:18:27 2022 +0200
keyboard: Add 'delete' OSK key action
This action will replace CLUTTER_KEY_Backspace emission for
the OSK backspace key. Following the available mockups, implement
different modes of operation:
- Single tap deletes a single character
- Long tap starts deleting characters one by one
- Longer tap switches to word-by-word deletion
This is made possible via the input method surrounding text,
inspecting the string to look the previous char/word position
backwards, and relies on IM focus providing enough context.
Since deleting text and getting surrounding text are both
async operations, we make one happen after the other, until
the button is released.
js/misc/inputMethod.js | 4 +++
js/ui/keyboard.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 102 insertions(+)
---
diff --git a/js/misc/inputMethod.js b/js/misc/inputMethod.js
index 361c08b6a8..354df963d9 100644
--- a/js/misc/inputMethod.js
+++ b/js/misc/inputMethod.js
@@ -295,4 +295,8 @@ var InputMethod = GObject.registerClass({
getSurroundingText() {
return [this._surroundingText, this._surroundingTextCursor];
}
+
+ hasPreedit() {
+ return this._preeditVisible && this._preeditStr !== '' && this._preeditStr !== null;
+ }
});
diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js
index f08a25adf7..87f4cf5c2b 100644
--- a/js/ui/keyboard.js
+++ b/js/ui/keyboard.js
@@ -1530,6 +1530,9 @@ var Keyboard = GObject.registerClass({
this._toggleEmoji();
} else if (key.action === 'modifier') {
this._toggleModifier(key.keyval);
+ } else if (key.action === 'delete') {
+ this._toggleDelete(true);
+ this._toggleDelete(false);
} else if (!this._longPressed && key.action === 'levelSwitch') {
this._setActiveLayer(key.level);
this._setLatched(
@@ -1552,6 +1555,12 @@ var Keyboard = GObject.registerClass({
}
}
+ if (key.action === 'delete') {
+ button.connect('long-press', () => {
+ this._toggleDelete(true);
+ });
+ }
+
if (key.action === 'modifier') {
let modifierKeys = this._modifierKeys[key.keyval] || [];
modifierKeys.push(button);
@@ -1565,6 +1574,95 @@ var Keyboard = GObject.registerClass({
}
}
+ _previousWordPosition(text, cursor) {
+ /* Skip word prior to cursor */
+ let pos = text.slice(0, cursor).lastIndexOf(' ');
+ if (pos < 0)
+ return 0;
+
+ /* Skip contiguous spaces */
+ for (; pos >= 0; pos--) {
+ if (text.charAt(pos) !== ' ')
+ return pos + 1;
+ }
+
+ return 0;
+ }
+
+ _toggleDelete(enabled) {
+ if (this._deleteEnabled === enabled)
+ return;
+
+ this._deleteEnabled = enabled;
+ this._timesDeleted = 0;
+
+ if (!Main.inputMethod.currentFocus || Main.inputMethod.hasPreedit()) {
+ /* If there is no IM focus or are in the middle of preedit,
+ * fallback to keypresses */
+ if (enabled)
+ this._keyboardController.keyvalPress(Clutter.KEY_BackSpace);
+ else
+ this._keyboardController.keyvalRelease(Clutter.KEY_BackSpace);
+ return;
+ }
+
+ if (enabled) {
+ let func = (text, cursor) => {
+ if (cursor === 0)
+ return;
+
+ let encoder = new TextEncoder();
+ let decoder = new TextDecoder();
+
+ /* Find cursor/anchor position in characters */
+ const cursorIdx = decoder.decode(encoder.encode(
+ text).slice(0, cursor)).length;
+ const anchorIdx = this._timesDeleted < 50
+ ? cursorIdx - 1
+ : this._previousWordPosition(text, cursor);
+ /* Now get offset from cursor */
+ const offset = anchorIdx - cursorIdx;
+
+ this._timesDeleted++;
+ Main.inputMethod.delete_surrounding(offset, Math.abs(offset));
+ };
+
+ this._surroundingUpdateId = Main.inputMethod.connect(
+ 'surrounding-text-updated', () => {
+ let [text, cursor] = Main.inputMethod.getSurroundingText();
+ if (this._timesDeleted === 0) {
+ func(text, cursor);
+ } else {
+ if (this._surroundingUpdateTimeoutId > 0) {
+ GLib.source_remove(this._surroundingUpdateTimeoutId);
+ this._surroundingUpdateTimeoutId = 0;
+ }
+ this._surroundingUpdateTimeoutId =
+ GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => {
+ func(text, cursor);
+ this._surroundingUpdateTimeoutId = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+ }
+ });
+
+ let [text, cursor] = Main.inputMethod.getSurroundingText();
+ if (text)
+ func(text, cursor);
+ else
+ Main.inputMethod.request_surrounding();
+ } else {
+ if (this._surroundingUpdateId > 0) {
+ Main.inputMethod.disconnect(this._surroundingUpdateId);
+ this._surroundingUpdateId = 0;
+ }
+ if (this._surroundingUpdateTimeoutId > 0) {
+ GLib.source_remove(this._surroundingUpdateTimeoutId);
+ this._surroundingUpdateTimeoutId = 0;
+ }
+ }
+ }
+
_setLatched(latched) {
this._latched = latched;
this._setCurrentLevelLatched(this._currentPage, this._latched);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]