ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

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:
M.ltk/ltk.cfg | 1+
Msrc/entry.c | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/entry.h | 3++-
Msrc/event_xlib.c | 2++
Msrc/ltkd.c | 1+
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)