URI: 
       tAdd mouse support to text entry - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 8ba1124273be1373b9afbfcefbf2a4df2d6fec24
   DIR parent 5bcc196ebfd966a0d6479164d02e4a05d10b38fa
  HTML 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 +
         M src/entry.c                         |     107 +++++++++++++++++++++++++++++--
         M src/entry.h                         |       3 ++-
         M src/event_xlib.c                    |       2 ++
         M src/ltkd.c                          |       1 +
       
       5 files changed, 109 insertions(+), 5 deletions(-)
       ---
   DIR diff --git a/.ltk/ltk.cfg b/.ltk/ltk.cfg
       t@@ -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]
   DIR diff --git a/src/entry.c b/src/entry.c
       t@@ -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>
       t@@ -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);
       t@@ -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 {
       t@@ -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;
       t@@ -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;
       t@@ -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) {
       t@@ -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;
       t@@ -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;
   DIR diff --git a/src/entry.h b/src/entry.h
       t@@ -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);
   DIR diff --git a/src/event_xlib.c b/src/event_xlib.c
       t@@ -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,
       t@@ -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,
   DIR diff --git a/src/ltkd.c b/src/ltkd.c
       t@@ -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)