menu.c (58157B)
1 /* 2 * Copyright (c) 2022-2023 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 /* NOTE: The implementation of menus and menu entries is a collection of ugly hacks. */ 18 19 /* FIXME: parent is pressed when scroll arrows pressed */ 20 /* -> this is because the pressed handling checks if the widget is activatable, then goes to the parent, 21 but the child isn't geometrically in the parent here, so that's weird */ 22 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <stdint.h> 26 #include <string.h> 27 #include <stdarg.h> 28 #include <math.h> 29 30 #include "proto_types.h" 31 #include "event.h" 32 #include "memory.h" 33 #include "color.h" 34 #include "rect.h" 35 #include "widget.h" 36 #include "ltk.h" 37 #include "util.h" 38 #include "text.h" 39 #include "menu.h" 40 #include "graphics.h" 41 #include "surface_cache.h" 42 #include "theme.h" 43 #include "cmd.h" 44 45 #define MAX_MENU_BORDER_WIDTH 100 46 #define MAX_MENU_PAD 500 47 #define MAX_MENU_ARROW_SIZE 100 48 49 #define MAX(a, b) ((a) > (b) ? (a) : (b)) 50 51 static struct theme { 52 int pad; 53 int arrow_pad; 54 int arrow_size; 55 int border_width; 56 int compress_borders; 57 58 ltk_color border; 59 ltk_color background; 60 ltk_color scroll_background; 61 ltk_color scroll_arrow_color; 62 } menu_theme, submenu_theme; 63 64 static struct entry_theme { 65 int text_pad; 66 int arrow_pad; 67 int arrow_size; 68 int border_width; 69 int compress_borders; 70 /* FIXME: should border_sides actually factor into 71 size calculation? - probably useless and would 72 just make it more complicated */ 73 /* FIXME: allow different values for different states? */ 74 ltk_border_sides border_sides; 75 76 ltk_color text; 77 ltk_color border; 78 ltk_color fill; 79 80 ltk_color text_pressed; 81 ltk_color border_pressed; 82 ltk_color fill_pressed; 83 84 ltk_color text_active; 85 ltk_color border_active; 86 ltk_color fill_active; 87 88 ltk_color text_disabled; 89 ltk_color border_disabled; 90 ltk_color fill_disabled; 91 } menu_entry_theme, submenu_entry_theme; 92 93 static void ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r); 94 static void ltk_menu_resize(ltk_widget *self); 95 static void ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); 96 static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret); 97 static void ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step); 98 static void ltk_menu_scroll_callback(void *data); 99 static void stop_scrolling(ltk_menu *menu); 100 static ltk_widget *ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y); 101 static int set_scroll_timer(ltk_menu *menu, int x, int y); 102 static int ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event); 103 static void ltk_menu_hide(ltk_widget *self); 104 static void popup_active_menu(ltk_menuentry *e); 105 static void unpopup_active_entry(ltk_menuentry *e); 106 static int ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event); 107 static int ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event); 108 static int ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event); 109 static ltk_menu *ltk_menu_create(ltk_window *window, const char *id, int is_submenu); 110 static void recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget); 111 static void shrink_entries(ltk_menu *menu); 112 static size_t get_entry_with_id(ltk_menu *menu, const char *id); 113 static void ltk_menu_destroy(ltk_widget *self, int shallow); 114 115 static ltk_widget *ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect); 116 static ltk_widget *ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget); 117 static ltk_widget *ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget); 118 static ltk_widget *ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget); 119 static ltk_widget *ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget); 120 121 static ltk_menuentry *ltk_menuentry_create(ltk_window *window, const char *id, const char *text); 122 static void ltk_menuentry_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); 123 static void ltk_menuentry_destroy(ltk_widget *self, int shallow); 124 static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state); 125 static int ltk_menuentry_release(ltk_widget *self); 126 static void ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry); 127 static int ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err); 128 static void ltk_menuentry_detach_submenu(ltk_menuentry *e); 129 130 static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err); 131 static int ltk_menuentry_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err); 132 133 static ltk_widget *ltk_menu_prev_child(ltk_widget *self, ltk_widget *child); 134 static ltk_widget *ltk_menu_next_child(ltk_widget *self, ltk_widget *child); 135 static ltk_widget *ltk_menu_first_child(ltk_widget *self); 136 static ltk_widget *ltk_menu_last_child(ltk_widget *self); 137 static ltk_widget *ltk_menuentry_get_child(ltk_widget *self); 138 139 #define IN_SUBMENU(e) (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)e->widget.parent)->is_submenu) 140 141 static struct ltk_widget_vtable vtable = { 142 .key_press = NULL, 143 .key_release = NULL, 144 .mouse_press = NULL, 145 .mouse_scroll = <k_menu_mouse_scroll, 146 .motion_notify = <k_menu_motion_notify, 147 .mouse_release = NULL, 148 .mouse_enter = <k_menu_mouse_enter, 149 .mouse_leave = <k_menu_mouse_leave, 150 .get_child_at_pos = <k_menu_get_child_at_pos, 151 .resize = <k_menu_resize, 152 .change_state = NULL, 153 .hide = <k_menu_hide, 154 .draw = <k_menu_draw, 155 .destroy = <k_menu_destroy, 156 .child_size_change = &recalc_ideal_menu_size, 157 .remove_child = <k_menu_remove_child, 158 .prev_child = <k_menu_prev_child, 159 .next_child = <k_menu_next_child, 160 .first_child = <k_menu_first_child, 161 .last_child = <k_menu_last_child, 162 .nearest_child = <k_menu_nearest_child, 163 .nearest_child_left = <k_menu_nearest_child_left, 164 .nearest_child_right = <k_menu_nearest_child_right, 165 .nearest_child_above = <k_menu_nearest_child_above, 166 .nearest_child_below = <k_menu_nearest_child_below, 167 .ensure_rect_shown = <k_menu_ensure_rect_shown, 168 .type = LTK_WIDGET_MENU, 169 .flags = LTK_NEEDS_REDRAW, 170 }; 171 172 static struct ltk_widget_vtable entry_vtable = { 173 .key_press = NULL, 174 .key_release = NULL, 175 .mouse_press = NULL, 176 .motion_notify = NULL, 177 .mouse_release = NULL, 178 .release = <k_menuentry_release, 179 .mouse_enter = NULL, 180 .mouse_leave = NULL, 181 .get_child_at_pos = NULL, 182 .resize = NULL, 183 .change_state = <k_menuentry_change_state, 184 .hide = NULL, 185 .draw = <k_menuentry_draw, 186 .destroy = <k_menuentry_destroy, 187 .child_size_change = NULL, 188 .remove_child = <k_menuentry_remove_child, 189 .first_child = <k_menuentry_get_child, 190 .last_child = <k_menuentry_get_child, 191 .type = LTK_WIDGET_MENUENTRY, 192 .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_HOVER_IS_ACTIVE, 193 }; 194 195 /* FIXME: standardize menuentry vs. menu_entry */ 196 197 static ltk_theme_parseinfo menu_parseinfo[] = { 198 {"pad", THEME_INT, {.i = &menu_theme.pad}, {.i = 0}, 0, MAX_MENU_PAD, 0}, 199 {"arrow-pad", THEME_INT, {.i = &menu_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, 200 {"arrow-size", THEME_INT, {.i = &menu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, 201 {"border-width", THEME_INT, {.i = &menu_theme.border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0}, 202 {"compress-borders", THEME_BOOL, {.b = &menu_theme.compress_borders}, {.b = 1}, 0, 0, 0}, 203 {"border", THEME_COLOR, {.color = &menu_theme.border}, {.color = "#000000"}, 0, 0, 0}, 204 {"background", THEME_COLOR, {.color = &menu_theme.background}, {.color = "#000000"}, 0, 0, 0}, 205 {"scroll-background", THEME_COLOR, {.color = &menu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0}, 206 {"scroll-arrow-color", THEME_COLOR, {.color = &menu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0}, 207 }; 208 static int menu_parseinfo_sorted = 0; 209 210 int 211 ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value) { 212 return ltk_theme_handle_value(window, "menu", prop, value, menu_parseinfo, LENGTH(menu_parseinfo), &menu_parseinfo_sorted); 213 } 214 215 int 216 ltk_menu_fill_theme_defaults(ltk_window *window) { 217 return ltk_theme_fill_defaults(window, "menu", menu_parseinfo, LENGTH(menu_parseinfo)); 218 } 219 220 void 221 ltk_menu_uninitialize_theme(ltk_window *window) { 222 ltk_theme_uninitialize(window, menu_parseinfo, LENGTH(menu_parseinfo)); 223 } 224 225 static ltk_theme_parseinfo menu_entry_parseinfo[] = { 226 {"text-pad", THEME_INT, {.i = &menu_entry_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, 227 {"arrow-pad", THEME_INT, {.i = &menu_entry_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, 228 {"arrow-size", THEME_INT, {.i = &menu_entry_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, 229 {"border-width", THEME_INT, {.i = &menu_entry_theme.border_width}, {.i = 2}, 0, MAX_MENU_BORDER_WIDTH, 0}, 230 {"border-sides", THEME_BORDERSIDES, {.border = &menu_entry_theme.border_sides}, {.border = LTK_BORDER_ALL}, 0, 0, 0}, 231 {"compress-borders", THEME_BOOL, {.b = &menu_entry_theme.compress_borders}, {.b = 1}, 0, 0, 0}, 232 {"text", THEME_COLOR, {.color = &menu_entry_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0}, 233 {"border", THEME_COLOR, {.color = &menu_entry_theme.border}, {.color = "#339999"}, 0, 0, 0}, 234 {"fill", THEME_COLOR, {.color = &menu_entry_theme.fill}, {.color = "#113355"}, 0, 0, 0}, 235 {"text-pressed", THEME_COLOR, {.color = &menu_entry_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0}, 236 {"border-pressed", THEME_COLOR, {.color = &menu_entry_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, 237 {"fill-pressed", THEME_COLOR, {.color = &menu_entry_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, 238 {"text-active", THEME_COLOR, {.color = &menu_entry_theme.text_active}, {.color = "#000000"}, 0, 0, 0}, 239 {"border-active", THEME_COLOR, {.color = &menu_entry_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, 240 {"fill-active", THEME_COLOR, {.color = &menu_entry_theme.fill_active}, {.color = "#738194"}, 0, 0, 0}, 241 {"text-disabled", THEME_COLOR, {.color = &menu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, 242 {"border-disabled", THEME_COLOR, {.color = &menu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, 243 {"fill-disabled", THEME_COLOR, {.color = &menu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, 244 }; 245 static int menu_entry_parseinfo_sorted = 0; 246 247 int 248 ltk_menuentry_ini_handler(ltk_window *window, const char *prop, const char *value) { 249 return ltk_theme_handle_value(window, "menu-entry", prop, value, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo), &menu_entry_parseinfo_sorted); 250 } 251 252 int 253 ltk_menuentry_fill_theme_defaults(ltk_window *window) { 254 return ltk_theme_fill_defaults(window, "menu-entry", menu_entry_parseinfo, LENGTH(menu_entry_parseinfo)); 255 } 256 257 void 258 ltk_menuentry_uninitialize_theme(ltk_window *window) { 259 ltk_theme_uninitialize(window, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo)); 260 } 261 262 static ltk_theme_parseinfo submenu_parseinfo[] = { 263 {"pad", THEME_INT, {.i = &submenu_theme.pad}, {.i = 0}, 0, MAX_MENU_PAD, 0}, 264 {"arrow-pad", THEME_INT, {.i = &submenu_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, 265 {"arrow-size", THEME_INT, {.i = &submenu_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, 266 {"border-width", THEME_INT, {.i = &submenu_theme.border_width}, {.i = 1}, 0, MAX_MENU_BORDER_WIDTH, 0}, 267 {"compress-borders", THEME_BOOL, {.b = &submenu_theme.compress_borders}, {.b = 1}, 0, 0, 0}, 268 {"border", THEME_COLOR, {.color = &submenu_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0}, 269 {"background", THEME_COLOR, {.color = &submenu_theme.background}, {.color = "#000000"}, 0, 0, 0}, 270 {"scroll-background", THEME_COLOR, {.color = &submenu_theme.scroll_background}, {.color = "#333333"}, 0, 0, 0}, 271 {"scroll-arrow-color", THEME_COLOR, {.color = &submenu_theme.scroll_arrow_color}, {.color = "#000000"}, 0, 0, 0}, 272 }; 273 static int submenu_parseinfo_sorted = 0; 274 275 int 276 ltk_submenu_ini_handler(ltk_window *window, const char *prop, const char *value) { 277 return ltk_theme_handle_value(window, "submenu", prop, value, submenu_parseinfo, LENGTH(submenu_parseinfo), &submenu_parseinfo_sorted); 278 } 279 280 int 281 ltk_submenu_fill_theme_defaults(ltk_window *window) { 282 return ltk_theme_fill_defaults(window, "submenu", submenu_parseinfo, LENGTH(submenu_parseinfo)); 283 } 284 285 void 286 ltk_submenu_uninitialize_theme(ltk_window *window) { 287 ltk_theme_uninitialize(window, submenu_parseinfo, LENGTH(submenu_parseinfo)); 288 } 289 290 static ltk_theme_parseinfo submenu_entry_parseinfo[] = { 291 {"text-pad", THEME_INT, {.i = &submenu_entry_theme.text_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, 292 {"arrow-pad", THEME_INT, {.i = &submenu_entry_theme.arrow_pad}, {.i = 5}, 0, MAX_MENU_PAD, 0}, 293 {"arrow-size", THEME_INT, {.i = &submenu_entry_theme.arrow_size}, {.i = 10}, 0, MAX_MENU_ARROW_SIZE, 0}, 294 {"border-width", THEME_INT, {.i = &submenu_entry_theme.border_width}, {.i = 0}, 0, MAX_MENU_BORDER_WIDTH, 0}, 295 {"border-sides", THEME_BORDERSIDES, {.border = &submenu_entry_theme.border_sides}, {.border = LTK_BORDER_NONE}, 0, 0, 0}, 296 {"compress-borders", THEME_BOOL, {.b = &submenu_entry_theme.compress_borders}, {.b = 0}, 0, 0, 0}, 297 {"text", THEME_COLOR, {.color = &submenu_entry_theme.text}, {.color = "#FFFFFF"}, 0, 0, 0}, 298 {"border", THEME_COLOR, {.color = &submenu_entry_theme.border}, {.color = "#FFFFFF"}, 0, 0, 0}, 299 {"fill", THEME_COLOR, {.color = &submenu_entry_theme.fill}, {.color = "#113355"}, 0, 0, 0}, 300 {"text-pressed", THEME_COLOR, {.color = &submenu_entry_theme.text_pressed}, {.color = "#000000"}, 0, 0, 0}, 301 {"border-pressed", THEME_COLOR, {.color = &submenu_entry_theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, 302 {"fill-pressed", THEME_COLOR, {.color = &submenu_entry_theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, 303 {"text-active", THEME_COLOR, {.color = &submenu_entry_theme.text_active}, {.color = "#000000"}, 0, 0, 0}, 304 {"border-active", THEME_COLOR, {.color = &submenu_entry_theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, 305 {"fill-active", THEME_COLOR, {.color = &submenu_entry_theme.fill_active}, {.color = "#738194"}, 0, 0, 0}, 306 {"text-disabled", THEME_COLOR, {.color = &submenu_entry_theme.text_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, 307 {"border-disabled", THEME_COLOR, {.color = &submenu_entry_theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, 308 {"fill-disabled", THEME_COLOR, {.color = &submenu_entry_theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, 309 }; 310 static int submenu_entry_parseinfo_sorted = 0; 311 312 int 313 ltk_submenuentry_ini_handler(ltk_window *window, const char *prop, const char *value) { 314 return ltk_theme_handle_value(window, "submenu-entry", prop, value, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo), &submenu_entry_parseinfo_sorted); 315 } 316 317 int 318 ltk_submenuentry_fill_theme_defaults(ltk_window *window) { 319 return ltk_theme_fill_defaults(window, "submenu-entry", submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo)); 320 } 321 322 void 323 ltk_submenuentry_uninitialize_theme(ltk_window *window) { 324 ltk_theme_uninitialize(window, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo)); 325 } 326 327 static void 328 ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) { 329 ltk_menuentry *e = (ltk_menuentry *)self; 330 int in_submenu = IN_SUBMENU(e); 331 int submenus_opened = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus; 332 if (!(self->state & (LTK_ACTIVE | LTK_PRESSED))) { 333 /* Note: This only has to take care of the submenu that is the direct child 334 of e because ltk_window_set_active_widget already calls change_state for 335 the whole hierarchy */ 336 unpopup_active_entry(e); 337 } else if ((self->state & LTK_PRESSED) && !(old_state & LTK_PRESSED) && submenus_opened) { 338 ((ltk_menu *)self->parent)->popup_submenus = 0; 339 } else if (((self->state & LTK_PRESSED) || 340 ((self->state & LTK_ACTIVE) && (in_submenu || submenus_opened))) && 341 e->submenu && e->submenu->widget.hidden) { 342 popup_active_menu(e); 343 if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENU) 344 ((ltk_menu *)self->parent)->popup_submenus = 1; 345 } 346 } 347 348 static ltk_widget * 349 ltk_menuentry_get_child(ltk_widget *self) { 350 ltk_menuentry *e = (ltk_menuentry *)self; 351 if (e->submenu && !e->submenu->widget.hidden) 352 return &e->submenu->widget; 353 return NULL; 354 } 355 356 static void 357 ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { 358 /* FIXME: figure out how hidden should work */ 359 if (self->hidden) 360 return; 361 ltk_menuentry *entry = (ltk_menuentry *)self; 362 int in_submenu = IN_SUBMENU(entry); 363 struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme; 364 ltk_color *text, *border, *fill; 365 if (self->state & LTK_DISABLED) { 366 text = &t->text_disabled; 367 border = &t->border_disabled; 368 fill = &t->fill_disabled; 369 } else if (self->state & LTK_PRESSED) { 370 text = &t->text_pressed; 371 border = &t->border_pressed; 372 fill = &t->fill_pressed; 373 } else if (self->state & LTK_HOVERACTIVE) { 374 text = &t->text_active; 375 border = &t->border_active; 376 fill = &t->fill_active; 377 } else { 378 text = &t->text; 379 border = &t->border; 380 fill = &t->fill; 381 } 382 ltk_rect lrect = self->lrect; 383 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); 384 if (clip_final.w <= 0 || clip_final.h <= 0) 385 return; 386 ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h}; 387 ltk_surface_fill_rect(draw_surf, fill, surf_clip); 388 389 ltk_surface *s; 390 int text_w, text_h; 391 ltk_text_line_get_size(entry->text_line, &text_w, &text_h); 392 if (!ltk_surface_cache_get_surface(entry->text_surface_key, &s) || self->dirty) { 393 ltk_surface_fill_rect(s, fill, (ltk_rect){0, 0, text_w, text_h}); 394 ltk_text_line_draw(entry->text_line, s, text, 0, 0); 395 self->dirty = 0; 396 } 397 int text_x = t->text_pad + t->border_width; 398 int text_y = t->text_pad + t->border_width; 399 ltk_rect text_clip = ltk_rect_intersect(clip, (ltk_rect){text_x, text_y, text_w, text_h}); 400 ltk_surface_copy( 401 s, draw_surf, 402 (ltk_rect){text_clip.x - text_x, text_clip.y - text_y, text_clip.w, text_clip.h}, x + text_clip.x, y + text_clip.y 403 ); 404 405 if (in_submenu && entry->submenu) { 406 ltk_point arrow_points[] = { 407 {x + lrect.w - t->arrow_pad - t->border_width, y + lrect.h / 2}, 408 {x + lrect.w - t->arrow_pad - t->border_width - t->arrow_size, y + lrect.h / 2 - t->arrow_size / 2}, 409 {x + lrect.w - t->arrow_pad - t->border_width - t->arrow_size, y + lrect.h / 2 + t->arrow_size / 2} 410 }; 411 ltk_surface_fill_polygon_clipped(draw_surf, text, arrow_points, LENGTH(arrow_points), surf_clip); 412 } 413 ltk_surface_draw_border_clipped(draw_surf, border, (ltk_rect){x, y, lrect.w, lrect.h}, surf_clip, t->border_width, t->border_sides); 414 } 415 416 static void 417 ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { 418 if (self->hidden) 419 return; 420 ltk_menu *menu = (ltk_menu *)self; 421 ltk_rect lrect = self->lrect; 422 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); 423 if (clip_final.w <= 0 || clip_final.h <= 0) 424 return; 425 struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; 426 ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h}; 427 ltk_surface_fill_rect(s, &t->background, surf_clip); 428 ltk_widget *ptr = NULL; 429 for (size_t i = 0; i < menu->num_entries; i++) { 430 /* FIXME: I guess it could be improved *slightly* by making the clip rect 431 smaller when scrollarrows are shown */ 432 /* draw active entry after others so it isn't hidden with compress_borders */ 433 if ((menu->entries[i]->widget.state & (LTK_ACTIVE | LTK_PRESSED | LTK_HOVER)) && i < menu->num_entries - 1) { 434 ptr = &menu->entries[i + 1]->widget; 435 ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); 436 ptr = &menu->entries[i]->widget; 437 ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); 438 i++; 439 } else { 440 ptr = &menu->entries[i]->widget; 441 ltk_menuentry_draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, clip_final)); 442 } 443 } 444 445 /* FIXME: active, pressed states */ 446 int sz = t->arrow_size + t->arrow_pad * 2; 447 int ww = self->lrect.w; 448 int wh = self->lrect.h; 449 int wx = x, wy = y; 450 int mbw = t->border_width; 451 /* FIXME: handle pathological case where rect is so small that this still draws outside */ 452 /* -> this is currently a mess because some parts handle clipping properly, but the scroll arrow drawing doesn't */ 453 if (lrect.w < (int)self->ideal_w) { 454 ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, sz, wh - mbw * 2}); 455 ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){wx + ww - sz - mbw, wy + mbw, sz, wh - mbw * 2}); 456 ltk_point arrow_points[3] = { 457 {wx + t->arrow_pad + mbw, wy + wh / 2}, 458 {wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 - t->arrow_size / 2}, 459 {wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 + t->arrow_size / 2} 460 }; 461 ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); 462 arrow_points[0] = (ltk_point){wx + ww - t->arrow_pad - mbw, wy + wh / 2}; 463 arrow_points[1] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 - t->arrow_size / 2}; 464 arrow_points[2] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 + t->arrow_size / 2}; 465 ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); 466 } 467 if (lrect.h < (int)self->ideal_h) { 468 ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz}); 469 ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz}); 470 ltk_point arrow_points[3] = { 471 {wx + ww / 2, wy + t->arrow_pad + mbw}, 472 {wx + ww / 2 - t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size}, 473 {wx + ww / 2 + t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size} 474 }; 475 ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); 476 arrow_points[0] = (ltk_point){wx + ww / 2, wy + wh - t->arrow_pad - mbw}; 477 arrow_points[1] = (ltk_point){wx + ww / 2 - t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size}; 478 arrow_points[2] = (ltk_point){wx + ww / 2 + t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size}; 479 ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3); 480 } 481 ltk_surface_draw_border_clipped(s, &t->border, (ltk_rect){x, y, lrect.w, lrect.h}, surf_clip, mbw, LTK_BORDER_ALL); 482 483 self->dirty = 0; 484 } 485 486 487 static void 488 ltk_menu_resize(ltk_widget *self) { 489 ltk_menu *menu = (ltk_menu *)self; 490 int max_x, max_y; 491 ltk_menu_get_max_scroll_offset(menu, &max_x, &max_y); 492 if (menu->x_scroll_offset > max_x) 493 menu->x_scroll_offset = max_x; 494 if (menu->y_scroll_offset > max_y) 495 menu->y_scroll_offset = max_y; 496 497 ltk_rect lrect = self->lrect; 498 struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; 499 struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme; 500 501 int ideal_w = self->ideal_w, ideal_h = self->ideal_h; 502 int arrow_size = t->arrow_pad * 2 + t->arrow_size; 503 int start_x = lrect.w < ideal_w ? arrow_size : 0; 504 int start_y = lrect.h < ideal_h ? arrow_size : 0; 505 start_x += t->border_width; 506 start_y += t->border_width; 507 508 int mbw = t->border_width; 509 int cur_abs_x = -(int)menu->x_scroll_offset + start_x + t->pad; 510 int cur_abs_y = -(int)menu->y_scroll_offset + start_y + t->pad; 511 512 for (size_t i = 0; i < menu->num_entries; i++) { 513 ltk_menuentry *e = menu->entries[i]; 514 e->widget.lrect.x = cur_abs_x; 515 e->widget.lrect.y = cur_abs_y; 516 if (menu->is_submenu) { 517 e->widget.lrect.w = ideal_w - 2 * t->pad - 2 * mbw; 518 e->widget.lrect.h = e->widget.ideal_h; 519 cur_abs_y += e->widget.ideal_h + t->pad; 520 if (et->compress_borders) 521 cur_abs_y -= et->border_width; 522 } else { 523 e->widget.lrect.w = e->widget.ideal_w; 524 e->widget.lrect.h = ideal_h - 2 * t->pad - 2 * mbw; 525 cur_abs_x += e->widget.ideal_w + t->pad; 526 if (et->compress_borders) 527 cur_abs_x -= et->border_width; 528 } 529 e->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, e->widget.lrect); 530 } 531 self->dirty = 1; 532 ltk_window_invalidate_widget_rect(self->window, self); 533 } 534 535 static void 536 ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r) { 537 ltk_menu *menu = (ltk_menu *)self; 538 struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; 539 int extra_size = theme->arrow_size + theme->arrow_pad * 2 + theme->border_width; 540 int delta = 0; 541 if (self->lrect.w < (int)self->ideal_w && !menu->is_submenu) { 542 if (r.x + r.w > self->lrect.w - extra_size && r.w <= self->lrect.w - 2 * extra_size) 543 delta = r.x - (self->lrect.w - extra_size - r.w); 544 else if (r.x < extra_size || r.w > self->lrect.w - 2 * extra_size) 545 delta = r.x - extra_size; 546 if (delta) 547 ltk_menu_scroll(menu, 0, 0, 0, 1, delta); 548 } else if (self->lrect.h < (int)self->ideal_h && menu->is_submenu) { 549 if (r.y + r.h > self->lrect.h - extra_size && r.h <= self->lrect.h - 2 * extra_size) 550 delta = r.y - (self->lrect.h - extra_size - r.h); 551 else if (r.y < extra_size || r.h > self->lrect.h - 2 * extra_size) 552 delta = r.y - extra_size; 553 if (delta) 554 ltk_menu_scroll(menu, 0, 1, 0, 0, delta); 555 } 556 } 557 558 static void 559 ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret) { 560 struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; 561 int extra_size = theme->arrow_size * 2 + theme->arrow_pad * 4; 562 *x_ret = 0; 563 *y_ret = 0; 564 if (menu->widget.lrect.w < (int)menu->widget.ideal_w) { 565 *x_ret = menu->widget.ideal_w - (menu->widget.lrect.w - extra_size); 566 } 567 if (menu->widget.lrect.h < (int)menu->widget.ideal_h) { 568 *y_ret = menu->widget.ideal_h - (menu->widget.lrect.h - extra_size); 569 } 570 } 571 572 static void 573 ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step) { 574 int max_scroll_x, max_scroll_y; 575 ltk_menu_get_max_scroll_offset(menu, &max_scroll_x, &max_scroll_y); 576 double y_old = menu->y_scroll_offset; 577 double x_old = menu->x_scroll_offset; 578 if (t) 579 menu->y_scroll_offset -= step; 580 else if (b) 581 menu->y_scroll_offset += step; 582 else if (l) 583 menu->x_scroll_offset -= step; 584 else if (r) 585 menu->x_scroll_offset += step; 586 if (menu->x_scroll_offset < 0) 587 menu->x_scroll_offset = 0; 588 if (menu->y_scroll_offset < 0) 589 menu->y_scroll_offset = 0; 590 if (menu->x_scroll_offset > max_scroll_x) 591 menu->x_scroll_offset = max_scroll_x; 592 if (menu->y_scroll_offset > max_scroll_y) 593 menu->y_scroll_offset = max_scroll_y; 594 /* FIXME: sensible epsilon? */ 595 if (fabs(x_old - menu->x_scroll_offset) > 0.01 || 596 fabs(y_old - menu->y_scroll_offset) > 0.01) { 597 ltk_menu_resize(&menu->widget); 598 menu->widget.dirty = 1; 599 ltk_window_invalidate_widget_rect(menu->widget.window, &menu->widget); 600 } 601 } 602 603 /* FIXME: show scroll arrow disabled when nothing further */ 604 static void 605 ltk_menu_scroll_callback(void *data) { 606 ltk_menu *menu = (ltk_menu *)data; 607 ltk_menu_scroll( 608 menu, 609 menu->scroll_top_hover, menu->scroll_bottom_hover, 610 menu->scroll_left_hover, menu->scroll_right_hover, 2 611 ); 612 } 613 614 static void 615 stop_scrolling(ltk_menu *menu) { 616 menu->scroll_top_hover = 0; 617 menu->scroll_bottom_hover = 0; 618 menu->scroll_left_hover = 0; 619 menu->scroll_right_hover = 0; 620 if (menu->scroll_timer_id >= 0) 621 ltk_unregister_timer(menu->scroll_timer_id); 622 } 623 624 /* FIXME: should ideal_w, ideal_h just be int? */ 625 static ltk_widget * 626 ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) { 627 ltk_menu *menu = (ltk_menu *)self; 628 struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; 629 int arrow_size = t->arrow_size + t->arrow_pad * 2; 630 int mbw = t->border_width; 631 int start_x = mbw, end_x = self->lrect.w - mbw; 632 int start_y = mbw, end_y = self->lrect.h - mbw; 633 if (self->lrect.w < (int)self->ideal_w) { 634 start_x += arrow_size; 635 end_x -= arrow_size; 636 } 637 if (self->lrect.h < (int)self->ideal_h) { 638 start_y += arrow_size; 639 end_y -= arrow_size; 640 } 641 /* FIXME: use crect for this */ 642 if (!ltk_collide_rect((ltk_rect){start_x, start_y, end_x - start_x, end_y - start_y}, x, y)) 643 return NULL; 644 645 for (size_t i = 0; i < menu->num_entries; i++) { 646 if (ltk_collide_rect(menu->entries[i]->widget.crect, x, y)) 647 return &menu->entries[i]->widget; 648 } 649 return NULL; 650 } 651 652 /* FIXME: make sure timers are always destroyed when widget is destroyed */ 653 static int 654 set_scroll_timer(ltk_menu *menu, int x, int y) { 655 /* this check probably isn't necessary, but whatever */ 656 if (x < 0 || y < 0 || x >= menu->widget.lrect.w || y >= menu->widget.lrect.h) 657 return 0; 658 int t = 0, b = 0, l = 0,r = 0; 659 struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme; 660 int arrow_size = theme->arrow_size + theme->arrow_pad * 2; 661 if (menu->widget.lrect.w < (int)menu->widget.ideal_w) { 662 if (x < arrow_size) 663 l = 1; 664 else if (x > menu->widget.lrect.w - arrow_size) 665 r = 1; 666 } 667 if (menu->widget.lrect.h < (int)menu->widget.ideal_h) { 668 if (y < arrow_size) 669 t = 1; 670 else if (y > menu->widget.lrect.h - arrow_size) 671 b = 1; 672 } 673 if (t == menu->scroll_top_hover && 674 b == menu->scroll_bottom_hover && 675 l == menu->scroll_left_hover && 676 r == menu->scroll_right_hover) 677 return 0; 678 stop_scrolling(menu); 679 menu->scroll_top_hover = t; 680 menu->scroll_bottom_hover = b; 681 menu->scroll_left_hover = l; 682 menu->scroll_right_hover = r; 683 ltk_menu_scroll_callback(menu); 684 menu->scroll_timer_id = ltk_register_timer(0, 300, <k_menu_scroll_callback, menu); 685 return 1; 686 } 687 688 /* FIXME: The mouse release handler checks if the mouse collides with the rect of the widget 689 before calling this, but that doesn't work with menuentries because part of their rect may 690 be hidden when scrolling in a menu. Maybe widgets also need a "visible rect"? */ 691 static int 692 ltk_menuentry_release(ltk_widget *self) { 693 ltk_menuentry *e = (ltk_menuentry *)self; 694 int in_submenu = IN_SUBMENU(e); 695 int keep_popup = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus; 696 if (in_submenu || !keep_popup) { 697 ltk_window_unregister_all_popups(self->window); 698 } 699 ltk_queue_specific_event(self, "menu", LTK_PWEVENTMASK_MENUENTRY_PRESS, "press"); 700 return 1; 701 } 702 703 static int 704 ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) { 705 ltk_menu *menu = (ltk_menu *)self; 706 ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y); 707 /* FIXME: configure scroll step */ 708 /* FIXME: fix the interface for ltk_menu_scroll */ 709 if (event->dx > 0) 710 ltk_menu_scroll(menu, 0, 0, 0, 1, event->dx * 10); 711 else if (event->dx < 0) 712 ltk_menu_scroll(menu, 0, 0, 1, 0, -event->dx * 10); 713 if (event->dy > 0) 714 ltk_menu_scroll(menu, 1, 0, 0, 0, event->dy * 10); 715 else if (event->dy < 0) 716 ltk_menu_scroll(menu, 0, 1, 0, 0, -event->dy * 10); 717 ltk_window_fake_motion_event(self->window, glob.x, glob.y); 718 return 1; 719 } 720 721 static void 722 ltk_menu_hide(ltk_widget *self) { 723 ltk_menu *menu = (ltk_menu *)self; 724 if (menu->scroll_timer_id >= 0) 725 ltk_unregister_timer(menu->scroll_timer_id); 726 menu->scroll_bottom_hover = menu->scroll_top_hover = 0; 727 menu->scroll_left_hover = menu->scroll_right_hover = 0; 728 ltk_window_unregister_popup(self->window, self); 729 ltk_window_invalidate_widget_rect(self->window, self); 730 /* FIXME: this is really ugly/hacky */ 731 if (menu->unpopup_submenus_on_hide && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY && 732 self->parent->parent && self->parent->parent->vtable->type == LTK_WIDGET_MENU) { 733 ((ltk_menu *)self->parent->parent)->popup_submenus = 0; 734 } 735 menu->unpopup_submenus_on_hide = 1; 736 } 737 738 /* FIXME: hacky because entries need to know about their parents to be able to properly position the popup */ 739 static void 740 popup_active_menu(ltk_menuentry *e) { 741 if (!e->submenu) 742 return; 743 int in_submenu = 0, was_opened_left = 0; 744 ltk_rect menu_rect = e->widget.lrect; 745 ltk_point entry_global = ltk_widget_pos_to_global(&e->widget, 0, 0); 746 ltk_point menu_global; 747 if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { 748 ltk_menu *menu = (ltk_menu *)e->widget.parent; 749 in_submenu = menu->is_submenu; 750 was_opened_left = menu->was_opened_left; 751 menu_rect = menu->widget.lrect; 752 menu_global = ltk_widget_pos_to_global(e->widget.parent, 0, 0); 753 } else { 754 menu_global = ltk_widget_pos_to_global(&e->widget, 0, 0); 755 } 756 int win_w = e->widget.window->rect.w; 757 int win_h = e->widget.window->rect.h; 758 ltk_menu *submenu = e->submenu; 759 int ideal_w = submenu->widget.ideal_w; 760 int ideal_h = submenu->widget.ideal_h; 761 int x_final = 0, y_final = 0, w_final = ideal_w, h_final = ideal_h; 762 if (in_submenu) { 763 int space_left = menu_global.x; 764 int space_right = win_w - (menu_global.x + menu_rect.w); 765 int x_right = menu_global.x + menu_rect.w; 766 int x_left = menu_global.x - ideal_w; 767 if (submenu_theme.compress_borders) { 768 x_right -= submenu_theme.border_width; 769 x_left += submenu_theme.border_width; 770 } 771 if (was_opened_left) { 772 if (x_left >= 0) { 773 x_final = x_left; 774 submenu->was_opened_left = 1; 775 } else if (space_right >= ideal_w) { 776 x_final = x_right; 777 submenu->was_opened_left = 0; 778 } else { 779 x_final = 0; 780 if (win_w < ideal_w) 781 w_final = win_w; 782 submenu->was_opened_left = 1; 783 } 784 } else { 785 if (space_right >= ideal_w) { 786 x_final = x_right; 787 submenu->was_opened_left = 0; 788 } else if (space_left >= ideal_w) { 789 x_final = x_left; 790 submenu->was_opened_left = 1; 791 } else { 792 x_final = win_w - ideal_w; 793 if (x_final < 0) { 794 x_final = 0; 795 w_final = win_w; 796 } 797 submenu->was_opened_left = 0; 798 } 799 } 800 /* subtract padding and border width so the actual entries are at the right position */ 801 y_final = entry_global.y - submenu_theme.pad - submenu_theme.border_width; 802 if (y_final + ideal_h > win_h) 803 y_final = win_h - ideal_h; 804 if (y_final < 0) { 805 y_final = 0; 806 h_final = win_h; 807 } 808 } else { 809 int space_top = menu_global.y; 810 int space_bottom = win_h - (menu_global.y + menu_rect.h); 811 int y_top = menu_global.y - ideal_h; 812 int y_bottom = menu_global.y + menu_rect.h; 813 if (menu_theme.compress_borders) { 814 y_top += menu_theme.border_width; 815 y_bottom -= menu_theme.border_width; 816 } 817 if (space_top > space_bottom) { 818 y_final = y_top; 819 if (y_final < 0) { 820 y_final = 0; 821 h_final = menu_rect.y; 822 } 823 submenu->was_opened_above = 1; 824 } else { 825 y_final = y_bottom; 826 if (space_bottom < ideal_h) 827 h_final = space_bottom; 828 submenu->was_opened_above = 0; 829 } 830 /* FIXME: maybe threshold so there's always at least a part of 831 the menu contents shown (instead of maybe just a few pixels) */ 832 /* pathological case where window is way too small */ 833 if (h_final <= 0) { 834 y_final = 0; 835 h_final = win_h; 836 } 837 x_final = entry_global.x; 838 if (x_final + ideal_w > win_w) 839 x_final = win_w - ideal_w; 840 if (x_final < 0) { 841 x_final = 0; 842 w_final = win_w; 843 } 844 } 845 /* reset everything just in case */ 846 submenu->x_scroll_offset = submenu->y_scroll_offset = 0; 847 submenu->scroll_top_hover = submenu->scroll_bottom_hover = 0; 848 submenu->scroll_left_hover = submenu->scroll_right_hover = 0; 849 submenu->widget.lrect.x = x_final; 850 submenu->widget.lrect.y = y_final; 851 submenu->widget.lrect.w = w_final; 852 submenu->widget.lrect.h = h_final; 853 submenu->widget.crect = submenu->widget.lrect; 854 submenu->widget.dirty = 1; 855 submenu->widget.hidden = 0; 856 submenu->popup_submenus = 0; 857 submenu->unpopup_submenus_on_hide = 1; 858 ltk_menu_resize(&submenu->widget); 859 ltk_window_register_popup(e->widget.window, (ltk_widget *)submenu); 860 ltk_window_invalidate_widget_rect(submenu->widget.window, &submenu->widget); 861 } 862 863 static void 864 unpopup_active_entry(ltk_menuentry *e) { 865 if (e->submenu && !e->submenu->widget.hidden) { 866 e->submenu->unpopup_submenus_on_hide = 0; 867 ltk_widget_hide(&e->submenu->widget); 868 } 869 } 870 871 static int 872 ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event) { 873 set_scroll_timer((ltk_menu *)self, event->x, event->y); 874 return 1; 875 } 876 877 static int 878 ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event) { 879 set_scroll_timer((ltk_menu *)self, event->x, event->y); 880 return 1; 881 } 882 883 static int 884 ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event) { 885 (void)event; 886 stop_scrolling((ltk_menu *)self); 887 return 1; 888 } 889 890 static ltk_menu * 891 ltk_menu_create(ltk_window *window, const char *id, int is_submenu) { 892 ltk_menu *menu = ltk_malloc(sizeof(ltk_menu)); 893 menu->widget.ideal_w = menu_theme.pad; 894 menu->widget.ideal_h = menu_theme.pad; 895 ltk_fill_widget_defaults(&menu->widget, id, window, &vtable, menu->widget.ideal_w, menu->widget.ideal_h); 896 menu->widget.dirty = 1; 897 898 menu->entries = NULL; 899 menu->num_entries = menu->num_alloc = 0; 900 menu->x_scroll_offset = menu->y_scroll_offset = 0; 901 menu->is_submenu = is_submenu; 902 menu->was_opened_left = 0; 903 menu->was_opened_above = 0; 904 menu->scroll_timer_id = -1; 905 menu->scroll_top_hover = menu->scroll_bottom_hover = 0; 906 menu->scroll_left_hover = menu->scroll_right_hover = 0; 907 menu->popup_submenus = 0; 908 menu->unpopup_submenus_on_hide = 1; 909 /* FIXME: hide widget by default so recalc doesn't cause 910 unnecessary redrawing */ 911 recalc_ideal_menu_size(&menu->widget, NULL); 912 913 return menu; 914 } 915 916 static int 917 insert_entry(ltk_menu *menu, ltk_menuentry *e, size_t idx) { 918 if (idx > menu->num_entries) 919 return 1; 920 if (menu->num_entries == menu->num_alloc) { 921 menu->num_alloc = ideal_array_size(menu->num_alloc, menu->num_entries + 1); 922 menu->entries = ltk_reallocarray(menu->entries, menu->num_alloc, sizeof(ltk_menuentry *)); 923 } 924 memmove( 925 menu->entries + idx + 1, 926 menu->entries + idx, 927 sizeof(ltk_menuentry *) * (menu->num_entries - idx) 928 ); 929 menu->num_entries++; 930 menu->entries[idx] = e; 931 return 0; 932 } 933 934 static void 935 recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget) { 936 ltk_menu *menu = (ltk_menu *)self; 937 /* If widget with size change is submenu, it doesn't affect this menu */ 938 if (widget && widget->vtable->type == LTK_WIDGET_MENU) { 939 ltk_widget_resize(widget); 940 return; 941 } 942 struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme; 943 struct entry_theme *et = menu->is_submenu ? &submenu_entry_theme : &menu_entry_theme; 944 unsigned int old_w = menu->widget.ideal_w, old_h = menu->widget.ideal_h; 945 menu->widget.ideal_w = menu->widget.ideal_h = t->pad + t->border_width * 2; 946 ltk_menuentry *e; 947 for (size_t i = 0; i < menu->num_entries; i++) { 948 e = menu->entries[i]; 949 if (menu->is_submenu) { 950 menu->widget.ideal_w = MAX((t->pad + t->border_width) * 2 + (int)e->widget.ideal_w, (int)menu->widget.ideal_w); 951 menu->widget.ideal_h += e->widget.ideal_h + t->pad; 952 if (et->compress_borders && i != 0) 953 menu->widget.ideal_h -= et->border_width; 954 } else { 955 menu->widget.ideal_w += e->widget.ideal_w + t->pad; 956 if (et->compress_borders && i != 0) 957 menu->widget.ideal_w -= et->border_width; 958 menu->widget.ideal_h = MAX((t->pad + t->border_width) * 2 + (int)e->widget.ideal_h, (int)menu->widget.ideal_h); 959 } 960 } 961 if ((old_w != menu->widget.ideal_w || old_h != menu->widget.ideal_h) && 962 menu->widget.parent && menu->widget.parent->vtable->child_size_change) { 963 menu->widget.parent->vtable->child_size_change(menu->widget.parent, (ltk_widget *)menu); 964 } else { 965 ltk_menu_resize(self); 966 } 967 menu->widget.dirty = 1; 968 if (!menu->widget.hidden) 969 ltk_window_invalidate_widget_rect(menu->widget.window, &menu->widget); 970 } 971 972 static void 973 ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry) { 974 int in_submenu = IN_SUBMENU(entry); 975 struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme; 976 int extra_size = (in_submenu && entry->submenu) ? t->arrow_pad * 2 + t->arrow_size : 0; 977 978 int text_w, text_h; 979 ltk_text_line_get_size(entry->text_line, &text_w, &text_h); 980 entry->widget.ideal_w = text_w + extra_size + (t->text_pad + t->border_width) * 2; 981 entry->widget.ideal_h = MAX(text_h + t->text_pad * 2, extra_size) + t->border_width * 2; 982 /* FIXME: only call if something changed */ 983 if (entry->widget.parent && entry->widget.parent->vtable->child_size_change) { 984 entry->widget.parent->vtable->child_size_change(entry->widget.parent, (ltk_widget *)entry); 985 } 986 } 987 988 static ltk_menuentry * 989 ltk_menuentry_create(ltk_window *window, const char *id, const char *text) { 990 ltk_menuentry *e = ltk_malloc(sizeof(ltk_menuentry)); 991 e->text_line = ltk_text_line_create(window->text_context, window->theme->font_size, (char *)text, 0, -1); 992 e->submenu = NULL; 993 int w, h; 994 ltk_text_line_get_size(e->text_line, &w, &h); 995 e->text_surface_key = ltk_surface_cache_get_unnamed_key(window->surface_cache, w, h); 996 ltk_fill_widget_defaults(&e->widget, id, window, &entry_vtable, 5, 5); 997 /* Note: This is only set as a dummy value! The actual ideal size can't 998 be set until it is part of a menu because it needs to know which 999 theme it should use */ 1000 ltk_menuentry_recalc_ideal_size(e); 1001 e->widget.dirty = 1; 1002 return e; 1003 } 1004 1005 static int 1006 ltk_menuentry_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err) { 1007 ltk_menuentry *e = (ltk_menuentry *)self; 1008 if (widget != &e->submenu->widget) { 1009 err->type = ERR_WIDGET_NOT_IN_CONTAINER; 1010 return 1; 1011 } 1012 widget->parent = NULL; 1013 e->submenu = NULL; 1014 ltk_menuentry_recalc_ideal_size(e); 1015 return 0; 1016 } 1017 1018 static void 1019 ltk_menuentry_destroy(ltk_widget *self, int shallow) { 1020 ltk_menuentry *e = (ltk_menuentry *)self; 1021 ltk_text_line_destroy(e->text_line); 1022 ltk_surface_cache_release_key(e->text_surface_key); 1023 /* FIXME: function to call when parent is destroyed */ 1024 /* also function to call when parent added */ 1025 if (e->submenu) { 1026 e->submenu->widget.parent = NULL; 1027 if (!shallow) { 1028 ltk_error err; 1029 ltk_widget_destroy(&e->submenu->widget, shallow, &err); 1030 } 1031 } 1032 ltk_free(e); 1033 } 1034 1035 static int 1036 ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx, ltk_error *err) { 1037 if (entry->widget.parent) { 1038 err->type = ERR_WIDGET_IN_CONTAINER; 1039 return 1; 1040 } 1041 if (insert_entry(menu, entry, idx)) { 1042 err->type = ERR_INVALID_INDEX; 1043 return 1; 1044 } 1045 entry->widget.parent = &menu->widget; 1046 ltk_menuentry_recalc_ideal_size(entry); 1047 recalc_ideal_menu_size(&menu->widget, NULL); 1048 menu->widget.dirty = 1; 1049 return 0; 1050 } 1051 1052 static int 1053 ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry, ltk_error *err) { 1054 return ltk_menu_insert_entry(menu, entry, menu->num_entries, err); 1055 } 1056 1057 /* FIXME: maybe allow any menu and just change is_submenu (also need to recalculate size then) */ 1058 static int 1059 ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err) { 1060 if (!submenu->is_submenu) { 1061 err->type = ERR_MENU_NOT_SUBMENU; 1062 return 1; 1063 } else if (e->submenu) { 1064 err->type = ERR_MENU_ENTRY_CONTAINS_SUBMENU; 1065 return 1; 1066 } 1067 e->submenu = submenu; 1068 ltk_menuentry_recalc_ideal_size(e); 1069 e->widget.dirty = 1; 1070 if (submenu) { 1071 submenu->widget.hidden = 1; 1072 submenu->widget.parent = &e->widget; 1073 } 1074 if (!e->widget.hidden) 1075 ltk_window_invalidate_widget_rect(e->widget.window, &e->widget); 1076 return 0; 1077 } 1078 1079 /* FIXME: hide all entries when menu hidden? */ 1080 1081 static void 1082 shrink_entries(ltk_menu *menu) { 1083 size_t new_alloc = ideal_array_size(menu->num_alloc, menu->num_entries); 1084 if (new_alloc != menu->num_alloc) { 1085 menu->entries = ltk_reallocarray(menu->entries, new_alloc, sizeof(ltk_menuentry *)); 1086 menu->num_alloc = new_alloc; 1087 } 1088 } 1089 1090 static int 1091 ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, ltk_error *err) { 1092 if (idx >= menu->num_entries) { 1093 err->type = ERR_INVALID_INDEX; 1094 return 1; 1095 } 1096 menu->entries[idx]->widget.parent = NULL; 1097 ltk_menuentry_recalc_ideal_size(menu->entries[idx]); 1098 memmove( 1099 menu->entries + idx, 1100 menu->entries + idx + 1, 1101 sizeof(ltk_menuentry *) * (menu->num_entries - idx - 1) 1102 ); 1103 menu->num_entries--; 1104 shrink_entries(menu); 1105 recalc_ideal_menu_size(&menu->widget, NULL); 1106 return 0; 1107 } 1108 1109 static size_t 1110 get_entry_with_id(ltk_menu *menu, const char *id) { 1111 for (size_t i = 0; i < menu->num_entries; i++) { 1112 if (!strcmp(id, menu->entries[i]->widget.id)) 1113 return i; 1114 } 1115 return SIZE_MAX; 1116 } 1117 1118 static int 1119 ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, ltk_error *err) { 1120 size_t idx = get_entry_with_id(menu, id); 1121 if (idx >= menu->num_entries) { 1122 err->type = ERR_INVALID_WIDGET_ID; 1123 return 1; 1124 } 1125 return ltk_menu_remove_entry_index(menu, idx, err); 1126 } 1127 1128 static int 1129 ltk_menu_remove_child(ltk_widget *child, ltk_widget *self, ltk_error *err) { 1130 ltk_menu *menu = (ltk_menu *)self; 1131 for (size_t i = 0; i < menu->num_entries; i++) { 1132 if (&menu->entries[i]->widget == child) { 1133 return ltk_menu_remove_entry_index(menu, i, err); 1134 } 1135 } 1136 err->type = ERR_WIDGET_NOT_IN_CONTAINER; 1137 return 1; 1138 } 1139 1140 static void 1141 ltk_menu_remove_all_entries(ltk_menu *menu) { 1142 for (size_t i = 0; i < menu->num_entries; i++) { 1143 menu->entries[i]->widget.parent = NULL; 1144 ltk_menuentry_recalc_ideal_size(menu->entries[i]); 1145 } 1146 menu->num_entries = menu->num_alloc = 0; 1147 ltk_free(menu->entries); 1148 menu->entries = NULL; 1149 recalc_ideal_menu_size(&menu->widget, NULL); 1150 } 1151 1152 static ltk_widget * 1153 ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect) { 1154 ltk_menu *menu = (ltk_menu *)self; 1155 ltk_widget *minw = NULL; 1156 int min_dist = INT_MAX; 1157 int cx = rect.x + rect.w / 2; 1158 int cy = rect.y + rect.h / 2; 1159 ltk_rect r; 1160 int dist; 1161 for (size_t i = 0; i < menu->num_entries; i++) { 1162 r = menu->entries[i]->widget.lrect; 1163 dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy); 1164 if (dist < min_dist) { 1165 min_dist = dist; 1166 minw = &menu->entries[i]->widget; 1167 } 1168 } 1169 return minw; 1170 } 1171 1172 /* FIXME: These need to be updated if all menus are allowed to be horizontal or vertical! */ 1173 /* FIXME: this doesn't work properly when parent and child are both on the same side, 1174 but I guess there's no good way to fix that */ 1175 /* FIXME: behavior is a bit weird when e.g. moving down when every active menu entry in hierarchy 1176 is already at bottom of respective menu - the top-level menu will give the first submenu in 1177 the current active hierarchy as child widget again, and nearest_child on that submenu will 1178 (probably) give the bottom widget again, so nothing changes except that all submenus except 1179 for the first and second one disappeare */ 1180 static ltk_widget * 1181 ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget) { 1182 ltk_menu *menu = (ltk_menu *)self; 1183 ltk_widget *left = NULL; 1184 if (!menu->is_submenu) { 1185 left = ltk_menu_prev_child(self, widget); 1186 } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY && 1187 ((ltk_menuentry *)widget)->submenu && 1188 !((ltk_menuentry *)widget)->submenu->widget.hidden && 1189 ((ltk_menuentry *)widget)->submenu->was_opened_left) { 1190 left = &((ltk_menuentry *)widget)->submenu->widget; 1191 } else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { 1192 ltk_menuentry *e = (ltk_menuentry *)self->parent; 1193 if (!menu->was_opened_left && IN_SUBMENU(e)) { 1194 left = self->parent; 1195 } else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { 1196 left = ltk_menu_prev_child(e->widget.parent, &e->widget); 1197 } 1198 } 1199 return left; 1200 } 1201 1202 static ltk_widget * 1203 ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget) { 1204 ltk_menu *menu = (ltk_menu *)self; 1205 ltk_widget *right = NULL; 1206 if (!menu->is_submenu) { 1207 right = ltk_menu_next_child(self, widget); 1208 } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY && 1209 ((ltk_menuentry *)widget)->submenu && 1210 !((ltk_menuentry *)widget)->submenu->widget.hidden && 1211 !((ltk_menuentry *)widget)->submenu->was_opened_left) { 1212 right = &((ltk_menuentry *)widget)->submenu->widget; 1213 } else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { 1214 ltk_menuentry *e = (ltk_menuentry *)self->parent; 1215 if (menu->was_opened_left && IN_SUBMENU(e)) { 1216 right = self->parent; 1217 } else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { 1218 right = ltk_menu_next_child(e->widget.parent, &e->widget); 1219 } 1220 } 1221 return right; 1222 } 1223 1224 static ltk_widget * 1225 ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget) { 1226 ltk_menu *menu = (ltk_menu *)self; 1227 ltk_widget *above = NULL; 1228 if (menu->is_submenu) { 1229 above = ltk_menu_prev_child(self, widget); 1230 if (!above && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { 1231 ltk_menuentry *e = (ltk_menuentry *)self->parent; 1232 if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { 1233 ltk_menu *pmenu = (ltk_menu *)e->widget.parent; 1234 if (!menu->was_opened_above && !pmenu->is_submenu) { 1235 above = self->parent; 1236 } else if (pmenu->is_submenu) { 1237 above = ltk_menu_prev_child(e->widget.parent, &e->widget); 1238 } 1239 } 1240 } 1241 } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) { 1242 ltk_menuentry *e = (ltk_menuentry *)widget; 1243 if (e->submenu && !e->submenu->widget.hidden && e->submenu->was_opened_above) { 1244 above = &e->submenu->widget; 1245 } 1246 } 1247 return above; 1248 } 1249 1250 static ltk_widget * 1251 ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget) { 1252 ltk_menu *menu = (ltk_menu *)self; 1253 ltk_widget *below = NULL; 1254 if (menu->is_submenu) { 1255 below = ltk_menu_next_child(self, widget); 1256 if (!below && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) { 1257 ltk_menuentry *e = (ltk_menuentry *)self->parent; 1258 if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) { 1259 ltk_menu *pmenu = (ltk_menu *)e->widget.parent; 1260 if (menu->was_opened_above && !pmenu->is_submenu) { 1261 below = self->parent; 1262 } else if (pmenu->is_submenu) { 1263 below = ltk_menu_next_child(e->widget.parent, &e->widget); 1264 } 1265 } 1266 } 1267 } else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) { 1268 ltk_menuentry *e = (ltk_menuentry *)widget; 1269 if (e->submenu && !e->submenu->widget.hidden && !e->submenu->was_opened_above) { 1270 below = &e->submenu->widget; 1271 } 1272 } 1273 return below; 1274 } 1275 1276 static ltk_widget * 1277 ltk_menu_prev_child(ltk_widget *self, ltk_widget *child) { 1278 ltk_menu *menu = (ltk_menu *)self; 1279 for (size_t i = menu->num_entries; i-- > 0;) { 1280 if (&menu->entries[i]->widget == child) 1281 return i > 0 ? &menu->entries[i-1]->widget : NULL; 1282 } 1283 return NULL; 1284 } 1285 1286 static ltk_widget * 1287 ltk_menu_next_child(ltk_widget *self, ltk_widget *child) { 1288 ltk_menu *menu = (ltk_menu *)self; 1289 for (size_t i = 0; i < menu->num_entries; i++) { 1290 if (&menu->entries[i]->widget == child) 1291 return i < menu->num_entries - 1 ? &menu->entries[i+1]->widget : NULL; 1292 } 1293 return NULL; 1294 } 1295 1296 static ltk_widget * 1297 ltk_menu_first_child(ltk_widget *self) { 1298 ltk_menu *menu = (ltk_menu *)self; 1299 return menu->num_entries > 0 ? &menu->entries[0]->widget : NULL; 1300 } 1301 1302 static ltk_widget * 1303 ltk_menu_last_child(ltk_widget *self) { 1304 ltk_menu *menu = (ltk_menu *)self; 1305 return menu->num_entries > 0 ? &menu->entries[menu->num_entries-1]->widget : NULL; 1306 } 1307 1308 /* FIXME: unregister from window popups? */ 1309 static void 1310 ltk_menuentry_detach_submenu(ltk_menuentry *e) { 1311 if (e->submenu) 1312 e->submenu->widget.parent = NULL; 1313 e->submenu = NULL; 1314 ltk_menuentry_recalc_ideal_size(e); 1315 } 1316 1317 static void 1318 ltk_menu_destroy(ltk_widget *self, int shallow) { 1319 ltk_menu *menu = (ltk_menu *)self; 1320 if (!menu) { 1321 ltk_warn("Tried to destroy NULL menu.\n"); 1322 return; 1323 } 1324 if (menu->scroll_timer_id >= 0) 1325 ltk_unregister_timer(menu->scroll_timer_id); 1326 ltk_window_unregister_popup(self->window, self); 1327 if (!shallow) { 1328 ltk_error err; 1329 for (size_t i = 0; i < menu->num_entries; i++) { 1330 /* for efficiency - to avoid ltk_widget_destroy calling 1331 ltk_menu_remove_child for each of the entries */ 1332 menu->entries[i]->widget.parent = NULL; 1333 ltk_widget_destroy(&menu->entries[i]->widget, shallow, &err); 1334 } 1335 ltk_free(menu->entries); 1336 } else { 1337 ltk_menu_remove_all_entries(menu); 1338 } 1339 ltk_free(menu); 1340 } 1341 1342 /* TODO: get-index-for-id */ 1343 1344 /* [sub]menu <menu id> create */ 1345 static int 1346 ltk_menu_cmd_create( 1347 ltk_window *window, 1348 ltk_menu *menu_unneeded, 1349 ltk_cmd_token *tokens, 1350 size_t num_tokens, 1351 ltk_error *err) { 1352 (void)menu_unneeded; 1353 ltk_cmdarg_parseinfo cmd[] = { 1354 {.type = CMDARG_STRING, .optional = 0}, 1355 {.type = CMDARG_STRING, .optional = 0}, 1356 {.type = CMDARG_IGNORE, .optional = 0}, 1357 }; 1358 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 1359 return 1; 1360 if (!ltk_widget_id_free(cmd[1].val.str)) { 1361 err->type = ERR_WIDGET_ID_IN_USE; 1362 err->arg = 1; 1363 return 1; 1364 } 1365 ltk_menu *menu; 1366 if (!strcmp(cmd[0].val.str, "menu")) { 1367 menu = ltk_menu_create(window, cmd[1].val.str, 0); 1368 } else { 1369 menu = ltk_menu_create(window, cmd[1].val.str, 1); 1370 } 1371 ltk_set_widget((ltk_widget *)menu, cmd[1].val.str); 1372 return 0; 1373 } 1374 1375 /* menu <menu id> insert-entry <entry widget id> <index> */ 1376 static int 1377 ltk_menu_cmd_insert_entry( 1378 ltk_window *window, 1379 ltk_menu *menu, 1380 ltk_cmd_token *tokens, 1381 size_t num_tokens, 1382 ltk_error *err) { 1383 ltk_cmdarg_parseinfo cmd[] = { 1384 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENUENTRY, .optional = 0}, 1385 {.type = CMDARG_INT, .min = 0, .max = menu->num_entries, .optional = 0}, 1386 }; 1387 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 1388 return 1; 1389 if (ltk_menu_insert_entry(menu, (ltk_menuentry *)cmd[0].val.widget, cmd[1].val.i, err)) { 1390 err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 0 : 1; 1391 return 1; 1392 } 1393 return 0; 1394 } 1395 1396 /* menu <menu id> add-entry <entry widget id> */ 1397 static int 1398 ltk_menu_cmd_add_entry( 1399 ltk_window *window, 1400 ltk_menu *menu, 1401 ltk_cmd_token *tokens, 1402 size_t num_tokens, 1403 ltk_error *err) { 1404 ltk_cmdarg_parseinfo cmd[] = { 1405 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENUENTRY, .optional = 0}, 1406 }; 1407 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 1408 return 1; 1409 if (ltk_menu_add_entry(menu, (ltk_menuentry *)cmd[0].val.widget, err)) { 1410 err->arg = 0; 1411 return 1; 1412 } 1413 return 0; 1414 } 1415 1416 /* menu <menu id> remove-entry-index <entry index> */ 1417 static int 1418 ltk_menu_cmd_remove_entry_index( 1419 ltk_window *window, 1420 ltk_menu *menu, 1421 ltk_cmd_token *tokens, 1422 size_t num_tokens, 1423 ltk_error *err) { 1424 ltk_cmdarg_parseinfo cmd[] = { 1425 {.type = CMDARG_INT, .min = 0, .max = menu->num_entries - 1, .optional = 0}, 1426 }; 1427 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 1428 return 1; 1429 if (!ltk_menu_remove_entry_index(menu, cmd[0].val.i, err)) { 1430 err->arg = 0; 1431 return 1; 1432 } 1433 return 0; 1434 } 1435 1436 /* menu <menu id> remove-entry-id <entry id> */ 1437 static int 1438 ltk_menu_cmd_remove_entry_id( 1439 ltk_window *window, 1440 ltk_menu *menu, 1441 ltk_cmd_token *tokens, 1442 size_t num_tokens, 1443 ltk_error *err) { 1444 ltk_cmdarg_parseinfo cmd[] = { 1445 {.type = CMDARG_STRING, .optional = 0}, 1446 }; 1447 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 1448 return 1; 1449 if (!ltk_menu_remove_entry_id(menu, cmd[0].val.str, err)) { 1450 err->arg = 0; 1451 return 1; 1452 } 1453 return 0; 1454 } 1455 1456 /* menu <menu id> remove-all-entries */ 1457 static int 1458 ltk_menu_cmd_remove_all_entries( 1459 ltk_window *window, 1460 ltk_menu *menu, 1461 ltk_cmd_token *tokens, 1462 size_t num_tokens, 1463 ltk_error *err) { 1464 (void)window; 1465 (void)tokens; 1466 (void)num_tokens; 1467 (void)err; 1468 ltk_menu_remove_all_entries(menu); 1469 return 0; 1470 } 1471 1472 /* menuentry <id> create <text> */ 1473 static int 1474 ltk_menuentry_cmd_create( 1475 ltk_window *window, 1476 ltk_menuentry *e_unneeded, 1477 ltk_cmd_token *tokens, 1478 size_t num_tokens, 1479 ltk_error *err) { 1480 (void)e_unneeded; 1481 ltk_cmdarg_parseinfo cmd[] = { 1482 {.type = CMDARG_STRING, .optional = 0}, 1483 {.type = CMDARG_STRING, .optional = 0}, 1484 {.type = CMDARG_IGNORE, .optional = 0}, 1485 {.type = CMDARG_STRING, .optional = 0}, 1486 }; 1487 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 1488 return 1; 1489 if (!ltk_widget_id_free(cmd[1].val.str)) { 1490 err->type = ERR_WIDGET_ID_IN_USE; 1491 err->arg = 1; 1492 return 1; 1493 } 1494 ltk_menuentry *e = ltk_menuentry_create(window, cmd[1].val.str, cmd[3].val.str); 1495 ltk_set_widget((ltk_widget *)e, cmd[1].val.str); 1496 return 0; 1497 } 1498 1499 /* menuentry <menuentry id> attach-submenu <submenu id> */ 1500 static int 1501 ltk_menuentry_cmd_attach_submenu( 1502 ltk_window *window, 1503 ltk_menuentry *e, 1504 ltk_cmd_token *tokens, 1505 size_t num_tokens, 1506 ltk_error *err) { 1507 ltk_cmdarg_parseinfo cmd[] = { 1508 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENU, .optional = 0}, 1509 }; 1510 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 1511 return 1; 1512 if (ltk_menuentry_attach_submenu(e, (ltk_menu *)cmd[0].val.widget, err)) { 1513 /* FIXME: allow setting err->arg to arg before the args given to function */ 1514 /*err->arg = err->type == ERR_MENU_NOT_SUBMENU ? 0 : -2;*/ 1515 err->arg = 0; 1516 return 1; 1517 } 1518 return 0; 1519 } 1520 1521 /* menuentry <menuentry id> detach-submenu */ 1522 static int 1523 ltk_menuentry_cmd_detach_submenu( 1524 ltk_window *window, 1525 ltk_menuentry *e, 1526 ltk_cmd_token *tokens, 1527 size_t num_tokens, 1528 ltk_error *err) { 1529 (void)window; 1530 (void)tokens; 1531 (void)num_tokens; 1532 (void)err; 1533 ltk_menuentry_detach_submenu(e); 1534 return 0; 1535 } 1536 1537 /* FIXME: sort out menu/submenu - it's weird right now */ 1538 /* FIXME: distinguish between menu/submenu in commands other than create? */ 1539 1540 static struct menu_cmd { 1541 char *name; 1542 int (*func)(ltk_window *, ltk_menu *, ltk_cmd_token *, size_t, ltk_error *); 1543 int needs_all; 1544 } menu_cmds[] = { 1545 {"add-entry", <k_menu_cmd_add_entry, 0}, 1546 {"create", <k_menu_cmd_create, 1}, 1547 {"insert-entry", <k_menu_cmd_insert_entry, 0}, 1548 {"remove-all-entries", <k_menu_cmd_remove_all_entries, 0}, 1549 {"remove-entry-index", <k_menu_cmd_remove_entry_index, 0}, 1550 {"remove-entry-id", <k_menu_cmd_remove_entry_id, 0}, 1551 }; 1552 1553 static struct menuentry_cmd { 1554 char *name; 1555 int (*func)(ltk_window *, ltk_menuentry *, ltk_cmd_token *, size_t, ltk_error *); 1556 int needs_all; 1557 } menuentry_cmds[] = { 1558 {"attach-submenu", <k_menuentry_cmd_attach_submenu, 0}, 1559 {"create", <k_menuentry_cmd_create, 1}, 1560 {"detach-submenu", <k_menuentry_cmd_detach_submenu, 0}, 1561 }; 1562 1563 GEN_CMD_HELPERS(ltk_menu_cmd, LTK_WIDGET_MENU, ltk_menu, menu_cmds, struct menu_cmd) 1564 GEN_CMD_HELPERS(ltk_menuentry_cmd, LTK_WIDGET_MENUENTRY, ltk_menuentry, menuentry_cmds, struct menuentry_cmd)