widget.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 = <k_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 = <k_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 }