1 #include "allegro5/allegro.h"
2 #include "allegro5/allegro_native_dialog.h"
3 #include "allegro5/internal/aintern_native_dialog.h"
4 #include "allegro5/internal/aintern_dtor.h"
5 #include "allegro5/internal/aintern_system.h"
6 #include "allegro5/internal/aintern_vector.h"
7
8 /* DISPLAY_MENU keeps track of which menu is associated with a display */
9 typedef struct DISPLAY_MENU DISPLAY_MENU;
10
11 struct DISPLAY_MENU
12 {
13 ALLEGRO_DISPLAY *display;
14 ALLEGRO_MENU *menu;
15 };
16
17 static _AL_VECTOR display_menus = _AL_VECTOR_INITIALIZER(DISPLAY_MENU);
18
19 /* The unique id. This is used to reverse lookup menus.
20 * The primarily need for this arises from Windows, which cannot store
21 * ALLEGRO_MENU_ID's wholesale.*/
22 static uint16_t unique_id;
23 static _AL_VECTOR menu_ids = _AL_VECTOR_INITIALIZER(_AL_MENU_ID);
24
25 /* The default event source is used with any menu that does not have
26 its own event source enabled. */
27 static ALLEGRO_EVENT_SOURCE default_menu_es;
28
29 /* Private functions */
30 static ALLEGRO_MENU *clone_menu(ALLEGRO_MENU *menu, bool popup);
31 static ALLEGRO_MENU_ITEM *create_menu_item(char const *title, uint16_t id, int flags, ALLEGRO_MENU *popup);
32 static void destroy_menu_item(ALLEGRO_MENU_ITEM *item);
33 static bool find_menu_item_r(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item, int index, void *extra);
34 static ALLEGRO_MENU_ITEM *interpret_menu_id_param(ALLEGRO_MENU **menu, int *id);
35 static ALLEGRO_MENU_INFO *parse_menu_info(ALLEGRO_MENU *parent, ALLEGRO_MENU_INFO *info);
36 static bool set_menu_display_r(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item, int index, void *extra);
37
38 /* True if the id is actually unique.
39 */
get_unique_id(uint16_t * id)40 static bool get_unique_id(uint16_t* id)
41 {
42 if (unique_id + 1 == UINT16_MAX) {
43 return false;
44 }
45 *id = unique_id++;
46 return true;
47 }
48
49 /* The menu item owns the icon bitmap. It is converted to a memory bitmap
50 * when set to make sure any system threads will be able to read the data.
51 */
set_item_icon(ALLEGRO_MENU_ITEM * item,ALLEGRO_BITMAP * icon)52 static void set_item_icon(ALLEGRO_MENU_ITEM *item, ALLEGRO_BITMAP *icon)
53 {
54 item->icon = icon;
55
56 if (icon && al_get_bitmap_flags(item->icon) & ALLEGRO_VIDEO_BITMAP) {
57 int old_flags = al_get_new_bitmap_flags();
58 al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
59 item->icon = al_clone_bitmap(icon);
60 al_destroy_bitmap(icon);
61 al_set_new_bitmap_flags(old_flags);
62 }
63 }
64
create_menu_item(char const * title,uint16_t id,int flags,ALLEGRO_MENU * popup)65 static ALLEGRO_MENU_ITEM *create_menu_item(char const *title, uint16_t id, int flags, ALLEGRO_MENU *popup)
66 {
67 ALLEGRO_MENU_ITEM *item = al_calloc(1, sizeof(*item));
68 if (!item) return NULL;
69 if (!get_unique_id(&item->unique_id)) {
70 return NULL;
71 }
72
73 if (flags & ALLEGRO_MENU_ITEM_CHECKED)
74 flags |= ALLEGRO_MENU_ITEM_CHECKBOX;
75
76 if (title)
77 item->caption = al_ustr_new(title);
78 item->id = id;
79 item->flags = flags;
80 item->popup = popup;
81
82 return item;
83 }
84
85 /* Recursively walks over the entire menu structure, calling the user proc once per menu item,
86 * and once per menu. The deepest menu is called first. If the proc returns true, then the
87 * process terminates. It is not safe for the proc to modify the structure (add/remove items).
88 */
_al_walk_over_menu(ALLEGRO_MENU * menu,bool (* proc)(ALLEGRO_MENU * menu,ALLEGRO_MENU_ITEM * item,int index,void * extra),void * extra)89 bool _al_walk_over_menu(ALLEGRO_MENU *menu,
90 bool (*proc)(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item, int index, void *extra), void *extra)
91 {
92 ALLEGRO_MENU_ITEM **slot;
93 size_t i;
94 ASSERT(menu);
95 ASSERT(proc);
96
97 for (i = 0; i < _al_vector_size(&menu->items); ++i) {
98 slot = _al_vector_ref(&menu->items, i);
99
100 if ((*slot)->popup && _al_walk_over_menu((*slot)->popup, proc, extra))
101 return true;
102
103 if (proc(menu, *slot, i, extra))
104 return true;
105 }
106
107 return proc(menu, NULL, -1, extra);
108 }
109
110 /* A callback proc for _al_walk_over_menu that sets each menu's display parameter
111 * to the "extra" parameter.
112 */
set_menu_display_r(ALLEGRO_MENU * menu,ALLEGRO_MENU_ITEM * item,int index,void * extra)113 static bool set_menu_display_r(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item, int index, void *extra)
114 {
115 (void) index;
116
117 if (!item) {
118 menu->display = extra;
119 }
120
121 return false;
122 }
123
124 /* A callback proc for _al_walk_over_menu that searches a menu for a given id. If found it sets
125 * the "parent" parameter to the menu that contains it, and the "id" parameter to the index.
126 */
find_menu_item_r(ALLEGRO_MENU * menu,ALLEGRO_MENU_ITEM * item,int index,void * extra)127 static bool find_menu_item_r(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item, int index, void *extra)
128 {
129 ALLEGRO_MENU_ITEM *info = (ALLEGRO_MENU_ITEM *) extra;
130
131 if (item != NULL && info->id == item->id) {
132 info->id = index;
133 info->parent = menu;
134 return true;
135 }
136
137 return false;
138 }
139
140 /* Like find_menu_item_r, but searches by unique_id.
141 */
find_menu_item_r_unique(ALLEGRO_MENU * menu,ALLEGRO_MENU_ITEM * item,int index,void * extra)142 static bool find_menu_item_r_unique(ALLEGRO_MENU *menu, ALLEGRO_MENU_ITEM *item, int index, void *extra)
143 {
144 ALLEGRO_MENU_ITEM *info = (ALLEGRO_MENU_ITEM *) extra;
145
146 if (item != NULL && info->unique_id == item->unique_id) {
147 info->id = index;
148 info->parent = menu;
149 return true;
150 }
151
152 return false;
153 }
154
155 /* Carefully destroy a menu item... If the item is part of a menu, it must be
156 * removed from it.
157 */
destroy_menu_item(ALLEGRO_MENU_ITEM * item)158 static void destroy_menu_item(ALLEGRO_MENU_ITEM *item)
159 {
160 ASSERT(item);
161
162 if (!item->parent) {
163 /* This normally won't happen. */
164 _al_destroy_menu_item_at(item, -1);
165 }
166 else {
167 size_t i;
168 for (i = 0; i < _al_vector_size(&item->parent->items); ++i) {
169 if (*(ALLEGRO_MENU_ITEM **)_al_vector_ref(&item->parent->items, i) == item) {
170 /* Notify the platform that the item is to be removed. */
171 _al_destroy_menu_item_at(item, i);
172
173 /* Remove the command from the look-up vector. */
174 if (item->id != 0) {
175 _AL_MENU_ID *menu_id;
176 size_t j;
177
178 for (j = 0; j < _al_vector_size(&menu_ids); ++j) {
179 menu_id = (_AL_MENU_ID *) _al_vector_ref(&menu_ids, j);
180 if (menu_id->menu == item->parent && menu_id->unique_id == item->unique_id) {
181 _al_vector_delete_at(&menu_ids, j);
182 break;
183 }
184 }
185 }
186
187 /* Remove the menu from the parent's list. */
188 _al_vector_delete_at(&item->parent->items, i);
189
190 break;
191 }
192 }
193 }
194
195 if (item->caption)
196 al_ustr_free(item->caption);
197
198 if (item->popup) {
199 /* Delete the sub-menu. Must set the parent/display to NULL ahead of time to
200 * avoid recursing back here.
201 */
202 item->popup->parent = NULL;
203 item->popup->display = NULL;
204 al_destroy_menu(item->popup);
205 }
206
207 if (item->icon) {
208 al_destroy_bitmap(item->icon);
209 }
210
211 al_free(item);
212 }
213
214 /* An ALLEGRO_MENU_INFO structure represents a heirarchy of menus. This function
215 * recursively steps through it and builds the entire menu.
216 */
parse_menu_info(ALLEGRO_MENU * parent,ALLEGRO_MENU_INFO * info)217 static ALLEGRO_MENU_INFO *parse_menu_info(ALLEGRO_MENU *parent, ALLEGRO_MENU_INFO *info)
218 {
219 ASSERT(parent);
220 ASSERT(info);
221
222 /* The end of the menu is marked by a NULL caption and an id of 0. */
223 while (info->caption || info->id) {
224 if (!info->caption) {
225 /* A separator */
226 al_append_menu_item(parent, NULL, 0, 0, NULL, NULL);
227 ++info;
228 }
229 else if (strlen(info->caption) > 2 &&
230 !strncmp("->", info->caption + strlen(info->caption) - 2, 2)) {
231 /* An item with a sub-menu has a -> marker as part of its caption.
232 * (e.g., "File->").
233 */
234 ALLEGRO_MENU *menu = al_create_menu();
235 if (menu) {
236 /* Strip the -> mark off the end. */
237 ALLEGRO_USTR *s = al_ustr_new(info->caption);
238 al_ustr_remove_range(s, al_ustr_size(s) - 2, al_ustr_size(s));
239 al_append_menu_item(parent, al_cstr(s), info->id, 0, NULL, menu);
240 info = parse_menu_info(menu, info + 1);
241 al_ustr_free(s);
242 }
243 }
244 else {
245 /* Just ar regular item */
246 al_append_menu_item(parent, info->caption, info->id, info->flags, info->icon, NULL);
247 ++info;
248 }
249 }
250
251 return info + 1;
252 }
253
254 /* All public functions that take a menu and id parameter have two interpretations:
255 *
256 * 1) If id > 0, then it represents an id anywhere within the menu structure,
257 * including child menus. If there are non-unique IDs, the first one found is
258 * returned, but the exact order is undefined. (IDs are meant to be unique.)
259 *
260 * 2) If id <= 0, then its absolute value represents an ordered index for that
261 * exact menu.
262 *
263 * If the parameters are valid, it returns a pointer to the corresponding
264 * MENU_ITEM, and the menu/id parameters are set to the item's parent and its
265 * index. Otherwise (on invalid parameters), it returns NULL and the menu/id
266 * parameters are left undefined.
267 *
268 * (Note that the private OS specific functions always take a direct index.)
269 */
interpret_menu_id_param(ALLEGRO_MENU ** menu,int * id)270 static ALLEGRO_MENU_ITEM *interpret_menu_id_param(ALLEGRO_MENU **menu, int *id)
271 {
272 if (*id > 0) {
273 if (!al_find_menu_item(*menu, *id, menu, id))
274 return NULL;
275 }
276 else {
277 *id = (0 - *id);
278
279 if ((size_t) *id >= _al_vector_size(&((*menu)->items)))
280 return NULL;
281 }
282
283 return *(ALLEGRO_MENU_ITEM **) _al_vector_ref(&((*menu)->items), (size_t) *id);
284 }
285
286 /* A helper function for al_clone_menu() and al_clone_menu_for_popup().
287 * Note that only the root menu is created as a "popup" (if popup == TRUE).
288 */
clone_menu(ALLEGRO_MENU * menu,bool popup)289 static ALLEGRO_MENU *clone_menu(ALLEGRO_MENU *menu, bool popup)
290 {
291 ALLEGRO_MENU *clone = NULL;
292 size_t i;
293
294 if (menu) {
295 clone = popup ? al_create_popup_menu() : al_create_menu();
296
297 for (i = 0; i < _al_vector_size(&menu->items); ++i) {
298 const ALLEGRO_MENU_ITEM *item = *(ALLEGRO_MENU_ITEM **)_al_vector_ref(&menu->items, i);
299 ALLEGRO_BITMAP *icon = item->icon;
300
301 if (icon)
302 icon = al_clone_bitmap(icon);
303
304 al_append_menu_item(clone, item->caption ? al_cstr(item->caption) : NULL,
305 item->id, item->flags, icon, al_clone_menu(item->popup));
306 }
307 }
308
309 return clone;
310 }
311
312 /* Function: al_create_menu
313 */
al_create_menu(void)314 ALLEGRO_MENU *al_create_menu(void)
315 {
316 ALLEGRO_MENU *m = al_calloc(1, sizeof(*m));
317
318 if (m) {
319 _al_vector_init(&m->items, sizeof(ALLEGRO_MENU_ITEM*));
320
321 /* Make sure the platform actually supports menus */
322 if (!_al_init_menu(m)) {
323 al_destroy_menu(m);
324 m = NULL;
325 }
326 }
327
328 return m;
329 }
330
331 /* Function: al_create_popup_menu
332 */
al_create_popup_menu(void)333 ALLEGRO_MENU *al_create_popup_menu(void)
334 {
335 ALLEGRO_MENU *m = al_calloc(1, sizeof(*m));
336
337 if (m) {
338 _al_vector_init(&m->items, sizeof(ALLEGRO_MENU_ITEM*));
339
340 if (!_al_init_popup_menu(m)) {
341 al_destroy_menu(m);
342 m = NULL;
343 }
344 else {
345 /* Popups are slightly different... They can be used multiple times
346 * with al_popup_menu(), but never as a display menu.
347 */
348 m->is_popup_menu = true;
349 }
350 }
351
352 return m;
353 }
354
355 /* Function: al_clone_menu
356 */
al_clone_menu(ALLEGRO_MENU * menu)357 ALLEGRO_MENU *al_clone_menu(ALLEGRO_MENU *menu)
358 {
359 return clone_menu(menu, false);
360 }
361
362 /* Function: al_clone_menu_for_popup
363 */
al_clone_menu_for_popup(ALLEGRO_MENU * menu)364 ALLEGRO_MENU *al_clone_menu_for_popup(ALLEGRO_MENU *menu)
365 {
366 return clone_menu(menu, true);
367 }
368
369 /* Function: al_build_menu
370 */
al_build_menu(ALLEGRO_MENU_INFO * info)371 ALLEGRO_MENU *al_build_menu(ALLEGRO_MENU_INFO *info)
372 {
373 ALLEGRO_MENU *root = al_create_menu();
374
375 if (root)
376 parse_menu_info(root, info);
377
378 return root;
379 }
380
381 /* Function: al_append_menu_item
382 */
al_append_menu_item(ALLEGRO_MENU * parent,char const * title,uint16_t id,int flags,ALLEGRO_BITMAP * icon,ALLEGRO_MENU * submenu)383 int al_append_menu_item(ALLEGRO_MENU *parent, char const *title, uint16_t id,
384 int flags, ALLEGRO_BITMAP *icon, ALLEGRO_MENU *submenu)
385 {
386 ASSERT(parent);
387
388 /* Same thing as inserting a menu item at position == -SIZE */
389 return al_insert_menu_item(parent, 0 - (int) _al_vector_size(&parent->items),
390 title, id, flags, icon, submenu);
391 }
392
393 /* Function: al_insert_menu_item
394 */
al_insert_menu_item(ALLEGRO_MENU * parent,int pos,char const * title,uint16_t id,int flags,ALLEGRO_BITMAP * icon,ALLEGRO_MENU * submenu)395 int al_insert_menu_item(ALLEGRO_MENU *parent, int pos, char const *title,
396 uint16_t id, int flags, ALLEGRO_BITMAP *icon, ALLEGRO_MENU *submenu)
397 {
398 ALLEGRO_MENU_ITEM *item;
399 ALLEGRO_MENU_ITEM **slot;
400 _AL_MENU_ID *menu_id;
401 size_t i;
402
403 ASSERT(parent);
404
405 /* If not found, then treat as an append. */
406 if (!interpret_menu_id_param(&parent, &pos))
407 pos = _al_vector_size(&parent->items);
408
409 /* At this point pos == the _index_ of where to insert */
410
411 /* The sub-menu must not already be in use. */
412 if (submenu && (submenu->display || submenu->parent || submenu->is_popup_menu))
413 return -1;
414
415 item = create_menu_item(title, id, flags, submenu);
416 if (!item)
417 return -1;
418 item->parent = parent;
419
420 set_item_icon(item, icon);
421
422 i = (size_t) pos;
423
424 if (i >= _al_vector_size(&parent->items)) {
425 /* Append */
426 i = _al_vector_size(&parent->items);
427 slot = _al_vector_alloc_back(&parent->items);
428 }
429 else {
430 /* Insert */
431 slot = _al_vector_alloc_mid(&parent->items, i);
432 }
433
434 if (!slot) {
435 destroy_menu_item(item);
436 return -1;
437 }
438 *slot = item;
439
440 if (submenu) {
441 submenu->parent = item;
442
443 if (parent->display)
444 _al_walk_over_menu(submenu, set_menu_display_r, parent->display);
445 }
446
447 _al_insert_menu_item_at(item, (int) i);
448
449 if (id) {
450 /* Append the menu's ID to the search vector */
451 menu_id = (_AL_MENU_ID *) _al_vector_alloc_back(&menu_ids);
452 menu_id->unique_id = item->unique_id;
453 menu_id->id = id;
454 menu_id->menu = parent;
455 }
456
457 return (int) i;
458 }
459
460 /* Function: al_remove_menu_item
461 */
al_remove_menu_item(ALLEGRO_MENU * menu,int pos)462 bool al_remove_menu_item(ALLEGRO_MENU *menu, int pos)
463 {
464 ALLEGRO_MENU_ITEM *item;
465
466 ASSERT(menu);
467
468 item = interpret_menu_id_param(&menu, &pos);
469 if (!item)
470 return false;
471
472 destroy_menu_item(item);
473
474 return true;
475 }
476
477 /* Function: al_find_menu
478 */
al_find_menu(ALLEGRO_MENU * haystack,uint16_t id)479 ALLEGRO_MENU *al_find_menu(ALLEGRO_MENU *haystack, uint16_t id)
480 {
481 int index;
482
483 return !al_find_menu_item(haystack, id, &haystack, &index) ? NULL :
484 (*(ALLEGRO_MENU_ITEM **)_al_vector_ref(&haystack->items, index))->popup;
485 }
486
487 /* Function: al_find_menu_item
488 */
al_find_menu_item(ALLEGRO_MENU * haystack,uint16_t id,ALLEGRO_MENU ** menu,int * index)489 bool al_find_menu_item(ALLEGRO_MENU *haystack, uint16_t id, ALLEGRO_MENU **menu,
490 int *index)
491 {
492 ALLEGRO_MENU_ITEM item;
493
494 ASSERT(haystack);
495
496 /* Abuse the ALLEGRO_MENU_ITEM struct as a container for the _al_walk_over_menu callback.
497 * If found, it will return true, and the "parent" field will be the menu that and
498 * the "id" will be the index.
499 */
500 item.id = id;
501
502 if (!_al_walk_over_menu(haystack, find_menu_item_r, &item))
503 return false;
504
505 if (menu)
506 *menu = item.parent;
507
508 if (index)
509 *index = item.id;
510
511 return true;
512 }
513
514 /* As al_find_menu_item, but searches by the unique id.
515 */
_al_find_menu_item_unique(ALLEGRO_MENU * haystack,uint16_t unique_id,ALLEGRO_MENU ** menu,int * index)516 bool _al_find_menu_item_unique(ALLEGRO_MENU *haystack, uint16_t unique_id, ALLEGRO_MENU **menu,
517 int *index)
518 {
519 ALLEGRO_MENU_ITEM item;
520
521 ASSERT(haystack);
522
523 item.unique_id = unique_id;
524
525 if (!_al_walk_over_menu(haystack, find_menu_item_r_unique, &item))
526 return false;
527
528 if (menu)
529 *menu = item.parent;
530
531 if (index)
532 *index = item.id;
533
534 return true;
535 }
536
537 /* Function: al_get_menu_item_caption
538 */
al_get_menu_item_caption(ALLEGRO_MENU * menu,int pos)539 const char *al_get_menu_item_caption(ALLEGRO_MENU *menu, int pos)
540 {
541 ALLEGRO_MENU_ITEM *item;
542
543 ASSERT(menu);
544
545 item = interpret_menu_id_param(&menu, &pos);
546 return item && item->caption ? al_cstr(item->caption) : NULL;
547 }
548
549
550 /* Function: al_set_menu_item_caption
551 */
al_set_menu_item_caption(ALLEGRO_MENU * menu,int pos,const char * caption)552 void al_set_menu_item_caption(ALLEGRO_MENU *menu, int pos, const char *caption)
553 {
554 ALLEGRO_MENU_ITEM *item;
555
556 ASSERT(menu);
557
558 item = interpret_menu_id_param(&menu, &pos);
559
560 if (item && item->caption) {
561 al_ustr_free(item->caption);
562 item->caption = al_ustr_new(caption);
563 _al_update_menu_item_at(item, pos);
564 }
565 }
566
567 /* Function: al_get_menu_item_flags
568 */
al_get_menu_item_flags(ALLEGRO_MENU * menu,int pos)569 int al_get_menu_item_flags(ALLEGRO_MENU *menu, int pos)
570 {
571 ALLEGRO_MENU_ITEM *item;
572
573 ASSERT(menu);
574
575 item = interpret_menu_id_param(&menu, &pos);
576 return item ? item->flags : -1;
577 }
578
579 /* Function: al_set_menu_item_flags
580 */
al_set_menu_item_flags(ALLEGRO_MENU * menu,int pos,int flags)581 void al_set_menu_item_flags(ALLEGRO_MENU *menu, int pos, int flags)
582 {
583 ALLEGRO_MENU_ITEM *item;
584
585 ASSERT(menu);
586
587 item = interpret_menu_id_param(&menu, &pos);
588
589 if (item) {
590 /* The CHECKBOX flag is read-only after the menu is created, and
591 * the CHECKED flag can only be set if it is a CHECKBOX.
592 */
593 if (item->flags & ALLEGRO_MENU_ITEM_CHECKBOX)
594 flags |= ALLEGRO_MENU_ITEM_CHECKBOX;
595 else {
596 flags &= ~ALLEGRO_MENU_ITEM_CHECKED;
597 flags &= ~ALLEGRO_MENU_ITEM_CHECKBOX;
598 }
599
600 item->flags = flags;
601 _al_update_menu_item_at(item, pos);
602 }
603 }
604
605 /* Function: al_toggle_menu_item_flags
606 */
al_toggle_menu_item_flags(ALLEGRO_MENU * menu,int pos,int flags)607 int al_toggle_menu_item_flags(ALLEGRO_MENU *menu, int pos, int flags)
608 {
609 ALLEGRO_MENU_ITEM *item;
610
611 ASSERT(menu);
612
613 item = interpret_menu_id_param(&menu, &pos);
614
615 if (!item)
616 return -1;
617
618 /* The CHECKBOX flag is read-only after the menu is created, and
619 * the CHECKED flag can only be set if it is a CHECKBOX.
620 */
621 flags &= ~ALLEGRO_MENU_ITEM_CHECKBOX;
622 if (!(item->flags & ALLEGRO_MENU_ITEM_CHECKBOX)) {
623 flags &= ~ALLEGRO_MENU_ITEM_CHECKED;
624 }
625
626 item->flags ^= flags;
627 _al_update_menu_item_at(item, pos);
628
629 return item->flags & flags;
630 }
631
632 /* Function: al_get_menu_item_icon
633 */
al_get_menu_item_icon(ALLEGRO_MENU * menu,int pos)634 ALLEGRO_BITMAP *al_get_menu_item_icon(ALLEGRO_MENU *menu, int pos)
635 {
636 ALLEGRO_MENU_ITEM *item;
637
638 ASSERT(menu);
639
640 item = interpret_menu_id_param(&menu, &pos);
641 return item ? item->icon : NULL;
642 }
643
644
645 /* Function: al_set_menu_item_icon
646 */
al_set_menu_item_icon(ALLEGRO_MENU * menu,int pos,ALLEGRO_BITMAP * icon)647 void al_set_menu_item_icon(ALLEGRO_MENU *menu, int pos, ALLEGRO_BITMAP *icon)
648 {
649 ALLEGRO_MENU_ITEM *item;
650
651 ASSERT(menu);
652
653 item = interpret_menu_id_param(&menu, &pos);
654
655 if (item) {
656 if (item->icon)
657 al_destroy_bitmap(item->icon);
658
659 set_item_icon(item, icon);
660 _al_update_menu_item_at(item, pos);
661 }
662 }
663
664 /* Function: al_destroy_menu
665 */
al_destroy_menu(ALLEGRO_MENU * menu)666 void al_destroy_menu(ALLEGRO_MENU *menu)
667 {
668 ALLEGRO_MENU_ITEM **slot;
669 size_t i;
670 ASSERT(menu);
671
672 if (menu->parent) {
673 /* If the menu is attached to a menu item, then this is equivelant to
674 removing that menu item. */
675 ALLEGRO_MENU *parent = menu->parent->parent;
676 ASSERT(parent);
677
678 for (i = 0; i < _al_vector_size(&parent->items); ++i) {
679 slot = _al_vector_ref(&parent->items, i);
680 if (*slot == menu->parent) {
681 al_remove_menu_item(parent, 0 - (int) i);
682 return;
683 }
684 }
685
686 /* Should never get here. */
687 ASSERT(false);
688 return;
689 }
690 else if (menu->display && !menu->is_popup_menu) {
691 /* This is an active, top-level menu. */
692 al_remove_display_menu(menu->display);
693 }
694
695 /* Destroy each item associated with the menu. */
696 while (_al_vector_size(&menu->items)) {
697 slot = _al_vector_ref_back(&menu->items);
698 destroy_menu_item(*slot);
699 }
700
701 _al_vector_free(&menu->items);
702
703 al_disable_menu_event_source(menu);
704 al_free(menu);
705 }
706
707 /* Function: al_get_default_menu_event_source
708 */
al_get_default_menu_event_source(void)709 ALLEGRO_EVENT_SOURCE *al_get_default_menu_event_source(void)
710 {
711 return &default_menu_es;
712 }
713
714 /* Function: al_enable_menu_event_source
715 */
al_enable_menu_event_source(ALLEGRO_MENU * menu)716 ALLEGRO_EVENT_SOURCE *al_enable_menu_event_source(ALLEGRO_MENU *menu)
717 {
718 ASSERT(menu);
719
720 if (!menu->is_event_source) {
721 al_init_user_event_source(&menu->es);
722 menu->is_event_source = true;
723 }
724
725 return &menu->es;
726 }
727
728 /* Function: al_disable_menu_event_source
729 */
al_disable_menu_event_source(ALLEGRO_MENU * menu)730 void al_disable_menu_event_source(ALLEGRO_MENU *menu)
731 {
732 ASSERT(menu);
733
734 if (menu->is_event_source) {
735 al_destroy_user_event_source(&menu->es);
736 menu->is_event_source = false;
737 }
738 }
739
740 /* Function: al_get_display_menu
741 */
al_get_display_menu(ALLEGRO_DISPLAY * display)742 ALLEGRO_MENU *al_get_display_menu(ALLEGRO_DISPLAY *display)
743 {
744 size_t i;
745 ASSERT(display);
746
747 /* Search through the display_menus vector to see if this display has
748 * a menu associated with it. */
749 for (i = 0; i < _al_vector_size(&display_menus); ++i) {
750 DISPLAY_MENU *dm = (DISPLAY_MENU *) _al_vector_ref(&display_menus, i);
751 if (dm->display == display)
752 return dm->menu;
753 }
754
755 return NULL;
756 }
757
758 /* Function: al_set_display_menu
759 */
al_set_display_menu(ALLEGRO_DISPLAY * display,ALLEGRO_MENU * menu)760 bool al_set_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu)
761 {
762 DISPLAY_MENU *dm = NULL;
763 size_t i;
764 int menu_height = _al_get_menu_display_height();
765 bool automatic_menu_display_resize = true;
766 const char* automatic_menu_display_resize_value =
767 al_get_config_value(al_get_system_config(), "compatibility", "automatic_menu_display_resize");
768 if (automatic_menu_display_resize_value && strcmp(automatic_menu_display_resize_value, "false") == 0)
769 automatic_menu_display_resize = false;
770
771 ASSERT(display);
772
773 /* Check if this display has a menu associated with it */
774 for (i = 0; i < _al_vector_size(&display_menus); ++i) {
775 dm = (DISPLAY_MENU *) _al_vector_ref(&display_menus, i);
776 if (dm->display == display)
777 break;
778 }
779
780 /* If no display was found, reset dm to NULL */
781 if (i == _al_vector_size(&display_menus))
782 dm = NULL;
783
784 if (!menu) {
785 /* Removing the menu */
786
787 if (!dm)
788 return false;
789
790 _al_hide_display_menu(display, dm->menu);
791 _al_walk_over_menu(dm->menu, set_menu_display_r, NULL);
792 _al_vector_delete_at(&display_menus, i);
793
794 if (automatic_menu_display_resize && menu_height > 0) {
795 display->extra_resize_height = 0;
796 al_resize_display(display, al_get_display_width(display), al_get_display_height(display));
797 }
798 }
799 else {
800 /* Setting the menu. It must not currently be attached to any
801 * display, and it cannot have a parent menu. */
802 if (menu->display || menu->parent)
803 return false;
804
805 if (dm) {
806 /* hide the existing menu */
807 _al_hide_display_menu(display, dm->menu);
808 _al_walk_over_menu(dm->menu, set_menu_display_r, NULL);
809 }
810
811 if (!_al_show_display_menu(display, menu)) {
812 /* Unable to set the new menu, but already have hidden the
813 * previous one, so delete the display_menus slot. */
814 if (dm)
815 _al_vector_delete_at(&display_menus, i);
816 return false;
817 }
818
819 /* Set the entire menu tree as owned by the display */
820 _al_walk_over_menu(menu, set_menu_display_r, display);
821
822 if (!dm)
823 dm = _al_vector_alloc_back(&display_menus);
824
825 if (automatic_menu_display_resize && menu_height > 0) {
826 /* Temporarily disable the constraints so we don't send a RESIZE_EVENT. */
827 bool old_constraints = display->use_constraints;
828 display->use_constraints = false;
829 display->extra_resize_height = menu_height;
830 al_resize_display(display, al_get_display_width(display), al_get_display_height(display));
831 display->use_constraints = old_constraints;
832 }
833
834 dm->display = display;
835 dm->menu = menu;
836 }
837
838 return true;
839 }
840
841 /* Function: al_popup_menu
842 */
al_popup_menu(ALLEGRO_MENU * popup,ALLEGRO_DISPLAY * display)843 bool al_popup_menu(ALLEGRO_MENU *popup, ALLEGRO_DISPLAY *display)
844 {
845 bool ret;
846 ASSERT(popup);
847
848 if (!popup->is_popup_menu || popup->parent)
849 return false;
850
851 if (!display)
852 display = al_get_current_display();
853
854 /* Set the entire menu tree as owned by the display */
855 _al_walk_over_menu(popup, set_menu_display_r, display);
856
857 ret = _al_show_popup_menu(display, popup);
858
859 if (!ret) {
860 _al_walk_over_menu(popup, set_menu_display_r, NULL);
861 }
862 return ret;
863 }
864
865 /* Function: al_remove_display_menu
866 */
al_remove_display_menu(ALLEGRO_DISPLAY * display)867 ALLEGRO_MENU *al_remove_display_menu(ALLEGRO_DISPLAY *display)
868 {
869 ALLEGRO_MENU *menu;
870
871 ASSERT(display);
872
873 menu = al_get_display_menu(display);
874
875 if (menu)
876 al_set_display_menu(display, NULL);
877
878 return menu;
879 }
880
881 /* Tries to find the menu that has a child with the given id. If display
882 * is not NULL, then it must also match. The first match is returned.
883 */
_al_find_parent_menu_by_id(ALLEGRO_DISPLAY * display,uint16_t unique_id)884 _AL_MENU_ID *_al_find_parent_menu_by_id(ALLEGRO_DISPLAY *display, uint16_t unique_id)
885 {
886 _AL_MENU_ID *menu_id;
887 size_t i;
888
889 for (i = 0; i < _al_vector_size(&menu_ids); ++i) {
890 menu_id = (_AL_MENU_ID *) _al_vector_ref(&menu_ids, i);
891 if (menu_id->unique_id == unique_id) {
892 if (!display || menu_id->menu->display == display) {
893 return menu_id;
894 }
895 }
896 }
897
898 return NULL;
899 }
900
901 /* Each platform implementation must call this when a menu has been clicked.
902 * The display parameter should be sent if at all possible! If it isn't sent,
903 * and the user is using non-unique ids, it won't know which display actually
904 * triggered the menu click.
905 */
_al_emit_menu_event(ALLEGRO_DISPLAY * display,uint16_t unique_id)906 bool _al_emit_menu_event(ALLEGRO_DISPLAY *display, uint16_t unique_id)
907 {
908 ALLEGRO_EVENT event;
909 _AL_MENU_ID *menu_id = NULL;
910 ALLEGRO_EVENT_SOURCE *source = al_get_default_menu_event_source();
911
912 /* try to find the menu that triggered the event */
913 menu_id = _al_find_parent_menu_by_id(display, unique_id);
914
915 if (!menu_id)
916 return false;
917
918 if (menu_id->id == 0)
919 return false;
920
921 if (menu_id) {
922 /* A menu was found associated with the id. See if it has an
923 * event source associated with it, and adjust "source" accordingly. */
924 ALLEGRO_MENU *m = menu_id->menu;
925 while (true) {
926 if (m->is_event_source) {
927 source = &m->es;
928 break;
929 }
930
931 if (!m->parent)
932 break;
933
934 /* m->parent is of type MENU_ITEM,
935 * which always has a parent of type MENU */
936 ASSERT(m->parent->parent);
937 m = m->parent->parent;
938 }
939 }
940
941 event.user.type = ALLEGRO_EVENT_MENU_CLICK;
942 event.user.data1 = menu_id->id;
943 event.user.data2 = (intptr_t) display;
944 event.user.data3 = (intptr_t) menu_id->menu;
945
946 al_emit_user_event(source, &event, NULL);
947
948 return true;
949 }
950
951
952 /* vim: set sts=3 sw=3 et: */
953