URI: 
       twidget.c - 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
       ---
       twidget.c (40866B)
       ---
            1 /*
            2  * Copyright (c) 2021, 2022 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 #include <stdarg.h>
           18 #include <stdint.h>
           19 
           20 #include "event.h"
           21 #include "rect.h"
           22 #include "widget.h"
           23 #include "color.h"
           24 #include "ltk.h"
           25 #include "memory.h"
           26 #include "util.h"
           27 #include "khash.h"
           28 #include "surface_cache.h"
           29 #include "config.h"
           30 #include "array.h"
           31 #include "keys.h"
           32 
           33 static int cb_focus_active(ltk_window *window, ltk_key_event *event, int handled);
           34 static int cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled);
           35 static int cb_move_prev(ltk_window *window, ltk_key_event *event, int handled);
           36 static int cb_move_next(ltk_window *window, ltk_key_event *event, int handled);
           37 static int cb_move_left(ltk_window *window, ltk_key_event *event, int handled);
           38 static int cb_move_right(ltk_window *window, ltk_key_event *event, int handled);
           39 static int cb_move_up(ltk_window *window, ltk_key_event *event, int handled);
           40 static int cb_move_down(ltk_window *window, ltk_key_event *event, int handled);
           41 static int cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled);
           42 static int cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled);
           43 static int cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled);
           44 
           45 struct key_cb {
           46         char *func_name;
           47         int (*callback)(ltk_window *, ltk_key_event *, int handled);
           48 };
           49 
           50 static struct key_cb cb_map[] = {
           51         {"focus-active", &cb_focus_active},
           52         {"move-down", &cb_move_down},
           53         {"move-left", &cb_move_left},
           54         {"move-next", &cb_move_next},
           55         {"move-prev", &cb_move_prev},
           56         {"move-right", &cb_move_right},
           57         {"move-up", &cb_move_up},
           58         {"remove-popups", &cb_remove_popups},
           59         {"set-pressed", &cb_set_pressed},
           60         {"unfocus-active", &cb_unfocus_active},
           61         {"unset-pressed", &cb_unset_pressed},
           62 };
           63 
           64 
           65 struct keypress_cfg {
           66         ltk_keypress_binding b;
           67         struct key_cb cb;
           68 };
           69 
           70 struct keyrelease_cfg {
           71         ltk_keyrelease_binding b;
           72         struct key_cb cb;
           73 };
           74 
           75 LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg)
           76 LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg)
           77 LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg)
           78 LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg)
           79 
           80 static ltk_array(keypress) *keypresses = NULL;
           81 static ltk_array(keyrelease) *keyreleases = NULL;
           82 
           83 GEN_CB_MAP_HELPERS(cb_map, struct key_cb, func_name)
           84 
           85 /* FIXME: most of this is duplicated code */
           86 /* FIXME: document that pointers inside binding are taken over! */
           87 int ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
           88 int ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
           89 
           90 int
           91 ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) {
           92         if (!keypresses)
           93                 keypresses = ltk_array_create(keypress, 1);
           94         struct key_cb *cb = cb_map_get_entry(func_name, func_len);
           95         if (!cb)
           96                 return 1;
           97         struct keypress_cfg cfg = {b, *cb};
           98         ltk_array_append(keypress, keypresses, cfg);
           99         return 0;
          100 }
          101 
          102 int
          103 ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) {
          104         if (!keyreleases)
          105                 keyreleases = ltk_array_create(keyrelease, 1);
          106         struct key_cb *cb = cb_map_get_entry(func_name, func_len);
          107         if (!cb)
          108                 return 1;
          109         struct keyrelease_cfg cfg = {b, *cb};
          110         ltk_array_append(keyrelease, keyreleases, cfg);
          111         return 0;
          112 }
          113 
          114 static void
          115 destroy_keypress_cfg(struct keypress_cfg cfg) {
          116         ltk_keypress_binding_destroy(cfg.b);
          117 }
          118 
          119 void
          120 ltk_widget_cleanup(void) {
          121         ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg);
          122         ltk_array_destroy(keyrelease, keyreleases);
          123         keypresses = NULL;
          124         keyreleases = NULL;
          125 }
          126 
          127 static void ltk_destroy_widget_hash(void);
          128 
          129 KHASH_MAP_INIT_STR(widget, ltk_widget *)
          130 static khash_t(widget) *widget_hash = NULL;
          131 /* Hack to make ltk_destroy_widget_hash work */
          132 /* FIXME: any better way to do this? */
          133 static int hash_locked = 0;
          134 
          135 /* needed for passing keyboard events down the hierarchy */
          136 static ltk_widget **widget_stack = NULL;
          137 static size_t widget_stack_alloc = 0;
          138 static size_t widget_stack_len = 0;
          139 
          140 static void
          141 ltk_destroy_widget_hash(void) {
          142         hash_locked = 1;
          143         khint_t k;
          144         ltk_widget *ptr;
          145         ltk_error err;
          146         for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
          147                 if (kh_exist(widget_hash, k)) {
          148                         ptr = kh_value(widget_hash, k);
          149                         ltk_free((char *)kh_key(widget_hash, k));
          150                         ltk_widget_destroy(ptr, 1, &err);
          151                 }
          152         }
          153         kh_destroy(widget, widget_hash);
          154         widget_hash = NULL;
          155         hash_locked = 0;
          156 }
          157 
          158 /* FIXME: any way to optimize the whole event mask handling a bit? */
          159 void
          160 ltk_widget_remove_client(int client) {
          161         khint_t k;
          162         ltk_widget *ptr;
          163         for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
          164                 if (kh_exist(widget_hash, k)) {
          165                         ptr = kh_value(widget_hash, k);
          166                         for (size_t i = 0; i < ptr->masks_num; i++) {
          167                                 if (ptr->event_masks[i].client == client) {
          168                                         memmove(ptr->event_masks + i, ptr->event_masks + i + 1, ptr->masks_num - i - 1);
          169                                         ptr->masks_num--;
          170                                         /* FIXME: maybe reset to NULL in that case? */
          171                                         if (ptr->masks_num > 0) {
          172                                                 size_t sz = ideal_array_size(ptr->masks_alloc, ptr->masks_num);
          173                                                 if (sz != ptr->masks_alloc) {
          174                                                         ptr->masks_alloc = sz;
          175                                                         ptr->event_masks = ltk_reallocarray(ptr->event_masks, sz, sizeof(client_event_mask));
          176                                                 }
          177                                         }
          178                                         break;
          179                                 }
          180                         }
          181                 }
          182         }
          183 }
          184 
          185 static client_event_mask *
          186 get_mask_struct(ltk_widget *widget, int client) {
          187         for (size_t i = 0; i < widget->masks_num; i++) {
          188                 if (widget->event_masks[i].client == client)
          189                         return &widget->event_masks[i];
          190         }
          191         widget->masks_alloc = ideal_array_size(widget->masks_alloc, widget->masks_num + 1);
          192         widget->event_masks = ltk_reallocarray(widget->event_masks, widget->masks_alloc, sizeof(client_event_mask));
          193         client_event_mask *m = &widget->event_masks[widget->masks_num];
          194         widget->masks_num++;
          195         m->client = client;
          196         m->mask = m->lmask = m->wmask = m->lwmask = 0;
          197         return m;
          198 }
          199 
          200 void
          201 ltk_widget_set_event_mask(ltk_widget *widget, int client, uint32_t mask) {
          202         client_event_mask *m = get_mask_struct(widget, client);
          203         m->mask = mask;
          204 }
          205 
          206 void
          207 ltk_widget_set_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
          208         client_event_mask *m = get_mask_struct(widget, client);
          209         m->lmask = mask;
          210 }
          211 
          212 void
          213 ltk_widget_set_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
          214         client_event_mask *m = get_mask_struct(widget, client);
          215         m->wmask = mask;
          216 }
          217 
          218 void
          219 ltk_widget_set_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
          220         client_event_mask *m = get_mask_struct(widget, client);
          221         m->lwmask = mask;
          222 }
          223 
          224 void
          225 ltk_widget_add_to_event_mask(ltk_widget *widget, int client, uint32_t mask) {
          226         client_event_mask *m = get_mask_struct(widget, client);
          227         m->mask |= mask;
          228 }
          229 
          230 void
          231 ltk_widget_add_to_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
          232         client_event_mask *m = get_mask_struct(widget, client);
          233         m->lmask |= mask;
          234 }
          235 
          236 void
          237 ltk_widget_add_to_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
          238         client_event_mask *m = get_mask_struct(widget, client);
          239         m->wmask |= mask;
          240 }
          241 
          242 void
          243 ltk_widget_add_to_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
          244         client_event_mask *m = get_mask_struct(widget, client);
          245         m->lwmask |= mask;
          246 }
          247 
          248 void
          249 ltk_widget_remove_from_event_mask(ltk_widget *widget, int client, uint32_t mask) {
          250         client_event_mask *m = get_mask_struct(widget, client);
          251         m->mask &= ~mask;
          252 }
          253 
          254 void
          255 ltk_widget_remove_from_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
          256         client_event_mask *m = get_mask_struct(widget, client);
          257         m->lmask &= ~mask;
          258 }
          259 
          260 void
          261 ltk_widget_remove_from_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
          262         client_event_mask *m = get_mask_struct(widget, client);
          263         m->wmask &= ~mask;
          264 }
          265 
          266 void
          267 ltk_widget_remove_from_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
          268         client_event_mask *m = get_mask_struct(widget, client);
          269         m->lwmask &= ~mask;
          270 }
          271 
          272 void
          273 ltk_widgets_init() {
          274         widget_hash = kh_init(widget);
          275         if (!widget_hash) ltk_fatal_errno("Unable to initialize widget hash table.\n");
          276 }
          277 
          278 void
          279 ltk_widgets_cleanup() {
          280         free(widget_stack);
          281         if (widget_hash)
          282                 ltk_destroy_widget_hash();
          283 }
          284 
          285 void
          286 ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
          287     struct ltk_widget_vtable *vtable, int w, int h) {
          288         if (id)
          289                 widget->id = ltk_strdup(id);
          290         else
          291                 widget->id = NULL;
          292         widget->window = window;
          293         widget->parent = NULL;
          294 
          295         /* FIXME: possibly check that draw and destroy aren't NULL */
          296         widget->vtable = vtable;
          297 
          298         widget->state = LTK_NORMAL;
          299         widget->row = 0;
          300         widget->lrect.x = 0;
          301         widget->lrect.y = 0;
          302         widget->lrect.w = w;
          303         widget->lrect.h = h;
          304         widget->crect.x = 0;
          305         widget->crect.y = 0;
          306         widget->crect.w = w;
          307         widget->crect.h = h;
          308         widget->popup = 0;
          309 
          310         widget->ideal_w = widget->ideal_h = 0;
          311 
          312         widget->event_masks = NULL;
          313         widget->masks_num = widget->masks_alloc = 0;
          314 
          315         widget->row = 0;
          316         widget->column = 0;
          317         widget->row_span = 0;
          318         widget->column_span = 0;
          319         widget->sticky = 0;
          320         widget->dirty = 1;
          321         widget->hidden = 0;
          322 }
          323 
          324 void
          325 ltk_widget_hide(ltk_widget *widget) {
          326         if (widget->vtable->hide)
          327                 widget->vtable->hide(widget);
          328         widget->hidden = 1;
          329         /* remove hover state */
          330         /* FIXME: this needs to call change_state but that might cause issues */
          331         ltk_widget *hover = widget->window->hover_widget;
          332         while (hover) {
          333                 if (hover == widget) {
          334                         widget->window->hover_widget->state &= ~LTK_HOVER;
          335                         widget->window->hover_widget = NULL;
          336                         break;
          337                 }
          338                 hover = hover->parent;
          339         }
          340         ltk_widget *pressed = widget->window->pressed_widget;
          341         while (pressed) {
          342                 if (pressed == widget) {
          343                         widget->window->pressed_widget->state &= ~LTK_PRESSED;
          344                         widget->window->pressed_widget = NULL;
          345                         break;
          346                 }
          347                 pressed = pressed->parent;
          348         }
          349         ltk_widget *active = widget->window->active_widget;
          350         /* if current active widget is child, set active widget to widget above in hierarchy */
          351         int set_next = 0;
          352         while (active) {
          353                 if (active == widget) {
          354                         set_next = 1;
          355                 /* FIXME: use config values for all_activatable */
          356                 } else if (set_next && (active->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
          357                         ltk_window_set_active_widget(active->window, active);
          358                         break;
          359                 }
          360                 active = active->parent;
          361         }
          362         if (set_next && !active)
          363                 ltk_window_set_active_widget(active->window, NULL);
          364 }
          365 
          366 /* FIXME: Maybe pass the new width as arg here?
          367    That would make a bit more sense */
          368 /* FIXME: maybe give global and local position in event */
          369 void
          370 ltk_widget_resize(ltk_widget *widget) {
          371         int lock_client = -1;
          372         for (size_t i = 0; i < widget->masks_num; i++) {
          373                 if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
          374                         ltk_queue_sock_write_fmt(
          375                             widget->event_masks[i].client,
          376                             "eventl %s widget configure %d %d %d %d\n",
          377                             widget->id, widget->lrect.x, widget->lrect.y,
          378                             widget->lrect.w, widget->lrect.h
          379                         );
          380                         lock_client = widget->event_masks[i].client;
          381                 } else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
          382                         ltk_queue_sock_write_fmt(
          383                             widget->event_masks[i].client,
          384                             "event %s widget configure %d %d %d %d\n",
          385                             widget->id, widget->lrect.x, widget->lrect.y,
          386                             widget->lrect.w, widget->lrect.h
          387                         );
          388                 }
          389         }
          390         if (lock_client >= 0) {
          391                 if (ltk_handle_lock_client(widget->window, lock_client))
          392                         return;
          393         }
          394         if (widget->vtable->resize)
          395                 widget->vtable->resize(widget);
          396         widget->dirty = 1;
          397 }
          398 
          399 void
          400 ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) {
          401         if (old_state == widget->state)
          402                 return;
          403         int lock_client = -1;
          404         /* FIXME: give old and new state in event */
          405         for (size_t i = 0; i < widget->masks_num; i++) {
          406                 if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
          407                         ltk_queue_sock_write_fmt(
          408                             widget->event_masks[i].client,
          409                             "eventl %s widget statechange\n", widget->id
          410                         );
          411                         lock_client = widget->event_masks[i].client;
          412                 } else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
          413                         ltk_queue_sock_write_fmt(
          414                             widget->event_masks[i].client,
          415                             "event %s widget statechange\n", widget->id
          416                         );
          417                 }
          418         }
          419         if (lock_client >= 0) {
          420                 if (ltk_handle_lock_client(widget->window, lock_client))
          421                         return;
          422         }
          423         if (widget->vtable->change_state)
          424                 widget->vtable->change_state(widget, old_state);
          425         if (widget->vtable->flags & LTK_NEEDS_REDRAW) {
          426                 widget->dirty = 1;
          427                 ltk_window_invalidate_widget_rect(widget->window, widget);
          428         }
          429 }
          430 
          431 /* x and y are global! */
          432 static ltk_widget *
          433 get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret) {
          434         ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
          435         ltk_widget *next = NULL;
          436         *local_x_ret = x - glob.x;
          437         *local_y_ret = y - glob.y;
          438         while (widget && widget->vtable->get_child_at_pos) {
          439                 next = widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret);
          440                 if (!next) {
          441                         break;
          442                 } else {
          443                         widget = next;
          444                         if (next->popup) {
          445                                 *local_x_ret = x - next->lrect.x;
          446                                 *local_y_ret = y - next->lrect.y;
          447                         } else {
          448                                 *local_x_ret -= next->lrect.x;
          449                                 *local_y_ret -= next->lrect.y;
          450                         }
          451                 }
          452         }
          453         return widget;
          454 }
          455 
          456 static ltk_widget *
          457 get_hover_popup(ltk_window *window, int x, int y) {
          458         for (size_t i = window->popups_num; i-- > 0;) {
          459                 if (ltk_collide_rect(window->popups[i]->crect, x, y))
          460                         return window->popups[i];
          461         }
          462         return NULL;
          463 }
          464 
          465 static int
          466 is_parent(ltk_widget *parent, ltk_widget *child) {
          467         while (child && child != parent) {
          468                 child = child->parent;
          469         }
          470         return child != NULL;
          471 }
          472 
          473 /* FIXME: fix global and local coordinates! */
          474 static int
          475 queue_mouse_event(ltk_widget *widget, ltk_event_type type, int x, int y) {
          476         uint32_t mask;
          477         char *typename;
          478         switch (type) {
          479         case LTK_MOTION_EVENT:
          480                 mask = LTK_PEVENTMASK_MOUSEMOTION;
          481                 typename = "mousemotion";
          482                 break;
          483         case LTK_2BUTTONPRESS_EVENT:
          484                 mask = LTK_PEVENTMASK_2MOUSEPRESS;
          485                 typename = "2mousepress";
          486                 break;
          487         case LTK_3BUTTONPRESS_EVENT:
          488                 mask = LTK_PEVENTMASK_3MOUSEPRESS;
          489                 typename = "3mousepress";
          490                 break;
          491         case LTK_BUTTONRELEASE_EVENT:
          492                 mask = LTK_PEVENTMASK_MOUSERELEASE;
          493                 typename = "mouserelease";
          494                 break;
          495         case LTK_2BUTTONRELEASE_EVENT:
          496                 mask = LTK_PEVENTMASK_2MOUSERELEASE;
          497                 typename = "2mouserelease";
          498                 break;
          499         case LTK_3BUTTONRELEASE_EVENT:
          500                 mask = LTK_PEVENTMASK_3MOUSERELEASE;
          501                 typename = "3mouserelease";
          502                 break;
          503         case LTK_BUTTONPRESS_EVENT:
          504         default:
          505                 mask = LTK_PEVENTMASK_MOUSEPRESS;
          506                 typename = "mousepress";
          507                 break;
          508         }
          509         int lock_client = -1;
          510         for (size_t i = 0; i < widget->masks_num; i++) {
          511                 if (widget->event_masks[i].lmask & mask) {
          512                         ltk_queue_sock_write_fmt(
          513                             widget->event_masks[i].client,
          514                             "eventl %s widget %s %d %d %d %d\n",
          515                             widget->id, typename, x, y, x, y
          516                             /* x - widget->rect.x, y - widget->rect.y */
          517                         );
          518                         lock_client = widget->event_masks[i].client;
          519                 } else if (widget->event_masks[i].mask & mask) {
          520                         ltk_queue_sock_write_fmt(
          521                             widget->event_masks[i].client,
          522                             "event %s widget %s %d %d %d %d\n",
          523                             widget->id, typename, x, y, x, y
          524                             /* x - widget->rect.x, y - widget->rect.y */
          525                         );
          526                 }
          527         }
          528         if (lock_client >= 0) {
          529                 if (ltk_handle_lock_client(widget->window, lock_client))
          530                         return 1;
          531         }
          532         return 0;
          533 }
          534 
          535 /* FIXME: global/local coords (like above) */
          536 static int
          537 queue_scroll_event(ltk_widget *widget, int x, int y, int dx, int dy) {
          538         uint32_t mask = LTK_PEVENTMASK_MOUSESCROLL;
          539         int lock_client = -1;
          540         for (size_t i = 0; i < widget->masks_num; i++) {
          541                 if (widget->event_masks[i].lmask & mask) {
          542                         ltk_queue_sock_write_fmt(
          543                             widget->event_masks[i].client,
          544                             "eventl %s widget %s %d %d %d %d %d %d\n",
          545                             widget->id, "mousescroll", x, y, x, y, dx, dy
          546                             /* x - widget->rect.x, y - widget->rect.y */
          547                         );
          548                         lock_client = widget->event_masks[i].client;
          549                 } else if (widget->event_masks[i].mask & mask) {
          550                         ltk_queue_sock_write_fmt(
          551                             widget->event_masks[i].client,
          552                             "event %s widget %s %d %d %d %d %d %d\n",
          553                             widget->id, "mousescroll", x, y, x, y, dx, dy
          554                             /* x - widget->rect.x, y - widget->rect.y */
          555                         );
          556                 }
          557         }
          558         if (lock_client >= 0) {
          559                 if (ltk_handle_lock_client(widget->window, lock_client))
          560                         return 1;
          561         }
          562         return 0;
          563 }
          564 
          565 static void
          566 ensure_active_widget_shown(ltk_window *window) {
          567         ltk_widget *widget = window->active_widget;
          568         if (!widget)
          569                 return;
          570         ltk_rect r = widget->lrect;
          571         while (widget->parent) {
          572                 if (widget->parent->vtable->ensure_rect_shown)
          573                         widget->parent->vtable->ensure_rect_shown(widget->parent, r);
          574                 widget = widget->parent;
          575                 r.x += widget->lrect.x;
          576                 r.y += widget->lrect.y;
          577                 /* FIXME: this currently just aborts if a widget is positioned
          578                    absolutely because I'm not sure what the best action would
          579                    be in that case */
          580                 if (widget->popup)
          581                         break;
          582         }
          583         ltk_window_invalidate_widget_rect(window, widget);
          584 }
          585 
          586 /* FIXME: come up with a more elegant way to handle this? */
          587 /* FIXME: Handle hidden state here instead of in widgets */
          588 /* FIXME: handle disabled state */
          589 static int
          590 prev_child(ltk_window *window) {
          591         if (!window->root_widget)
          592                 return 0;
          593         ltk_config *config = ltk_config_get();
          594         ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
          595         ltk_widget *new, *cur = window->active_widget;
          596         int changed = 0;
          597         ltk_widget *prevcur = cur;
          598         while (1) {
          599                 if (cur) {
          600                         while (cur->parent) {
          601                                 new = NULL;
          602                                 if (cur->parent->vtable->prev_child)
          603                                         new = cur->parent->vtable->prev_child(cur->parent, cur);
          604                                 if (new) {
          605                                         cur = new;
          606                                         ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
          607                                         while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
          608                                                 cur = new;
          609                                                 if (cur->vtable->flags & act_flags)
          610                                                         last_activatable = cur;
          611                                         }
          612                                         if (last_activatable) {
          613                                                 cur = last_activatable;
          614                                                 changed = 1;
          615                                                 break;
          616                                         }
          617                                 } else {
          618                                         cur = cur->parent;
          619                                         if (cur->vtable->flags & act_flags) {
          620                                                 changed = 1;
          621                                                 break;
          622                                         }
          623                                 }
          624                         }
          625                 }
          626                 if (!changed) {
          627                         cur = window->root_widget;
          628                         ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
          629                         while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
          630                                 cur = new;
          631                                 if (cur->vtable->flags & act_flags)
          632                                         last_activatable = cur;
          633                         }
          634                         if (last_activatable)
          635                                 cur = last_activatable;
          636                 }
          637                 if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
          638                         break;
          639                 prevcur = cur;
          640         }
          641         /* FIXME: What exactly should be done if no activatable widget exists? */
          642         if (cur != window->active_widget) {
          643                 ltk_window_set_active_widget(window, cur);
          644                 ensure_active_widget_shown(window);
          645                 return 1;
          646         }
          647         return 0;
          648 }
          649 
          650 static int
          651 next_child(ltk_window *window) {
          652         if (!window->root_widget)
          653                 return 0;
          654         ltk_config *config = ltk_config_get();
          655         ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
          656         ltk_widget *new, *cur = window->active_widget;
          657         int changed = 0;
          658         ltk_widget *prevcur = cur;
          659         while (1) {
          660                 if (cur) {
          661                         while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
          662                                 cur = new;
          663                                 if (cur->vtable->flags & act_flags) {
          664                                         changed = 1;
          665                                         break;
          666                                 }
          667                         }
          668                         if (!changed) {
          669                                 while (cur->parent) {
          670                                         new = NULL;
          671                                         if (cur->parent->vtable->next_child)
          672                                                 new = cur->parent->vtable->next_child(cur->parent, cur);
          673                                         if (new) {
          674                                                 cur = new;
          675                                                 if (cur->vtable->flags & act_flags) {
          676                                                         changed = 1;
          677                                                         break;
          678                                                 }
          679                                                 while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
          680                                                         cur = new;
          681                                                         if (cur->vtable->flags & act_flags) {
          682                                                                 changed = 1;
          683                                                                 break;
          684                                                         }
          685                                                 }
          686                                                 if (changed)
          687                                                         break;
          688                                         } else {
          689                                                 cur = cur->parent;
          690                                         }
          691                                 }
          692                         }
          693                 }
          694                 if (!changed) {
          695                         cur = window->root_widget;
          696                         if (!(cur->vtable->flags & act_flags)) {
          697                                 while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
          698                                         cur = new;
          699                                         if (cur->vtable->flags & act_flags)
          700                                                 break;
          701                                 }
          702                         }
          703                         if (!(cur->vtable->flags & act_flags))
          704                                 cur = window->root_widget;
          705                 }
          706                 if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
          707                         break;
          708                 prevcur = cur;
          709         }
          710         if (cur != window->active_widget) {
          711                 ltk_window_set_active_widget(window, cur);
          712                 ensure_active_widget_shown(window);
          713                 return 1;
          714         }
          715         return 0;
          716 }
          717 
          718 /* FIXME: moving up/down/left/right needs to be rethought
          719    it generally is a bit weird, and in particular, nearest_child always searches for the child
          720    that has the smallest distance to the given rect, so it may not be the child that the user
          721    expects when going down (e.g. a vertical box with one widget closer vertically but on the
          722    other side horizontally, thus possibly leading to a different widget that is farther away
          723    vertically to be chosen instead) - what would be logical here? */
          724 static ltk_widget *
          725 nearest_child(ltk_widget *widget, ltk_rect r) {
          726         ltk_point local = ltk_global_to_widget_pos(widget, r.x, r.y);
          727         return widget->vtable->nearest_child(widget, (ltk_rect){local.x, local.y, r.w, r.h});
          728 }
          729 
          730 /* FIXME: maybe wrap around in these two functions? */
          731 static int
          732 left_top_child(ltk_window *window, int left) {
          733         if (!window->root_widget)
          734                 return 0;
          735         ltk_config *config = ltk_config_get();
          736         ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
          737         ltk_widget *new, *cur = window->active_widget;
          738         ltk_rect old_rect = {0, 0, 0, 0};
          739         if (cur) {
          740                 ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
          741                 old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
          742         }
          743         if (cur) {
          744                 while (cur->parent) {
          745                         new = NULL;
          746                         if (left) {
          747                                 if (cur->parent->vtable->nearest_child_left)
          748                                         new = cur->parent->vtable->nearest_child_left(cur->parent, cur);
          749                         } else {
          750                                 if (cur->parent->vtable->nearest_child_above)
          751                                         new = cur->parent->vtable->nearest_child_above(cur->parent, cur);
          752                         }
          753                         if (new) {
          754                                 cur = new;
          755                                 ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
          756                                 while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) {
          757                                         cur = new;
          758                                         if (cur->vtable->flags & act_flags)
          759                                                 last_activatable = cur;
          760                                 }
          761                                 if (last_activatable) {
          762                                         cur = last_activatable;
          763                                         break;
          764                                 }
          765                         } else {
          766                                 cur = cur->parent;
          767                                 if (cur->vtable->flags & act_flags) {
          768                                         break;
          769                                 }
          770                         }
          771                 }
          772         } else {
          773                 cur = window->root_widget;
          774                 ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
          775                 ltk_rect r = {cur->lrect.w, cur->lrect.h, 0, 0};
          776                 while (cur->vtable->nearest_child && (new = nearest_child(cur, r))) {
          777                         cur = new;
          778                         if (cur->vtable->flags & act_flags)
          779                                 last_activatable = cur;
          780                 }
          781                 if (last_activatable)
          782                         cur = last_activatable;
          783         }
          784         /* FIXME: What exactly should be done if no activatable widget exists? */
          785         if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
          786                 ltk_window_set_active_widget(window, cur);
          787                 ensure_active_widget_shown(window);
          788                 return 1;
          789         }
          790         return 0;
          791 }
          792 
          793 static int
          794 right_bottom_child(ltk_window *window, int right) {
          795         if (!window->root_widget)
          796                 return 0;
          797         ltk_config *config = ltk_config_get();
          798         ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
          799         ltk_widget *new, *cur = window->active_widget;
          800         int changed = 0;
          801         ltk_rect old_rect = {0, 0, 0, 0};
          802         ltk_rect corner = {0, 0, 0, 0};
          803         if (cur) {
          804                 ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
          805                 corner = (ltk_rect){glob.x, glob.y, 0, 0};
          806                 old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
          807                 while (cur->vtable->nearest_child && (new = nearest_child(cur, corner))) {
          808                         cur = new;
          809                         if (cur->vtable->flags & act_flags) {
          810                                 changed = 1;
          811                                 break;
          812                         }
          813                 }
          814                 if (!changed) {
          815                         while (cur->parent) {
          816                                 new = NULL;
          817                                 if (right) {
          818                                         if (cur->parent->vtable->nearest_child_right)
          819                                                 new = cur->parent->vtable->nearest_child_right(cur->parent, cur);
          820                                 } else {
          821                                         if (cur->parent->vtable->nearest_child_below)
          822                                                 new = cur->parent->vtable->nearest_child_below(cur->parent, cur);
          823                                 }
          824                                 if (new) {
          825                                         cur = new;
          826                                         if (cur->vtable->flags & act_flags) {
          827                                                 changed = 1;
          828                                                 break;
          829                                         }
          830                                         while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) {
          831                                                 cur = new;
          832                                                 if (cur->vtable->flags & act_flags) {
          833                                                         changed = 1;
          834                                                         break;
          835                                                 }
          836                                         }
          837                                         if (changed)
          838                                                 break;
          839                                 } else {
          840                                         cur = cur->parent;
          841                                 }
          842                         }
          843                 }
          844         } else {
          845                 cur = window->root_widget;
          846                 if (!(cur->vtable->flags & act_flags)) {
          847                         while (cur->vtable->nearest_child && (new = nearest_child(cur, (ltk_rect){0, 0, 0, 0}))) {
          848                                 cur = new;
          849                                 if (cur->vtable->flags & act_flags)
          850                                         break;
          851                         }
          852                 }
          853                 if (!(cur->vtable->flags & act_flags))
          854                         cur = window->root_widget;
          855         }
          856         if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
          857                 ltk_window_set_active_widget(window, cur);
          858                 ensure_active_widget_shown(window);
          859                 return 1;
          860         }
          861         return 0;
          862 }
          863 
          864 /* FIXME: maybe just set this when active widget changes */
          865 /* -> but would also need to change it when widgets are created/destroyed or parents change */
          866 static void
          867 gen_widget_stack(ltk_widget *bottom) {
          868         widget_stack_len = 0;
          869         while (bottom) {
          870                 if (widget_stack_len + 1 > widget_stack_alloc) {
          871                         widget_stack_alloc = ideal_array_size(widget_stack_alloc, widget_stack_len + 1);
          872                         widget_stack = ltk_reallocarray(widget_stack, widget_stack_alloc, sizeof(ltk_widget *));
          873                 }
          874                 widget_stack[widget_stack_len++] = bottom;
          875                 bottom = bottom->parent;
          876         }
          877 }
          878 
          879 /* FIXME: The focus behavior needs to be rethought. It's currently hard-coded in the vtable for each
          880    widget type, but what if the program using ltk wants to catch keyboard events even if the widget
          881    doesn't do that by default? */
          882 static int
          883 cb_focus_active(ltk_window *window, ltk_key_event *event, int handled) {
          884         (void)event;
          885         (void)handled;
          886         if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) {
          887                 /* FIXME: maybe also set widgets above in hierarchy? */
          888                 ltk_widget_state old_state = window->active_widget->state;
          889                 window->active_widget->state |= LTK_FOCUSED;
          890                 ltk_widget_change_state(window->active_widget, old_state);
          891                 return 1;
          892         }
          893         return 0;
          894 }
          895 
          896 static int
          897 cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled) {
          898         (void)event;
          899         (void)handled;
          900         if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) {
          901                 ltk_widget_state old_state = window->active_widget->state;
          902                 window->active_widget->state &= ~LTK_FOCUSED;
          903                 ltk_widget_change_state(window->active_widget, old_state);
          904                 return 1;
          905         }
          906         return 0;
          907 }
          908 
          909 static int
          910 cb_move_prev(ltk_window *window, ltk_key_event *event, int handled) {
          911         (void)event;
          912         (void)handled;
          913         return prev_child(window);
          914 }
          915 
          916 static int
          917 cb_move_next(ltk_window *window, ltk_key_event *event, int handled) {
          918         (void)event;
          919         (void)handled;
          920         return next_child(window);
          921 }
          922 
          923 static int
          924 cb_move_left(ltk_window *window, ltk_key_event *event, int handled) {
          925         (void)event;
          926         (void)handled;
          927         return left_top_child(window, 1);
          928 }
          929 
          930 static int
          931 cb_move_right(ltk_window *window, ltk_key_event *event, int handled) {
          932         (void)event;
          933         (void)handled;
          934         return right_bottom_child(window, 1);
          935 }
          936 
          937 static int
          938 cb_move_up(ltk_window *window, ltk_key_event *event, int handled) {
          939         (void)event;
          940         (void)handled;
          941         return left_top_child(window, 0);
          942 }
          943 
          944 static int
          945 cb_move_down(ltk_window *window, ltk_key_event *event, int handled) {
          946         (void)event;
          947         (void)handled;
          948         return right_bottom_child(window, 0);
          949 }
          950 
          951 static int
          952 cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled) {
          953         (void)event;
          954         (void)handled;
          955         if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
          956                 /* FIXME: only set pressed if needs keyboard? */
          957                 ltk_window_set_pressed_widget(window, window->active_widget, 0);
          958                 return 1;
          959         }
          960         return 0;
          961 }
          962 
          963 static int
          964 cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled) {
          965         (void)event;
          966         (void)handled;
          967         if (window->pressed_widget) {
          968                 ltk_window_set_pressed_widget(window, NULL, 1);
          969                 return 1;
          970         }
          971         return 0;
          972 }
          973 
          974 static int
          975 cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled) {
          976         (void)event;
          977         (void)handled;
          978         if (window->popups_num > 0) {
          979                 ltk_window_unregister_all_popups(window);
          980                 return 1;
          981         }
          982         return 0;
          983 }
          984 
          985 /* FIXME: should keyrelease events be ignored if the corresponding keypress event
          986    was consumed for movement? */
          987 /* FIXME: check if there's any weirdness when combining return and mouse press */
          988 /* FIXME: maybe it doesn't really make sense to make e.g. text entry pressed when enter is pressed? */
          989 /* FIXME: implement key binding flag to run before widget handler is called */
          990 void
          991 ltk_window_key_press_event(ltk_window *window, ltk_key_event *event) {
          992         int handled = 0;
          993         if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
          994                 gen_widget_stack(window->active_widget);
          995                 for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
          996                         /* FIXME: send event to socket! */
          997                         if (widget_stack[i]->vtable->key_press && widget_stack[i]->vtable->key_press(widget_stack[i], event)) {
          998                                 handled = 1;
          999                                 break;
         1000                         }
         1001                 }
         1002         }
         1003         if (!keypresses)
         1004                 return;
         1005         ltk_keypress_binding *b = NULL;
         1006         for (size_t i = 0; i < ltk_array_length(keypresses); i++) {
         1007                 b = &ltk_array_get(keypresses, i).b;
         1008                 if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
         1009                         continue;
         1010                 } else if (b->text) {
         1011                         if (event->mapped && !strcmp(b->text, event->mapped))
         1012                                 handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
         1013                 } else if (b->rawtext) {
         1014                         if (event->text && !strcmp(b->text, event->text))
         1015                                 handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
         1016                 } else if (b->sym != LTK_KEY_NONE) {
         1017                         if (event->sym == b->sym)
         1018                                 handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
         1019                 }
         1020         }
         1021 
         1022 }
         1023 
         1024 /* FIXME: need to actually check if any of parent widgets are focused and still pass to them even if bottom widget not focused? */
         1025 void
         1026 ltk_window_key_release_event(ltk_window *window, ltk_key_event *event) {
         1027         /* FIXME: emit event */
         1028         int handled = 0;
         1029         if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
         1030                 gen_widget_stack(window->active_widget);
         1031                 for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
         1032                         if (widget_stack[i]->vtable->key_release && widget_stack[i]->vtable->key_release(widget_stack[i], event)) {
         1033                                 handled = 1;
         1034                                 break;
         1035                         }
         1036                 }
         1037         }
         1038         if (!keyreleases)
         1039                 return;
         1040         ltk_keyrelease_binding *b = NULL;
         1041         for (size_t i = 0; i < ltk_array_length(keyreleases); i++) {
         1042                 b = &ltk_array_get(keyreleases, i).b;
         1043                 if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
         1044                         continue;
         1045                 } else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) {
         1046                         handled |= ltk_array_get(keyreleases, i).cb.callback(window, event, handled);
         1047                 }
         1048         }
         1049 }
         1050 
         1051 /* FIXME: This is still weird. */
         1052 void
         1053 ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
         1054         ltk_widget *widget = get_hover_popup(window, event->x, event->y);
         1055         int check_hide = 0;
         1056         if (!widget) {
         1057                 widget = window->root_widget;
         1058                 check_hide = 1;
         1059         }
         1060         if (!widget) {
         1061                 ltk_window_unregister_all_popups(window);
         1062                 return;
         1063         }
         1064         int orig_x = event->x, orig_y = event->y;
         1065         ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
         1066         /* FIXME: need to add more flags for more fine-grained control
         1067            -> also, should the widget still get mouse_press even if state doesn't change? */
         1068         /* FIXME: doesn't work with e.g. disabled menu entries */
         1069         if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
         1070                 ltk_window_unregister_all_popups(window);
         1071         }
         1072 
         1073         /* FIXME: this doesn't make much sense if the popups aren't a
         1074            hierarchy (right now, they're just menus, so that's always
         1075            a hierarchy */
         1076         /* don't hide popups if they are children of the now pressed widget */
         1077         if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0])))
         1078                 ltk_window_unregister_all_popups(window);
         1079 
         1080         /* FIXME: popups don't always have their children geometrically contained within parents,
         1081            so this won't work properly in all cases */
         1082         int first = 1;
         1083         while (cur_widget) {
         1084                 int handled = 0;
         1085                 ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
         1086                 event->x = local.x;
         1087                 event->y = local.y;
         1088                 if (cur_widget->state != LTK_DISABLED) {
         1089                         /* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled)
         1090                            get mouse press, but they are only set to pressed if they are activatable */
         1091                         if (queue_mouse_event(cur_widget, event->type, event->x, event->y))
         1092                                 handled = 1;
         1093                         else if (cur_widget->vtable->mouse_press)
         1094                                 handled = cur_widget->vtable->mouse_press(cur_widget, event);
         1095                         /* set first non-disabled widget to pressed widget */
         1096                         /* FIXME: use config values for all_activatable */
         1097                         if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
         1098                                 ltk_window_set_pressed_widget(window, cur_widget, 0);
         1099                                 first = 0;
         1100                         }
         1101                 }
         1102                 if (!handled)
         1103                         cur_widget = cur_widget->parent;
         1104                 else
         1105                         break;
         1106         }
         1107 }
         1108 
         1109 void
         1110 ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_event *event) {
         1111         /* FIXME: should it first be sent to pressed widget? */
         1112         ltk_widget *widget = get_hover_popup(window, event->x, event->y);
         1113         if (!widget)
         1114                 widget = window->root_widget;
         1115         if (!widget)
         1116                 return;
         1117         int orig_x = event->x, orig_y = event->y;
         1118         ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
         1119         /* FIXME: same issue with popups like in mouse_press above */
         1120         while (cur_widget) {
         1121                 int handled = 0;
         1122                 ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
         1123                 event->x = local.x;
         1124                 event->y = local.y;
         1125                 if (cur_widget->state != LTK_DISABLED) {
         1126                         if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy))
         1127                                 handled = 1;
         1128                         else if (cur_widget->vtable->mouse_scroll)
         1129                                 handled = cur_widget->vtable->mouse_scroll(cur_widget, event);
         1130                 }
         1131                 if (!handled)
         1132                         cur_widget = cur_widget->parent;
         1133                 else
         1134                         break;
         1135         }
         1136 }
         1137 
         1138 void
         1139 ltk_window_fake_motion_event(ltk_window *window, int x, int y) {
         1140         ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y};
         1141         ltk_window_motion_notify_event(window, &e);
         1142 }
         1143 
         1144 void
         1145 ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) {
         1146         ltk_widget *widget = window->pressed_widget;
         1147         int orig_x = event->x, orig_y = event->y;
         1148         /* FIXME: why does this only take pressed widget and popups into account? */
         1149         if (!widget) {
         1150                 widget = get_hover_popup(window, event->x, event->y);
         1151                 widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
         1152         }
         1153         /* FIXME: loop up to top of hierarchy if not handled */
         1154         if (widget && queue_mouse_event(widget, event->type, event->x, event->y)) {
         1155                 /* NOP */
         1156         } else if (widget && widget->vtable->mouse_release) {
         1157                 widget->vtable->mouse_release(widget, event);
         1158         }
         1159         if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) {
         1160                 int release = 0;
         1161                 if (window->pressed_widget) {
         1162                         ltk_rect prect = window->pressed_widget->lrect;
         1163                         ltk_point pglob = ltk_widget_pos_to_global(window->pressed_widget, 0, 0);
         1164                         if (ltk_collide_rect((ltk_rect){pglob.x, pglob.y, prect.w, prect.h}, orig_x, orig_y))
         1165                                 release = 1;
         1166                 }
         1167                 ltk_window_set_pressed_widget(window, NULL, release);
         1168                 /* send motion notify to widget under pointer */
         1169                 /* FIXME: only when not collide with rect? */
         1170                 ltk_window_fake_motion_event(window, orig_x, orig_y);
         1171         }
         1172 }
         1173 
         1174 void
         1175 ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) {
         1176         ltk_widget *widget = get_hover_popup(window, event->x, event->y);
         1177         int orig_x = event->x, orig_y = event->y;
         1178         if (!widget) {
         1179                 widget = window->pressed_widget;
         1180                 if (widget) {
         1181                         ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
         1182                         event->x = local.x;
         1183                         event->y = local.y;
         1184                         if (widget->vtable->motion_notify)
         1185                                 widget->vtable->motion_notify(widget, event);
         1186                         return;
         1187                 }
         1188                 widget = window->root_widget;
         1189         }
         1190         if (!widget)
         1191                 return;
         1192         ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
         1193         if (!ltk_collide_rect((ltk_rect){0, 0, widget->lrect.w, widget->lrect.h}, local.x, local.y)) {
         1194                 ltk_window_set_hover_widget(widget->window, NULL, event);
         1195                 return;
         1196         }
         1197         ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
         1198         int first = 1;
         1199         while (cur_widget) {
         1200                 int handled = 0;
         1201                 ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
         1202                 event->x = local.x;
         1203                 event->y = local.y;
         1204                 if (cur_widget->state != LTK_DISABLED) {
         1205                         if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y))
         1206                                 handled = 1;
         1207                         else if (cur_widget->vtable->motion_notify)
         1208                                 handled = cur_widget->vtable->motion_notify(cur_widget, event);
         1209                         /* set first non-disabled widget to hover widget */
         1210                         /* FIXME: should enter/leave event be sent to parent
         1211                            when moving from/to widget nested in parent? */
         1212                         /* FIXME: use config values for all_activatable */
         1213                         if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
         1214                                 event->x = orig_x;
         1215                                 event->y = orig_y;
         1216                                 ltk_window_set_hover_widget(window, cur_widget, event);
         1217                                 first = 0;
         1218                         }
         1219                 }
         1220                 if (!handled)
         1221                         cur_widget = cur_widget->parent;
         1222                 else
         1223                         break;
         1224         }
         1225         if (first) {
         1226                 event->x = orig_x;
         1227                 event->y = orig_y;
         1228                 ltk_window_set_hover_widget(window, NULL, event);
         1229         }
         1230 }
         1231 
         1232 int
         1233 ltk_widget_id_free(const char *id) {
         1234         khint_t k;
         1235         k = kh_get(widget, widget_hash, id);
         1236         if (k != kh_end(widget_hash)) {
         1237                 return 0;
         1238         }
         1239         return 1;
         1240 }
         1241 
         1242 ltk_widget *
         1243 ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err) {
         1244         khint_t k;
         1245         ltk_widget *widget;
         1246         k = kh_get(widget, widget_hash, id);
         1247         if (k == kh_end(widget_hash)) {
         1248                 err->type = ERR_INVALID_WIDGET_ID;
         1249                 return NULL;
         1250         }
         1251         widget = kh_value(widget_hash, k);
         1252         if (type != LTK_WIDGET_ANY && widget->vtable->type != type) {
         1253                 err->type = ERR_INVALID_WIDGET_TYPE;
         1254                 return NULL;
         1255         }
         1256         return widget;
         1257 }
         1258 
         1259 void
         1260 ltk_set_widget(ltk_widget *widget, const char *id) {
         1261         int ret;
         1262         khint_t k;
         1263         /* FIXME: make sure no widget is overwritten here */
         1264         char *tmp = ltk_strdup(id);
         1265         k = kh_put(widget, widget_hash, tmp, &ret);
         1266         kh_value(widget_hash, k) = widget;
         1267 }
         1268 
         1269 void
         1270 ltk_remove_widget(const char *id) {
         1271         if (hash_locked)
         1272                 return;
         1273         khint_t k;
         1274         k = kh_get(widget, widget_hash, id);
         1275         if (k != kh_end(widget_hash)) {
         1276                 ltk_free((char *)kh_key(widget_hash, k));
         1277                 kh_del(widget, widget_hash, k);
         1278         }
         1279 }
         1280 
         1281 int
         1282 ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err) {
         1283         /* widget->parent->remove_child should never be NULL because of the fact that
         1284            the widget is set as parent, but let's just check anyways... */
         1285         int invalid = 0;
         1286         if (widget->parent && widget->parent->vtable->remove_child) {
         1287                 invalid = widget->parent->vtable->remove_child(
         1288                     widget, widget->parent, err
         1289                 );
         1290         }
         1291         ltk_remove_widget(widget->id);
         1292         ltk_free(widget->id);
         1293         widget->id = NULL;
         1294         ltk_free(widget->event_masks);
         1295         widget->event_masks = NULL;
         1296         widget->vtable->destroy(widget, shallow);
         1297 
         1298         return invalid;
         1299 }
         1300 
         1301 int
         1302 ltk_widget_destroy_cmd(
         1303     ltk_window *window,
         1304     ltk_cmd_token *tokens,
         1305     size_t num_tokens,
         1306     ltk_error *err) {
         1307         (void)window;
         1308         int shallow = 1;
         1309         if (num_tokens != 2 && num_tokens != 3) {
         1310                 err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
         1311                 err->arg = -1;
         1312                 return 1;
         1313         }
         1314         if (tokens[1].contains_nul) {
         1315                 err->type = ERR_INVALID_ARGUMENT;
         1316                 err->arg = 1;
         1317                 return 1;
         1318         } else if (num_tokens == 3 && tokens[2].contains_nul) {
         1319                 err->type = ERR_INVALID_ARGUMENT;
         1320                 err->arg = 2;
         1321                 return 1;
         1322         }
         1323         if (num_tokens == 3) {
         1324                 if (strcmp(tokens[2].text, "deep") == 0) {
         1325                         shallow = 0;
         1326                 } else if (strcmp(tokens[2].text, "shallow") == 0) {
         1327                         shallow = 1;
         1328                 } else {
         1329                         err->type = ERR_INVALID_ARGUMENT;
         1330                         err->arg = 2;
         1331                         return 1;
         1332                 }
         1333         }
         1334         ltk_widget *widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err);
         1335         if (!widget) {
         1336                 err->arg = 1;
         1337                 return 1;
         1338         }
         1339         if (ltk_widget_destroy(widget, shallow, err)) {
         1340                 err->arg = -1;
         1341                 return 1;
         1342         }
         1343         return 0;
         1344 }