commit 8ba1124273be1373b9afbfcefbf2a4df2d6fec24
parent 5bcc196ebfd966a0d6479164d02e4a05d10b38fa
Author: lumidify <nobody@lumidify.org>
Date: Thu, 24 Aug 2023 23:18:32 +0200
Add mouse support to text entry
Diffstat:
5 files changed, 109 insertions(+), 5 deletions(-)
diff --git a/.ltk/ltk.cfg b/.ltk/ltk.cfg
@@ -43,6 +43,7 @@ bind-keypress expand-selection-left sym left mods shift
bind-keypress expand-selection-right sym right mods shift
bind-keypress selection-to-clipboard text c mods ctrl
bind-keypress paste-clipboard text v mods ctrl
+bind-keypress switch-selection-side text o mods alt
# default mapping (just to silence warnings)
[key-mapping]
diff --git a/src/entry.c b/src/entry.c
@@ -15,9 +15,10 @@
*/
/* FIXME: support RTL text! */
-/* FIXME: mouse actions, copy-paste, allow opening text in external program */
+/* FIXME: allow opening text in external program */
#include <stdio.h>
+#include <ctype.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
@@ -69,6 +70,7 @@ static void expand_selection_left(ltk_entry *entry, ltk_key_event *event);
static void expand_selection_right(ltk_entry *entry, ltk_key_event *event);
static void selection_to_primary(ltk_entry *entry, ltk_key_event *event);
static void selection_to_clipboard(ltk_entry *entry, ltk_key_event *event);
+static void switch_selection_side(ltk_entry *entry, ltk_key_event *event);
static void paste_primary(ltk_entry *entry, ltk_key_event *event);
static void paste_clipboard(ltk_entry *entry, ltk_key_event *event);
static void select_all(ltk_entry *entry, ltk_key_event *event);
@@ -97,6 +99,7 @@ static struct key_cb cb_map[] = {
{"select-all", &select_all},
{"selection-to-clipboard", &selection_to_clipboard},
{"selection-to-primary", &selection_to_primary},
+ {"switch-selection-side", &switch_selection_side},
};
struct keypress_cfg {
@@ -252,6 +255,7 @@ ltk_entry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect
ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y);
}
+/* FIXME: draw cursor in different color on selection side that will be expanded */
static void
ltk_entry_redraw_surface(ltk_entry *entry, ltk_surface *s) {
ltk_rect rect = entry->widget.lrect;
@@ -373,6 +377,7 @@ expand_selection(ltk_entry *entry, int dir) {
}
/* FIXME: different programs have different behaviors when they set the selection */
+/* FIXME: sometimes, it might be more useful to wipe the selection when sel_end == sel_start */
static void
selection_to_primary(ltk_entry *entry, ltk_key_event *event) {
(void)event;
@@ -394,6 +399,11 @@ selection_to_clipboard(ltk_entry *entry, ltk_key_event *event) {
txtbuf_appendn(clip, entry->text + entry->sel_start, entry->sel_end - entry->sel_start);
ltk_clipboard_set_clipboard_selection_owner(entry->widget.window->clipboard);
}
+static void
+switch_selection_side(ltk_entry *entry, ltk_key_event *event) {
+ (void)event;
+ entry->sel_side = !entry->sel_side;
+}
static void
paste_primary(ltk_entry *entry, ltk_key_event *event) {
@@ -579,22 +589,110 @@ ltk_entry_key_release(ltk_widget *self, ltk_key_event *event) {
static int
ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event) {
- (void)self; (void)event;
+ ltk_entry *e = (ltk_entry *)self;
+ int side = theme.border_width + theme.pad;
+ if (event->x < side || event->x > self->lrect.w - side ||
+ event->y < side || event->y > self->lrect.h - side) {
+ return 0;
+ }
+ if (event->button == LTK_BUTTONL) {
+ if (event->type == LTK_3BUTTONPRESS_EVENT) {
+ select_all(e, NULL);
+ } else if (event->type == LTK_2BUTTONPRESS_EVENT) {
+ /* FIXME: use proper unicode stuff */
+ /* Note: If pango is used to determine what a word is, maybe at least
+ allow a config option to revert to the naive behavior - I hate it
+ when word boundaries stop at punctuation because it's really
+ annoying to select URLs, etc. then. */
+ e->pos = ltk_text_line_xy_to_pos(e->tl, event->x - side + e->cur_offset, event->y - side, 0);
+ size_t cur = e->pos;
+ size_t left = 0, right = 0;
+ if (isspace(e->text[e->pos])) {
+ while (cur-- > 0) {
+ if (!isspace(e->text[cur])) {
+ left = cur + 1;
+ break;
+ }
+ }
+ for (cur = e->pos + 1; cur < e->len; cur++) {
+ if (!isspace(e->text[cur])) {
+ right = cur;
+ break;
+ } else if (cur == e->len - 1) {
+ right = cur + 1;
+ }
+ }
+ } else {
+ while (cur-- > 0) {
+ if (isspace(e->text[cur])) {
+ left = cur + 1;
+ break;
+ }
+ }
+ for (cur = e->pos + 1; cur < e->len; cur++) {
+ if (isspace(e->text[cur])) {
+ right = cur;
+ break;
+ } else if (cur == e->len - 1) {
+ right = cur + 1;
+ }
+ }
+ }
+ set_selection(e, left, right);
+ e->sel_side = 0;
+ } else if (event->type == LTK_BUTTONPRESS_EVENT) {
+ e->pos = ltk_text_line_xy_to_pos(e->tl, event->x - side + e->cur_offset, event->y - side, 1);
+ set_selection(e, e->pos, e->pos);
+ e->selecting = 1;
+ e->sel_side = 0;
+ }
+ } else if (event->button == LTK_BUTTONM) {
+ /* FIXME: configure if this should change the position or paste at the current position
+ (see behavior in ledit) */
+ wipe_selection(e);
+ e->pos = ltk_text_line_xy_to_pos(e->tl, event->x - side + e->cur_offset, event->y - side, 1);
+ paste_primary(e, NULL);
+ }
return 0;
}
static int
ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event) {
- (void)self; (void)event;
+ ltk_entry *e = (ltk_entry *)self;
+ if (event->button == LTK_BUTTONL) {
+ e->selecting = 0;
+ selection_to_primary(e, NULL);
+ }
return 0;
}
static int
ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event) {
- (void)self; (void)event;
+ ltk_entry *e = (ltk_entry *)self;
+ if (e->selecting) {
+ /* this occurs when something like deletion happens while text
+ is being selected (FIXME: a bit weird) */
+ if (e->sel_start == e->sel_end && e->pos != e->sel_start)
+ e->sel_start = e->sel_end = e->pos;
+ int side = theme.border_width + theme.pad;
+ size_t new = ltk_text_line_xy_to_pos(e->tl, event->x - side + e->cur_offset, event->y - side, 1);
+ size_t otherpos = e->sel_side == 1 ? e->sel_start : e->sel_end;
+ e->pos = new;
+ /* this takes care of moving the shown text when the mouse is
+ dragged to the right or left of the entry box */
+ ensure_cursor_shown(e);
+ if (new <= otherpos) {
+ set_selection(e, new, otherpos);
+ e->sel_side = 0;
+ } else if (otherpos < new) {
+ set_selection(e, otherpos, new);
+ e->sel_side = 1;
+ }
+ }
return 0;
}
+/* FIXME: set cursor */
static int
ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event) {
(void)self; (void)event;
@@ -625,6 +723,7 @@ ltk_entry_create(ltk_window *window, const char *id, char *text) {
entry->alloc = entry->len + 1;
entry->pos = entry->sel_start = entry->sel_end = 0;
entry->sel_side = 0;
+ entry->selecting = 0;
entry->widget.dirty = 1;
return entry;
diff --git a/src/entry.h b/src/entry.h
@@ -31,7 +31,8 @@ typedef struct {
int cur_offset;
/* 0 when side of selection that is expanded by cursor keys
is left, 1 when it is right */
- int sel_side;
+ char sel_side;
+ char selecting;
} ltk_entry;
int ltk_entry_ini_handler(ltk_window *window, const char *prop, const char *value);
diff --git a/src/event_xlib.c b/src/event_xlib.c
@@ -209,6 +209,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
next_event_valid = 1;
} else {
last_button_press[button] = xevent.xbutton.time;
+ was_2press[button] = 0;
}
*event = (ltk_event){.button = {
.type = LTK_BUTTONPRESS_EVENT,
@@ -273,6 +274,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
next_event_valid = 1;
} else {
last_button_release[button] = xevent.xbutton.time;
+ was_2release[button] = 0;
}
*event = (ltk_event){.button = {
.type = LTK_BUTTONRELEASE_EVENT,
diff --git a/src/ltkd.c b/src/ltkd.c
@@ -1173,6 +1173,7 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
tmp = tmp->parent;
}
if (old) {
+ old->state &= ~LTK_FOCUSED;
ltk_widget *cur = old;
while (cur) {
if (cur == common_parent)