URI:
       entry.c - ltk - GUI toolkit 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)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       entry.c (26602B)
       ---
            1 /*
            2  * Copyright (c) 2022-2026 lumidify <nobody@lumidify.org>
            3  *
            4  * Permission to use, copy, modify, and/or distribute this software for any
            5  * purpose with or without fee is hereby granted, provided that the above
            6  * copyright notice and this permission notice appear in all copies.
            7  *
            8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
            9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
           10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
           11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
           12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
           13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
           14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
           15  */
           16 
           17 /* FIXME: mouse actions for expanding selection (shift+click) */
           18 /* FIXME: cursors jump weirdly with bidi text
           19    (need to support strong/weak cursors in pango backend) */
           20 /* FIXME: set imspot - needs to be standardized so widgets don't all do their own thing */
           21 /* FIXME: some sort of width setting (setting a pixel width would be kind of ugly) */
           22 
           23 #include <ctype.h>
           24 #include <stdint.h>
           25 #include <string.h>
           26 
           27 #include "entry.h"
           28 #include "array.h"
           29 #include "clipboard.h"
           30 #include "color.h"
           31 #include "event.h"
           32 #include "eventdefs.h"
           33 #include "graphics.h"
           34 #include "ltk.h"
           35 #include "memory.h"
           36 #include "rect.h"
           37 #include "text.h"
           38 #include "txtbuf.h"
           39 #include "util.h"
           40 #include "widget.h"
           41 #include "config.h"
           42 #include "widget_internal.h"
           43 
           44 #define MAX_ENTRY_BORDER_WIDTH 10000
           45 #define MAX_ENTRY_PADDING 50000
           46 
           47 static void ltk_entry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
           48 static void ltk_entry_destroy(ltk_widget *self, int shallow);
           49 static void recalc_ideal_size(ltk_entry *entry);
           50 static void ltk_entry_recalc_ideal_size(ltk_widget *self);
           51 
           52 static int ltk_entry_key_press(ltk_widget *self, ltk_key_event *event);
           53 static int ltk_entry_key_release(ltk_widget *self, ltk_key_event *event);
           54 static int ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event);
           55 static int ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event);
           56 static int ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event);
           57 static int ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event);
           58 static int ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event);
           59 static void ltk_entry_cmd_return(ltk_widget *self, char *text, size_t len);
           60 
           61 /* FIXME: configure mouse actions, e.g. select-word-under-pointer, move-cursor-to-pointer */
           62 
           63 static int cursor_to_beginning(ltk_widget *self, ltk_key_event *event);
           64 static int cursor_to_end(ltk_widget *self, ltk_key_event *event);
           65 static int cursor_left(ltk_widget *self, ltk_key_event *event);
           66 static int cursor_right(ltk_widget *self, ltk_key_event *event);
           67 static int expand_selection_left(ltk_widget *self, ltk_key_event *event);
           68 static int expand_selection_right(ltk_widget *self, ltk_key_event *event);
           69 static int selection_to_primary(ltk_widget *self, ltk_key_event *event);
           70 static int selection_to_clipboard(ltk_widget *self, ltk_key_event *event);
           71 static int switch_selection_side(ltk_widget *self, ltk_key_event *event);
           72 static int paste_primary(ltk_widget *self, ltk_key_event *event);
           73 static int paste_clipboard(ltk_widget *self, ltk_key_event *event);
           74 static int select_all(ltk_widget *self, ltk_key_event *event);
           75 static int delete_char_backwards(ltk_widget *self, ltk_key_event *event);
           76 static int delete_char_forwards(ltk_widget *self, ltk_key_event *event);
           77 static int edit_external(ltk_widget *self, ltk_key_event *event);
           78 
           79 static void ensure_cursor_shown(ltk_entry *entry);
           80 static void insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor);
           81 
           82 static ltk_keybinding_cb cb_map[] = {
           83         {"cursor-left", &cursor_left},
           84         {"cursor-right", &cursor_right},
           85         {"cursor-to-beginning", &cursor_to_beginning},
           86         {"cursor-to-end", &cursor_to_end},
           87         {"delete-char-backwards", &delete_char_backwards},
           88         {"delete-char-forwards", &delete_char_forwards},
           89         {"edit-external", &edit_external},
           90         {"expand-selection-left", &expand_selection_left},
           91         {"expand-selection-right", &expand_selection_right},
           92         {"paste-clipboard", &paste_clipboard},
           93         {"paste-primary", &paste_primary},
           94         {"select-all", &select_all},
           95         {"selection-to-clipboard", &selection_to_clipboard},
           96         {"selection-to-primary", &selection_to_primary},
           97         {"switch-selection-side", &switch_selection_side},
           98 };
           99 
          100 /* FIXME: also support keyreleases */
          101 static ltk_array(keypress) *keypresses = NULL;
          102 
          103 void
          104 ltk_entry_get_keybinding_parseinfo(
          105         ltk_keybinding_cb **press_cbs_ret, size_t *press_len_ret,
          106         ltk_keybinding_cb **release_cbs_ret, size_t *release_len_ret,
          107         ltk_array(keypress) **presses_ret, ltk_array(keyrelease) **releases_ret
          108 ) {
          109         *press_cbs_ret = cb_map;
          110         *press_len_ret = LENGTH(cb_map);
          111         *release_cbs_ret = NULL;
          112         *release_len_ret = 0;
          113         if (!keypresses)
          114                 keypresses = ltk_array_create(keypress, 1);
          115         *presses_ret = keypresses;
          116         *releases_ret = NULL;
          117 }
          118 
          119 void
          120 ltk_entry_cleanup(void) {
          121         ltk_keypress_bindings_destroy(keypresses);
          122         keypresses = NULL;
          123 }
          124 
          125 static struct ltk_widget_vtable vtable = {
          126         .key_press = &ltk_entry_key_press,
          127         .key_release = &ltk_entry_key_release,
          128         .mouse_press = &ltk_entry_mouse_press,
          129         .mouse_release = &ltk_entry_mouse_release,
          130         .release = NULL,
          131         .motion_notify = &ltk_entry_motion_notify,
          132         .mouse_leave = &ltk_entry_mouse_leave,
          133         .mouse_enter = &ltk_entry_mouse_enter,
          134         .cmd_return = &ltk_entry_cmd_return,
          135         .change_state = NULL,
          136         .get_child_at_pos = NULL,
          137         .resize = NULL,
          138         .hide = NULL,
          139         .draw = &ltk_entry_draw,
          140         .destroy = &ltk_entry_destroy,
          141         .child_size_change = NULL,
          142         .remove_child = NULL,
          143         .recalc_ideal_size = &ltk_entry_recalc_ideal_size,
          144         .type = LTK_WIDGET_ENTRY,
          145         .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_NEEDS_KEYBOARD,
          146         .invalid_signal = LTK_ENTRY_SIGNAL_INVALID,
          147 };
          148 
          149 static struct {
          150         ltk_color *text_color;
          151         ltk_color *selection_color;
          152 
          153         ltk_color *border;
          154         ltk_color *fill;
          155 
          156         ltk_color *border_pressed;
          157         ltk_color *fill_pressed;
          158 
          159         ltk_color *border_hover;
          160         ltk_color *fill_hover;
          161 
          162         ltk_color *border_active;
          163         ltk_color *fill_active;
          164 
          165         ltk_color *border_disabled;
          166         ltk_color *fill_disabled;
          167 
          168         char *font;
          169         ltk_size border_width;
          170         ltk_size pad;
          171         ltk_size font_size;
          172 } theme;
          173 
          174 /* FIXME:
          175 need to distinguish between active and focused keybindings - entry binding for opening
          176 in external text editor should work no matter if active or focused */
          177 /* FIXME: mouse press also needs to set focused */
          178 static ltk_theme_parseinfo parseinfo[] = {
          179         {"border", THEME_COLOR, {.color = &theme.border}, {.color = "#339999"}, 0, 0, 0},
          180         {"border-hover", THEME_COLOR, {.color = &theme.border_hover}, {.color = "#339999"}, 0, 0, 0},
          181         {"border-active", THEME_COLOR, {.color = &theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
          182         {"border-disabled", THEME_COLOR, {.color = &theme.border_disabled}, {.color = "#339999"}, 0, 0, 0},
          183         {"border-pressed", THEME_COLOR, {.color = &theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
          184         {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#113355"}, 0, 0, 0},
          185         {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#113355"}, 0, 0, 0},
          186         {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#113355"}, 0, 0, 0},
          187         {"fill-disabled", THEME_COLOR, {.color = &theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
          188         {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0},
          189         {"border-width", THEME_SIZE, {.size = &theme.border_width}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_ENTRY_BORDER_WIDTH, 0},
          190         {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_ENTRY_PADDING, 0},
          191         {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
          192         {"selection-color", THEME_COLOR, {.color = &theme.selection_color}, {.color = "#000000"}, 0, 0, 0},
          193         {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
          194         {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
          195 };
          196 
          197 void
          198 ltk_entry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
          199         *p = parseinfo;
          200         *len = LENGTH(parseinfo);
          201 }
          202 
          203 /* FIXME: draw cursor in different color on selection side that will be expanded */
          204 static void
          205 ltk_entry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
          206         ltk_entry *entry = LTK_CAST_ENTRY(self);
          207         ltk_rect lrect = self->lrect;
          208         ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
          209         if (clip_final.w <= 0 || clip_final.h <= 0)
          210                 return;
          211 
          212         int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi);
          213         int pad = ltk_size_to_pixel(theme.pad, self->last_dpi);
          214         ltk_color *border = NULL, *fill = NULL;
          215         /* FIXME: HOVERACTIVE STATE */
          216         if (self->state & LTK_DISABLED) {
          217                 border = theme.border_disabled;
          218                 fill = theme.fill_disabled;
          219         } else if (self->state & LTK_PRESSED) {
          220                 border = theme.border_pressed;
          221                 fill = theme.fill_pressed;
          222         } else if (self->state & LTK_ACTIVE) {
          223                 border = theme.border_active;
          224                 fill = theme.fill_active;
          225         } else if (self->state & LTK_HOVER) {
          226                 border = theme.border_hover;
          227                 fill = theme.fill_hover;
          228         } else {
          229                 border = theme.border;
          230                 fill = theme.fill;
          231         }
          232         ltk_rect draw_rect = {x, y, lrect.w, lrect.h};
          233         ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
          234         ltk_surface_fill_rect(draw_surf, fill, draw_clip);
          235         if (bw > 0) {
          236                 ltk_surface_draw_border_clipped(
          237                         draw_surf, border, draw_rect, bw, LTK_BORDER_ALL, draw_clip
          238                 );
          239         }
          240 
          241         int text_w, text_h;
          242         ltk_text_line_get_size(entry->tl, &text_w, &text_h);
          243         /* FIXME: what if text_h > rect.h? */
          244         int x_offset = 0;
          245         if (text_w < lrect.w - 2 * (bw + pad) && ltk_text_line_get_softline_direction(entry->tl, 0) == LTK_TEXT_RTL)
          246                 x_offset = lrect.w - 2 * (bw + pad) - text_w;
          247         int text_x = x + bw + pad + x_offset;
          248         int text_y = y + (lrect.h - text_h) / 2;
          249         ltk_rect clip_rect = (ltk_rect){text_x, text_y, lrect.w - 2 * bw - 2 * pad, text_h};
          250         ltk_text_line_draw_clipped(entry->tl, draw_surf, theme.text_color, text_x - entry->cur_offset, text_y, clip_rect);
          251         if ((self->state & LTK_FOCUSED) && entry->sel_start == entry->sel_end) {
          252                 int cx, cy, cw, ch;
          253                 ltk_text_line_pos_to_rect(entry->tl, entry->pos, &cx, &cy, &cw, &ch);
          254                 ltk_rect line_rect = {cx - entry->cur_offset + text_x, cy + text_y, 1, ch};
          255                 /* FIXME: configure line width */
          256                 ltk_surface_fill_rect(
          257                         draw_surf, theme.text_color,
          258                         ltk_rect_intersect(draw_clip, line_rect)
          259                 );
          260         }
          261         self->dirty  = 0;
          262 }
          263 
          264 static size_t
          265 xy_to_pos(ltk_entry *e, int x, int y, int snap) {
          266         int bw = ltk_size_to_pixel(theme.border_width, LTK_CAST_WIDGET(e)->last_dpi);
          267         int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(e)->last_dpi);
          268         int side = bw + pad;
          269         int text_w, text_h;
          270         ltk_text_line_get_size(e->tl, &text_w, &text_h);
          271         if (text_w < e->widget.lrect.w - 2 * side && ltk_text_line_get_softline_direction(e->tl, 0) == LTK_TEXT_RTL)
          272                 x -= e->widget.lrect.w - 2 * side - text_w;
          273         return ltk_text_line_xy_to_pos(e->tl, x - side + e->cur_offset, y - side, snap);
          274 }
          275 
          276 static void
          277 set_selection(ltk_entry *entry, size_t start, size_t end) {
          278         ltk_widget *self = LTK_CAST_WIDGET(entry);
          279         entry->sel_start = start;
          280         entry->sel_end = end;
          281         ltk_text_line_clear_attrs(entry->tl);
          282         if (start != end)
          283                 ltk_text_line_add_attr_bg(entry->tl, entry->sel_start, entry->sel_end, theme.selection_color);
          284         self->dirty = 1;
          285         ltk_window_invalidate_widget_rect(self->window, self->id);
          286 }
          287 
          288 static void
          289 wipe_selection(ltk_entry *entry) {
          290         set_selection(entry, 0, 0);
          291 }
          292 
          293 static int
          294 cursor_to_beginning(ltk_widget *self, ltk_key_event *event) {
          295         (void)event;
          296         ltk_entry *entry = LTK_CAST_ENTRY(self);
          297         wipe_selection(entry);
          298         entry->pos = 0;
          299         ensure_cursor_shown(entry);
          300         return 1;
          301 }
          302 
          303 static int
          304 cursor_to_end(ltk_widget *self, ltk_key_event *event) {
          305         (void)event;
          306         ltk_entry *entry = LTK_CAST_ENTRY(self);
          307         wipe_selection(entry);
          308         entry->pos = entry->len;
          309         ensure_cursor_shown(entry);
          310         return 1;
          311 }
          312 
          313 static int
          314 cursor_left(ltk_widget *self, ltk_key_event *event) {
          315         (void)event;
          316         ltk_entry *entry = LTK_CAST_ENTRY(self);
          317         if (entry->sel_start != entry->sel_end)
          318                 entry->pos = entry->sel_start;
          319         else
          320                 entry->pos = ltk_text_line_move_cursor_visually(entry->tl, entry->pos, -1, NULL);
          321         wipe_selection(entry);
          322         ensure_cursor_shown(entry);
          323         return 1;
          324 }
          325 
          326 static int
          327 cursor_right(ltk_widget *self, ltk_key_event *event) {
          328         (void)event;
          329         ltk_entry *entry = LTK_CAST_ENTRY(self);
          330         if (entry->sel_start != entry->sel_end)
          331                 entry->pos = entry->sel_end;
          332         else
          333                 entry->pos = ltk_text_line_move_cursor_visually(entry->tl, entry->pos, 1, NULL);
          334         wipe_selection(entry);
          335         ensure_cursor_shown(entry);
          336         return 1;
          337 }
          338 
          339 static void
          340 expand_selection(ltk_entry *entry, int dir) {
          341         size_t pos = entry->pos;
          342         size_t otherpos = entry->pos;
          343         if (entry->sel_start != entry->sel_end) {
          344                 pos = entry->sel_side == 0 ? entry->sel_start : entry->sel_end;
          345                 otherpos = entry->sel_side == 1 ? entry->sel_start : entry->sel_end;
          346         }
          347         size_t new = ltk_text_line_move_cursor_visually(entry->tl, pos, dir, NULL);
          348         if (new < otherpos) {
          349                 set_selection(entry, new, otherpos);
          350                 entry->sel_side = 0;
          351         } else if (otherpos < new) {
          352                 set_selection(entry, otherpos, new);
          353                 entry->sel_side = 1;
          354         } else {
          355                 entry->pos = new;
          356                 wipe_selection(entry);
          357         }
          358         selection_to_primary(LTK_CAST_WIDGET(entry), NULL);
          359 }
          360 
          361 /* FIXME: different programs have different behaviors when they set the selection */
          362 /* FIXME: sometimes, it might be more useful to wipe the selection when sel_end == sel_start */
          363 static int
          364 selection_to_primary(ltk_widget *self, ltk_key_event *event) {
          365         (void)event;
          366         ltk_entry *entry = LTK_CAST_ENTRY(self);
          367         if (entry->sel_end == entry->sel_start)
          368                 return 1;
          369         txtbuf *primary = ltk_clipboard_get_primary_buffer(ltk_get_clipboard());
          370         txtbuf_clear(primary);
          371         txtbuf_appendn(primary, entry->text + entry->sel_start, entry->sel_end - entry->sel_start);
          372         ltk_clipboard_set_primary_selection_owner(ltk_get_clipboard());
          373         return 1;
          374 }
          375 
          376 static int
          377 selection_to_clipboard(ltk_widget *self, ltk_key_event *event) {
          378         (void)event;
          379         ltk_entry *entry = LTK_CAST_ENTRY(self);
          380         if (entry->sel_end == entry->sel_start)
          381                 return 1;
          382         txtbuf *clip = ltk_clipboard_get_clipboard_buffer(ltk_get_clipboard());
          383         txtbuf_clear(clip);
          384         txtbuf_appendn(clip, entry->text + entry->sel_start, entry->sel_end - entry->sel_start);
          385         ltk_clipboard_set_clipboard_selection_owner(ltk_get_clipboard());
          386         return 1;
          387 }
          388 
          389 static int
          390 switch_selection_side(ltk_widget *self, ltk_key_event *event) {
          391         (void)event;
          392         ltk_entry *entry = LTK_CAST_ENTRY(self);
          393         entry->sel_side = !entry->sel_side;
          394         return 1;
          395 }
          396 
          397 static int
          398 paste_primary(ltk_widget *self, ltk_key_event *event) {
          399         (void)event;
          400         ltk_entry *entry = LTK_CAST_ENTRY(self);
          401         txtbuf *buf = ltk_clipboard_get_primary_text(ltk_get_clipboard());
          402         if (buf)
          403                 insert_text(entry, buf->text, buf->len, 1);
          404         return 1;
          405 }
          406 
          407 static int
          408 paste_clipboard(ltk_widget *self, ltk_key_event *event) {
          409         (void)event;
          410         ltk_entry *entry = LTK_CAST_ENTRY(self);
          411         txtbuf *buf = ltk_clipboard_get_clipboard_text(ltk_get_clipboard());
          412         if (buf)
          413                 insert_text(entry, buf->text, buf->len, 1);
          414         return 1;
          415 }
          416 
          417 static int
          418 expand_selection_left(ltk_widget *self, ltk_key_event *event) {
          419         (void)event;
          420         ltk_entry *entry = LTK_CAST_ENTRY(self);
          421         expand_selection(entry, -1);
          422         return 1;
          423 }
          424 
          425 static int
          426 expand_selection_right(ltk_widget *self, ltk_key_event *event) {
          427         (void)event;
          428         ltk_entry *entry = LTK_CAST_ENTRY(self);
          429         expand_selection(entry, 1);
          430         return 1;
          431 }
          432 
          433 static void
          434 delete_text(ltk_entry *entry, size_t start, size_t end) {
          435         ltk_widget *self = LTK_CAST_WIDGET(entry);
          436         memmove(entry->text + start, entry->text + end, entry->len - end);
          437         entry->len -= end - start;
          438         entry->text[entry->len] = '\0';
          439         entry->pos = start;
          440         wipe_selection(entry);
          441         ltk_text_line_set_text(entry->tl, entry->text, 0);
          442         recalc_ideal_size(entry);
          443         ensure_cursor_shown(entry);
          444         self->dirty = 1;
          445         ltk_window_invalidate_widget_rect(self->window, self->id);
          446 }
          447 
          448 static int
          449 delete_char_backwards(ltk_widget *self, ltk_key_event *event) {
          450         (void)event;
          451         ltk_entry *entry = LTK_CAST_ENTRY(self);
          452         if (entry->sel_start != entry->sel_end) {
          453                 delete_text(entry, entry->sel_start, entry->sel_end);
          454         } else {
          455                 size_t new = prev_utf8(entry->text, entry->pos);
          456                 delete_text(entry, new, entry->pos);
          457         }
          458         return 1;
          459 }
          460 
          461 static int
          462 delete_char_forwards(ltk_widget *self, ltk_key_event *event) {
          463         (void)event;
          464         ltk_entry *entry = LTK_CAST_ENTRY(self);
          465         if (entry->sel_start != entry->sel_end) {
          466                 delete_text(entry, entry->sel_start, entry->sel_end);
          467         } else {
          468                 size_t new = next_utf8(entry->text, entry->len, entry->pos);
          469                 delete_text(entry, entry->pos, new);
          470         }
          471         return 1;
          472 }
          473 
          474 static int
          475 select_all(ltk_widget *self, ltk_key_event *event) {
          476         (void)event;
          477         ltk_entry *entry = LTK_CAST_ENTRY(self);
          478         set_selection(entry, 0, entry->len);
          479         if (entry->len)
          480                 selection_to_primary(LTK_CAST_WIDGET(entry), NULL);
          481         entry->sel_side = 0;
          482         return 1;
          483 }
          484 
          485 static void
          486 recalc_ideal_size_with_notification(ltk_entry *entry) {
          487         ltk_widget *self = LTK_CAST_WIDGET(entry);
          488         /* FIXME: need to react to resize and adjust cur_offset */
          489         unsigned int old_w = self->ideal_w;
          490         unsigned int old_h = self->ideal_h;
          491         recalc_ideal_size(entry);
          492         if (old_w != self->ideal_w || old_h != self->ideal_h) {
          493                 ltk_widget *parent = ltk_get_widget_or_null_from_id(self->parent);
          494                 if (parent && parent->vtable->child_size_change)
          495                         parent->vtable->child_size_change(parent, self->id);
          496         }
          497 }
          498 
          499 static void
          500 ensure_cursor_shown(ltk_entry *entry) {
          501         ltk_widget *self = LTK_CAST_WIDGET(entry);
          502         int x, y, w, h;
          503         ltk_text_line_pos_to_rect(entry->tl, entry->pos, &x, &y, &w, &h);
          504         /* FIXME: test if anything weird can happen since resize is called by parent->child_size_change,
          505            and then the stuff on the next few lines is done afterwards */
          506         /* FIXME: adjustable cursor width */
          507         int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi);
          508         int pad = ltk_size_to_pixel(theme.pad, self->last_dpi);
          509         int text_w = self->lrect.w - 2 * bw - 2 * pad;
          510         if (x + 1 > text_w + entry->cur_offset) {
          511                 entry->cur_offset = x - text_w + 1;
          512                 self->dirty = 1;
          513                 ltk_window_invalidate_widget_rect(self->window, self->id);
          514         } else if (x < entry->cur_offset) {
          515                 entry->cur_offset = x;
          516                 self->dirty = 1;
          517                 ltk_window_invalidate_widget_rect(self->window, self->id);
          518         }
          519 }
          520 
          521 /* FIXME: maybe make this a regular key binding with wildcard text like in ledit? */
          522 static void
          523 insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor) {
          524         ltk_widget *self = LTK_CAST_WIDGET(entry);
          525         size_t num = 0;
          526         /* FIXME: this is ugly and there are probably a lot of other
          527            cases that need to be handled */
          528         /* FIXME: Just ignoring newlines is weird, but what other option is there? */
          529         for (size_t i = 0; i < len; i++) {
          530                 if (text[i] == '\n' || text[i] == '\r')
          531                         num++;
          532         }
          533         size_t reallen = len - num;
          534         size_t new_alloc = ideal_array_size(entry->alloc, entry->len + reallen + 1 - (entry->sel_end - entry->sel_start));
          535         if (new_alloc != entry->alloc) {
          536                 entry->text = ltk_realloc(entry->text, new_alloc);
          537                 entry->alloc = new_alloc;
          538         }
          539         /* FIXME: also need to reset selecting status once mouse selections are supported */
          540         if (entry->sel_start != entry->sel_end) {
          541                 entry->pos = entry->sel_start;
          542                 memmove(entry->text + entry->pos + reallen, entry->text + entry->sel_end, entry->len - entry->sel_end);
          543                 entry->len = entry->len + reallen - (entry->sel_end - entry->sel_start);
          544                 wipe_selection(entry);
          545         } else {
          546                 memmove(entry->text + entry->pos + reallen, entry->text + entry->pos, entry->len - entry->pos);
          547                 entry->len += reallen;
          548         }
          549         for (size_t i = 0, j = entry->pos; i < len; i++) {
          550                 if (text[i] != '\n' && text[i] != '\r')
          551                         entry->text[j++] = text[i];
          552         }
          553         if (move_cursor)
          554                 entry->pos += reallen;
          555         entry->text[entry->len] = '\0';
          556         ltk_text_line_set_text(entry->tl, entry->text, 0);
          557         recalc_ideal_size_with_notification(entry);
          558         ensure_cursor_shown(entry);
          559         self->dirty = 1;
          560         ltk_window_invalidate_widget_rect(self->window, self->id);
          561 }
          562 
          563 static void
          564 ltk_entry_cmd_return(ltk_widget *self, char *text, size_t len) {
          565         ltk_entry *e = LTK_CAST_ENTRY(self);
          566         wipe_selection(e);
          567         e->len = e->pos = 0;
          568         insert_text(e, text, len, 0);
          569 }
          570 
          571 static int
          572 edit_external(ltk_widget *self, ltk_key_event *event) {
          573         (void)event;
          574         ltk_entry *entry = LTK_CAST_ENTRY(self);
          575         ltk_general_config *config = ltk_config_get_general();
          576         /* FIXME: allow arguments to key mappings - this would allow to have different key mappings
          577            for different editors instead of just one command */
          578         if (!config->line_editor) {
          579                 ltk_warn("Unable to run external editing command: line editor not configured\n");
          580         } else {
          581                 /* FIXME: somehow show that there was an error if this returns 1? */
          582                 /* FIXME: change interface to not require length of cmd */
          583                 ltk_call_cmd(self->id, config->line_editor, entry->text, entry->len);
          584         }
          585         return 1;
          586 }
          587 
          588 static int
          589 ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) {
          590         ltk_entry *entry = LTK_CAST_ENTRY(self);
          591         if (ltk_widget_handle_keypress_bindings(self, event, keypresses, 0)) {
          592                 self->dirty = 1;
          593                 ltk_window_invalidate_widget_rect(self->window, self->id);
          594                 return 1;
          595         }
          596         if (event->text && (event->modmask & (LTK_MOD_CTRL | LTK_MOD_ALT | LTK_MOD_SUPER)) == 0) {
          597                 /* FIXME: properly handle everything */
          598                 if (event->text[0] == '\n' || event->text[0] == '\r' || event->text[0] == 0x1b)
          599                         return 0;
          600                 insert_text(entry, event->text, strlen(event->text), 1);
          601                 return 1;
          602         }
          603         return 0;
          604 }
          605 
          606 static int
          607 ltk_entry_key_release(ltk_widget *self, ltk_key_event *event) {
          608         (void)self; (void)event;
          609         return 0;
          610 }
          611 
          612 static int
          613 ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event) {
          614         ltk_entry *e = LTK_CAST_ENTRY(self);
          615         int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi);
          616         int pad = ltk_size_to_pixel(theme.pad, self->last_dpi);
          617         int side = bw + pad;
          618         if (event->x < side || event->x > self->lrect.w - side ||
          619             event->y < side || event->y > self->lrect.h - side) {
          620                 return 0;
          621         }
          622         if (event->button == LTK_BUTTONL) {
          623                 if (event->type == LTK_3BUTTONPRESS_EVENT) {
          624                         select_all(LTK_CAST_WIDGET(e), NULL);
          625                 } else if (event->type == LTK_2BUTTONPRESS_EVENT) {
          626                         /* FIXME: use proper unicode stuff */
          627                         /* Note: If pango is used to determine what a word is, maybe at least
          628                            allow a config option to revert to the naive behavior - I hate it
          629                            when word boundaries stop at punctuation because it's really
          630                            annoying to select URLs, etc. then. */
          631                         e->pos = xy_to_pos(e, event->x, event->y, 0);
          632                         size_t cur = e->pos;
          633                         size_t left = 0, right = 0;
          634                         if (isspace(e->text[e->pos])) {
          635                                 while (cur-- > 0) {
          636                                         if (!isspace(e->text[cur])) {
          637                                                 left = cur + 1;
          638                                                 break;
          639                                         }
          640                                 }
          641                                 for (cur = e->pos + 1; cur < e->len; cur++) {
          642                                         if (!isspace(e->text[cur])) {
          643                                                 right = cur;
          644                                                 break;
          645                                         } else if (cur == e->len - 1) {
          646                                                 right = cur + 1;
          647                                         }
          648                                 }
          649                         } else {
          650                                 while (cur-- > 0) {
          651                                         if (isspace(e->text[cur])) {
          652                                                 left = cur + 1;
          653                                                 break;
          654                                         }
          655                                 }
          656                                 for (cur = e->pos + 1; cur < e->len; cur++) {
          657                                         if (isspace(e->text[cur])) {
          658                                                 right = cur;
          659                                                 break;
          660                                         } else if (cur == e->len - 1) {
          661                                                 right = cur + 1;
          662                                         }
          663                                 }
          664                         }
          665                         set_selection(e, left, right);
          666                         e->sel_side = 0;
          667                 } else if (event->type == LTK_BUTTONPRESS_EVENT) {
          668                         e->pos = xy_to_pos(e, event->x, event->y, 1);
          669                         set_selection(e, e->pos, e->pos);
          670                         e->selecting = 1;
          671                         e->sel_side = 0;
          672                 }
          673         } else if (event->button == LTK_BUTTONM) {
          674                 /* FIXME: configure if this should change the position or paste at the current position
          675                    (see behavior in ledit) */
          676                 wipe_selection(e);
          677                 e->pos = xy_to_pos(e, event->x, event->y, 1);
          678                 paste_primary(LTK_CAST_WIDGET(e), NULL);
          679         }
          680         return 0;
          681 }
          682 
          683 static int
          684 ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event) {
          685         ltk_entry *e = LTK_CAST_ENTRY(self);
          686         if (event->button == LTK_BUTTONL) {
          687                 e->selecting = 0;
          688                 selection_to_primary(LTK_CAST_WIDGET(e), NULL);
          689         }
          690         return 0;
          691 }
          692 
          693 static int
          694 ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event) {
          695         ltk_entry *e = LTK_CAST_ENTRY(self);
          696         if (e->selecting) {
          697                 /* this occurs when something like deletion happens while text
          698                    is being selected (FIXME: a bit weird) */
          699                 if (e->sel_start == e->sel_end && e->pos != e->sel_start)
          700                         e->sel_start = e->sel_end = e->pos;
          701                 size_t new = xy_to_pos(e, event->x, event->y, 1);
          702                 size_t otherpos = e->sel_side == 1 ? e->sel_start : e->sel_end;
          703                 e->pos = new;
          704                 /* this takes care of moving the shown text when the mouse is
          705                    dragged to the right or left of the entry box */
          706                 ensure_cursor_shown(e);
          707                 if (new <= otherpos) {
          708                         set_selection(e, new, otherpos);
          709                         e->sel_side = 0;
          710                 } else if (otherpos < new) {
          711                         set_selection(e, otherpos, new);
          712                         e->sel_side = 1;
          713                 }
          714         }
          715         return 0;
          716 }
          717 
          718 /* FIXME: set cursor */
          719 static int
          720 ltk_entry_mouse_enter(ltk_widget *self, ltk_motion_event *event) {
          721         (void)self; (void)event;
          722         return 0;
          723 }
          724 
          725 static int
          726 ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event) {
          727         (void)self; (void)event;
          728         return 0;
          729 }
          730 
          731 static void
          732 recalc_ideal_size(ltk_entry *entry) {
          733         ltk_widget *self = LTK_CAST_WIDGET(entry);
          734         int text_w, text_h;
          735         ltk_text_line_get_size(entry->tl, &text_w, &text_h);
          736         int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi);
          737         int pad = ltk_size_to_pixel(theme.pad, self->last_dpi);
          738         self->ideal_w = text_w + bw * 2 + pad * 2;
          739         self->ideal_h = text_h + bw * 2 + pad * 2;
          740 }
          741 
          742 static void
          743 ltk_entry_recalc_ideal_size(ltk_widget *self) {
          744         ltk_entry *entry = LTK_CAST_ENTRY(self);
          745         int font_size = ltk_size_to_pixel(theme.font_size, self->last_dpi);
          746         ltk_text_line_set_font_size(entry->tl, font_size);
          747         recalc_ideal_size(entry);
          748 }
          749 
          750 ltk_widget_id
          751 ltk_entry_create(ltk_widget_id windowid, const char *text) {
          752         ltk_entry *entry = ltk_malloc(sizeof(ltk_entry));
          753         ltk_widget *self = LTK_CAST_WIDGET(entry);
          754         ltk_widget_id id = ltk_initialize_widget(self, windowid, &vtable, 0, 0);
          755 
          756         entry->tl = ltk_text_line_create_const_text_default(
          757                 theme.font, ltk_size_to_pixel(theme.font_size, self->last_dpi), text, -1
          758         );
          759         recalc_ideal_size(entry);
          760 
          761         entry->cur_offset = 0;
          762         entry->text = ltk_strdup(text);
          763         entry->len = strlen(text);
          764         entry->alloc = entry->len + 1;
          765         entry->pos = entry->sel_start = entry->sel_end = 0;
          766         entry->sel_side = 0;
          767         entry->selecting = 0;
          768         self->dirty = 1;
          769 
          770         return id;
          771 }
          772 
          773 static void
          774 ltk_entry_destroy(ltk_widget *self, int shallow) {
          775         (void)shallow;
          776         ltk_entry *entry = LTK_CAST_ENTRY(self);
          777         ltk_free(entry->text);
          778         ltk_text_line_destroy(entry->tl);
          779         ltk_free(entry);
          780 }