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