[Allegro5] Popup menu fails to display
utz000

Hi, it's me again with another native dialogs problem. This time, it's al_popup_menu() giving me trouble.

The example below should display a simple popup menu on left mouse click. al_popup_menu() returns true indicating it was successful, but no popup menu is actually displayed, except in some rare circumstances. It tends to work on the very first run after compiling for the first time, but not afterwards. Occasionally the menu also shows up if I do a short drag to the lower left before releasing the mouse button.

Unlike with the other issue, registering mouse events and using that to detect the button press will not help here. So I'm pretty sure I'm doing something wrong.

For reference, my setup is Debian/GTK 3.22/Allegro 5.2.2.

#SelectExpand
1#include <iostream> 2#include <allegro5/allegro.h> 3#include <allegro5/allegro_native_dialog.h> 4 5int main(int argc, char **argv){ 6 7 if (!al_init()) { 8 std::cout << "Allegro initialization failed.\n"; 9 return -1; 10 } 11 12 ALLEGRO_DISPLAY *display; 13 ALLEGRO_TIMER *timer; 14 ALLEGRO_EVENT_QUEUE *queue; 15 ALLEGRO_MOUSE_STATE mouse; 16 ALLEGRO_MENU *menu; 17 ALLEGRO_EVENT event; 18 19 if (!al_init_native_dialog_addon()) { 20 std::cout << "Failed to initialize native dialogs.\n"; 21 return -1; 22 } 23 24#ifdef ALLEGRO_GTK_TOPLEVEL 25 al_set_new_display_flags(ALLEGRO_GTK_TOPLEVEL); 26#endif 27 28 display = al_create_display(640, 480); 29 if (!display) { 30 std::cout << "Failed to initialize display.\n"; 31 al_uninstall_system(); 32 return -1; 33 } 34 35 if (!al_install_mouse()) { 36 std::cout << "Failed to initialize keyboard.\n"; 37 al_destroy_display(display); 38 al_uninstall_system(); 39 return -1; 40 } 41 42 timer = al_create_timer(1.0f/25.0f); 43 if (!timer) { 44 std::cout << "Failed to initialize timer.\n"; 45 al_destroy_display(display); 46 al_uninstall_system(); 47 return -1; 48 } 49 50 menu = al_create_popup_menu(); 51 al_append_menu_item(menu, "Option", 1, 0, nullptr, nullptr); 52 bool menuDisplayed = false; 53 54 queue = al_create_event_queue(); 55 56 if(!queue) { 57 std::cout << "Failed to initialize event queue.\n"; 58 al_destroy_timer(timer); 59 al_destroy_display(display); 60 al_uninstall_system(); 61 return -1; 62 } 63 al_register_event_source(queue, al_get_display_event_source(display)); 64 al_register_event_source(queue, al_get_timer_event_source(timer)); 65// al_register_event_source(queue, al_get_mouse_event_source()); 66 al_start_timer(timer); 67 68 while (true) { 69 70 al_wait_for_event(queue, &event); 71 72 //neither this nor the timed version work 73// if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) { 74// 75// if (event.mouse.button == 1 && !menuDisplayed) { 76// 77// if (al_popup_menu(menu, display)) std::cout << "Displaying menu... or not\n"; 78// else std::cout << "Displaying menu failed"; 79// menuDisplayed = true; 80// } 81// else menuDisplayed = false; 82// } 83 84 if (event.type == ALLEGRO_EVENT_TIMER) { 85 86 al_get_mouse_state(&mouse); 87 88 if (mouse.buttons & 1 && !menuDisplayed) { 89 90 if (al_popup_menu(menu, display)) std::cout << "Displaying menu... or not\n"; 91 else std::cout << "Displaying menu failed"; 92 menuDisplayed = true; 93 } 94 else if (mouse.buttons & 2) menuDisplayed = false; 95 } 96 97 else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) break; 98 } 99 100 al_destroy_event_queue(queue); 101 al_destroy_timer(timer); 102 al_destroy_menu(menu); 103 al_destroy_display(display); 104 al_uninstall_system(); 105 return 0; 106}

Eric Johnson

Take a look at ex_menu.c for a working example of creating context menus. It is located in the examples directory of wherever you built Allegro 5.

ex_menu.c

#SelectExpand
1#include "allegro5/allegro.h" 2#include "allegro5/allegro_native_dialog.h" 3#include "allegro5/allegro_image.h" 4#include <stdio.h> 5#include <math.h> 6 7#include "common.c" 8 9/* The following is a list of menu item ids. They can be any non-zero, positive 10 * integer. A menu item must have an id in order for it to generate an event. 11 * Also, each menu item's id should be unique to get well defined results. 12 */ 13enum { 14 FILE_ID = 1, 15 FILE_OPEN_ID, 16 FILE_RESIZE_ID, 17 FILE_FULLSCREEN_ID, 18 FILE_CLOSE_ID, 19 FILE_EXIT_ID, 20 DYNAMIC_ID, 21 DYNAMIC_CHECKBOX_ID, 22 DYNAMIC_DISABLED_ID, 23 DYNAMIC_DELETE_ID, 24 DYNAMIC_CREATE_ID, 25 HELP_ABOUT_ID 26}; 27 28/* This is one way to define a menu. The entire system, nested menus and all, 29 * can be defined by this single array. 30 */ 31ALLEGRO_MENU_INFO main_menu_info[] = { 32 ALLEGRO_START_OF_MENU("&File", FILE_ID), 33 { "&Open", FILE_OPEN_ID, 0, NULL }, 34 ALLEGRO_MENU_SEPARATOR, 35 { "E&xit", FILE_EXIT_ID, 0, NULL }, 36 ALLEGRO_END_OF_MENU, 37 38 ALLEGRO_START_OF_MENU("&Dynamic Options", DYNAMIC_ID), 39 { "&Checkbox", DYNAMIC_CHECKBOX_ID, ALLEGRO_MENU_ITEM_CHECKED, NULL }, 40 { "&Disabled", DYNAMIC_DISABLED_ID, ALLEGRO_MENU_ITEM_DISABLED, NULL }, 41 { "DELETE ME!", DYNAMIC_DELETE_ID, 0, NULL }, 42 { "Click Me", DYNAMIC_CREATE_ID, 0, NULL }, 43 ALLEGRO_END_OF_MENU, 44 45 ALLEGRO_START_OF_MENU("&Help", 0), 46 { "&About", HELP_ABOUT_ID, 0, NULL }, 47 ALLEGRO_END_OF_MENU, 48 49 ALLEGRO_END_OF_MENU 50}; 51 52/* This is the menu on the secondary windows. */ 53ALLEGRO_MENU_INFO child_menu_info[] = { 54 ALLEGRO_START_OF_MENU("&File", 0), 55 { "&Close", FILE_CLOSE_ID, 0, NULL }, 56 ALLEGRO_END_OF_MENU, 57 ALLEGRO_END_OF_MENU 58}; 59 60int main(int argc, char **argv) 61{ 62 const int initial_width = 320; 63 const int initial_height = 200; 64 int windows_menu_height = 0; 65 int dcount = 0; 66 67 ALLEGRO_DISPLAY *display; 68 ALLEGRO_MENU *menu; 69 ALLEGRO_EVENT_QUEUE *queue; 70 ALLEGRO_TIMER *timer; 71 bool redraw = true; 72 bool menu_visible = true; 73 ALLEGRO_MENU *pmenu; 74 ALLEGRO_BITMAP *bg; 75 76 (void)argc; 77 (void)argv; 78 79 if (!al_init()) { 80 abort_example("Could not init Allegro.\n"); 81 } 82 if (!al_init_native_dialog_addon()) { 83 abort_example("Could not init the native dialog addon.\n"); 84 } 85 al_init_image_addon(); 86 al_install_keyboard(); 87 al_install_mouse(); 88 89 queue = al_create_event_queue(); 90 91#ifdef ALLEGRO_GTK_TOPLEVEL 92 /* ALLEGRO_GTK_TOPLEVEL is necessary for menus with GTK. */ 93 al_set_new_display_flags(ALLEGRO_RESIZABLE | ALLEGRO_GTK_TOPLEVEL); 94#else 95 al_set_new_display_flags(ALLEGRO_RESIZABLE); 96#endif 97 display = al_create_display(initial_width, initial_height); 98 if (!display) { 99 abort_example("Error creating display\n"); 100 } 101 al_set_window_title(display, "ex_menu - Main Window"); 102 103 menu = al_build_menu(main_menu_info); 104 if (!menu) { 105 abort_example("Error creating menu\n"); 106 } 107 108 /* Add an icon to the Help/About item. Note that Allegro assumes ownership 109 * of the bitmap. */ 110 al_set_menu_item_icon(menu, HELP_ABOUT_ID, al_load_bitmap("data/icon.tga")); 111 112 if (!al_set_display_menu(display, menu)) { 113 /* Since the menu could not be attached to the window, then treat it as 114 * a popup menu instead. */ 115 pmenu = al_clone_menu_for_popup(menu); 116 al_destroy_menu(menu); 117 menu = pmenu; 118 } 119 else { 120 /* Create a simple popup menu used when right clicking. */ 121 pmenu = al_create_popup_menu(); 122 if (pmenu) { 123 al_append_menu_item(pmenu, "&Open", FILE_OPEN_ID, 0, NULL, NULL); 124 al_append_menu_item(pmenu, "&Resize", FILE_RESIZE_ID, 0, NULL, NULL); 125 al_append_menu_item(pmenu, "&Fullscreen window", FILE_FULLSCREEN_ID, 0, NULL, NULL); 126 al_append_menu_item(pmenu, "E&xit", FILE_EXIT_ID, 0, NULL, NULL); 127 } 128 } 129 130 timer = al_create_timer(1.0 / 60); 131 132 al_register_event_source(queue, al_get_display_event_source(display)); 133 al_register_event_source(queue, al_get_default_menu_event_source()); 134 al_register_event_source(queue, al_get_keyboard_event_source()); 135 al_register_event_source(queue, al_get_mouse_event_source()); 136 al_register_event_source(queue, al_get_timer_event_source(timer)); 137 138 bg = al_load_bitmap("data/mysha.pcx"); 139 140 al_start_timer(timer); 141 142 while (true) { 143 ALLEGRO_EVENT event; 144 145 if (redraw && al_is_event_queue_empty(queue)) { 146 redraw = false; 147 if (bg) { 148 float t = al_get_timer_count(timer) * 0.1; 149 float sw = al_get_bitmap_width(bg); 150 float sh = al_get_bitmap_height(bg); 151 float dw = al_get_display_width(display); 152 float dh = al_get_display_height(display); 153 float cx = dw/2; 154 float cy = dh/2; 155 dw *= 1.2 + 0.2 * cos(t); 156 dh *= 1.2 + 0.2 * cos(1.1 * t); 157 al_draw_scaled_bitmap(bg, 0, 0, sw, sh, 158 cx - dw/2, cy - dh/2, dw, dh, 0); 159 } 160 al_flip_display(); 161 } 162 163 al_wait_for_event(queue, &event); 164 165 if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 166 if (event.display.source == display) { 167 /* Closing the primary display */ 168 break; 169 } 170 else { 171 /* Closing a secondary display */ 172 al_set_display_menu(event.display.source, NULL); 173 al_destroy_display(event.display.source); 174 } 175 } 176 else if (event.type == ALLEGRO_EVENT_MENU_CLICK) { 177 /* data1: id 178 * data2: display (could be null) 179 * data3: menu (could be null) 180 */ 181 if (event.user.data2 == (intptr_t) display) { 182 /* The main window. */ 183 if (event.user.data1 == FILE_OPEN_ID) { 184 ALLEGRO_DISPLAY *d = al_create_display(320, 240); 185 if (d) { 186 ALLEGRO_MENU *menu = al_build_menu(child_menu_info); 187 al_set_display_menu(d, menu); 188 al_clear_to_color(al_map_rgb(0,0,0)); 189 al_flip_display(); 190 al_register_event_source(queue, al_get_display_event_source(d)); 191 al_set_target_backbuffer(display); 192 al_set_window_title(d, "ex_menu - Child Window"); 193 } 194 } 195 else if (event.user.data1 == DYNAMIC_CHECKBOX_ID) { 196 al_set_menu_item_flags(menu, DYNAMIC_DISABLED_ID, al_get_menu_item_flags(menu, DYNAMIC_DISABLED_ID) ^ ALLEGRO_MENU_ITEM_DISABLED); 197 al_set_menu_item_caption(menu, DYNAMIC_DISABLED_ID, 198 (al_get_menu_item_flags(menu, DYNAMIC_DISABLED_ID) & ALLEGRO_MENU_ITEM_DISABLED) ? 199 "&Disabled" : "&Enabled"); 200 } 201 else if (event.user.data1 == DYNAMIC_DELETE_ID) { 202 al_remove_menu_item(menu, DYNAMIC_DELETE_ID); 203 } 204 else if (event.user.data1 == DYNAMIC_CREATE_ID) { 205 if (dcount < 5) { 206 char new_name[10]; 207 208 ++dcount; 209 if (dcount == 1) { 210 /* append a separator */ 211 al_append_menu_item(al_find_menu(menu, DYNAMIC_ID), NULL, 0, 0, NULL, NULL); 212 } 213 214 sprintf(new_name, "New #%d", dcount); 215 al_append_menu_item(al_find_menu(menu, DYNAMIC_ID), new_name, 0, 0, NULL, NULL); 216 217 if (dcount == 5) { 218 /* disable the option */ 219 al_set_menu_item_flags(menu, DYNAMIC_CREATE_ID, ALLEGRO_MENU_ITEM_DISABLED); 220 } 221 } 222 } 223 else if (event.user.data1 == HELP_ABOUT_ID) { 224 al_show_native_message_box(display, "About", "ex_menu", 225 "This is a sample program that shows how to use menus", 226 "OK", 0); 227 } 228 else if (event.user.data1 == FILE_EXIT_ID) { 229 break; 230 } 231 else if (event.user.data1 == FILE_RESIZE_ID) { 232 int w = al_get_display_width(display) * 2; 233 int h = al_get_display_height(display) * 2; 234 if (w > 960) 235 w = 960; 236 if (h > 600) 237 h = 600; 238 if (menu_visible) 239 al_resize_display(display, w, h + windows_menu_height); 240 else 241 al_resize_display(display, w, h); 242 } 243 else if (event.user.data1 == FILE_FULLSCREEN_ID) { 244 int flags = al_get_display_flags(display); 245 bool value = (flags & ALLEGRO_FULLSCREEN_WINDOW) ? true : false; 246 al_set_display_flag(display, ALLEGRO_FULLSCREEN_WINDOW, !value); 247 } 248 } 249 else { 250 /* The child window */ 251 if (event.user.data1 == FILE_CLOSE_ID) { 252 ALLEGRO_DISPLAY *d = (ALLEGRO_DISPLAY *) event.user.data2; 253 if (d) { 254 al_set_display_menu(d, NULL); 255 al_destroy_display(d); 256 } 257 } 258 } 259 } 260 else if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_UP) { 261 /* Popup a context menu on a right click. */ 262 if (event.mouse.display == display && event.mouse.button == 2) { 263 if (pmenu) 264 al_popup_menu(pmenu, display); 265 } 266 } 267 else if (event.type == ALLEGRO_EVENT_KEY_CHAR) { 268 /* Toggle the menu if the spacebar is pressed */ 269 if (event.keyboard.display == display) { 270 if (event.keyboard.unichar == ' ') { 271 if (menu_visible) 272 al_remove_display_menu(display); 273 else 274 al_set_display_menu(display, menu); 275 276 menu_visible = !menu_visible; 277 } 278 } 279 } 280 else if (event.type == ALLEGRO_EVENT_DISPLAY_RESIZE) { 281 al_acknowledge_resize(display); 282 redraw = true; 283 284#ifdef ALLEGRO_WINDOWS 285 /* XXX The Windows implementation currently uses part of the client's 286 * height to render the window. This triggers a resize event, which 287 * can be trapped and used to compute the menu height, and then 288 * resize the display again to what we expect it to be. 289 */ 290 if (event.display.source == display && windows_menu_height == 0) { 291 windows_menu_height = initial_height - al_get_display_height(display); 292 al_resize_display(display, initial_width, initial_height + windows_menu_height); 293 } 294#endif 295 } 296 else if (event.type == ALLEGRO_EVENT_TIMER) { 297 redraw = true; 298 } 299 } 300 301 /* You must remove the menu before destroying the display to free resources */ 302 al_set_display_menu(display, NULL); 303 304 return 0; 305}

common.c

#SelectExpand
1#include <stdio.h> 2#include <stdarg.h> 3 4#ifdef ALLEGRO_ANDROID 5 #include "allegro5/allegro_android.h" 6#endif 7 8void init_platform_specific(void); 9void abort_example(char const *format, ...); 10void open_log(void); 11void open_log_monospace(void); 12void close_log(bool wait_for_user); 13void log_printf(char const *format, ...); 14 15void init_platform_specific(void) 16{ 17#ifdef ALLEGRO_ANDROID 18 al_install_touch_input(); 19 al_android_set_apk_file_interface(); 20#endif 21} 22 23#ifdef ALLEGRO_POPUP_EXAMPLES 24 25#include "allegro5/allegro_native_dialog.h" 26 27ALLEGRO_TEXTLOG *textlog = NULL; 28 29void abort_example(char const *format, ...) 30{ 31 char str[1024]; 32 va_list args; 33 ALLEGRO_DISPLAY *display; 34 35 va_start(args, format); 36 vsnprintf(str, sizeof str, format, args); 37 va_end(args); 38 39 if (al_init_native_dialog_addon()) { 40 display = al_is_system_installed() ? al_get_current_display() : NULL; 41 al_show_native_message_box(display, "Error", "Cannot run example", str, NULL, 0); 42 } 43 else { 44 fprintf(stderr, "%s", str); 45 } 46 exit(1); 47} 48 49void open_log(void) 50{ 51 if (al_init_native_dialog_addon()) { 52 textlog = al_open_native_text_log("Log", 0); 53 } 54} 55 56void open_log_monospace(void) 57{ 58 if (al_init_native_dialog_addon()) { 59 textlog = al_open_native_text_log("Log", ALLEGRO_TEXTLOG_MONOSPACE); 60 } 61} 62 63void close_log(bool wait_for_user) 64{ 65 if (textlog && wait_for_user) { 66 ALLEGRO_EVENT_QUEUE *queue = al_create_event_queue(); 67 al_register_event_source(queue, al_get_native_text_log_event_source( 68 textlog)); 69 al_wait_for_event(queue, NULL); 70 al_destroy_event_queue(queue); 71 } 72 73 al_close_native_text_log(textlog); 74 textlog = NULL; 75} 76 77void log_printf(char const *format, ...) 78{ 79 char str[1024]; 80 va_list args; 81 va_start(args, format); 82 vsnprintf(str, sizeof str, format, args); 83 va_end(args); 84 al_append_native_text_log(textlog, "%s", str); 85} 86 87#else 88 89void abort_example(char const *format, ...) 90{ 91 va_list args; 92 va_start(args, format); 93 vfprintf(stderr, format, args); 94 va_end(args); 95 exit(1); 96} 97 98void open_log(void) 99{ 100} 101 102void open_log_monospace(void) 103{ 104} 105 106void close_log(bool wait_for_user) 107{ 108 (void)wait_for_user; 109} 110 111void log_printf(char const *format, ...) 112{ 113 va_list args; 114 va_start(args, format); 115 #ifdef ALLEGRO_ANDROID 116 char x[1024]; 117 vsnprintf(x, sizeof x, format, args); 118 ALLEGRO_TRACE_CHANNEL_LEVEL("log", 1)(x); 119 #else 120 vprintf(format, args); 121 #endif 122 va_end(args); 123} 124 125#endif 126 127/* vim: set sts=3 sw=3 et: */

utz000

I already looked at the example, of course, but I can't wrap my head around it.

The only major difference I can spot is that the example code listens for a MOUSE_BUTTON_UP event, rather than a MOUSE_BUTTON_DOWN event. Sure enough, if I register mouse events and listen for a MOUSE_BUTTON_UP event, it works...

        if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_UP) {
    
            if (event.mouse.button == 1 && !menuDisplayed) {
            
                if (al_popup_menu(menu, display)) std::cout << "Displaying menu... or not\n";
                else std::cout << "Displaying menu failed";
                menuDisplayed = true;
            }
            else menuDisplayed = false;
        }

... but only in this minimal example, not in my actual code. Still, I don't get why it won't work with MOUSE_BUTTON_DOWN or the the timed version. As mentioned, al_popup_menu() does return true, it just doesn't show the menu. Also, no MENU_CLICK events are generated, in any case.

Anyway, I'm trying to break the minimal example with MOUSE_BUTTON_UP at the moment, will report back later if I find something.

Update:
Found a work-around that works by manually polling the mouse state with al_get_mouse_state(), then creating a new event queue and listening for MOUSE_BUTTON_UP on that one. Yuk!
I still don't know what exactly is breaking the popup menu display. I'll look into it if I can find some free time.

Update 2: The work-around will fail after the menu is closed before a MENU_CLICK is generated. Tried destroying and recreating the menu in case this happens... which destroys my main (non-popup) menu instead. So the statement in the docs that "It is safe to call this on a menu that is currently being displayed." is sort of incorrect.

Update 3:
The error reported in Update 2 was due to trying to read the event type field before it was initialized. My bad.

Eric Johnson

I'm not too familiar with the native dialog addon nor its functions, so I'm not sure why it's giving you so much trouble. I'm glad you found a workaround though. Hopefully someone more knowledgeable can help you further.

utz000

Well, for now all my Allegro problems are solved :) Aside from these, working with the library has been a breeze so far. The interface is very well design imo, and the documentation is great, too.

Concerning the problems, I guess they're partially due to the fact that I'm still a programming novice. On the other hand I guess that the native_dialog add-on simply doesn't get used that much, especially not with GTK - most game designers will probably design their own menus. So some corner cases might simply have slipped under the radar.

Anyway, I'll look a bit more into these issues and try to find out what exactly is going on before opening some issues on github. I guess a bunch of Allegro devs might be on holiday at the moment anyway ;)

Thread #617010. Printed from Allegro.cc