/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * The core GUI routines. * * By Shawn Hargreaves. * * Peter Pavlovic modified the drawing and positioning of menus. * * Menu auto-opening added by Angelo Mottola. * * Eric Botcazou added the support for non-blocking menus. * * Elias Pschernig and Sven Sandberg improved the focus algorithm. * * See readme.txt for copyright information. */ #include #include "allegro.h" #include "allegro/internal/aintern.h" /* if set, the input focus follows the mouse pointer */ int gui_mouse_focus = TRUE; /* font alignment value */ int gui_font_baseline = 0; /* Pointers to the currently active dialog and menu objects. * * Note: active_dialog_player always points to the currently active dialog * player. However, active_menu_player only ever points to menu players * started by a d_menu_proc. The code also assumes that that d_menu_proc can * be found in the currently active dialog. * * Note: active_dialog points to the whole dialog currently running. However, * active_menu points to the *current item* of the currently running menu, * and should really have been called active_menu_item. */ static DIALOG_PLAYER *active_dialog_player = NULL; static MENU_PLAYER *active_menu_player = NULL; static int active_menu_player_zombie = FALSE; DIALOG *active_dialog = NULL; MENU *active_menu = NULL; static BITMAP *gui_screen = NULL; /* list of currently active (initialized) dialog players */ struct al_active_dialog_player { DIALOG_PLAYER *player; struct al_active_dialog_player *next; }; static struct al_active_dialog_player *first_active_dialog_player = 0; static struct al_active_dialog_player *current_active_dialog_player = 0; /* forward declarations */ static int shutdown_single_menu(MENU_PLAYER *, int *); /* hook function for reading the mouse x position */ static int default_mouse_x(void) { if (mouse_needs_poll()) poll_mouse(); return mouse_x; } END_OF_STATIC_FUNCTION(default_mouse_x); /* hook function for reading the mouse y position */ static int default_mouse_y(void) { if (mouse_needs_poll()) poll_mouse(); return mouse_y; } END_OF_STATIC_FUNCTION(default_mouse_y); /* hook function for reading the mouse z position */ static int default_mouse_z(void) { if (mouse_needs_poll()) poll_mouse(); return mouse_z; } END_OF_STATIC_FUNCTION(default_mouse_z); /* hook function for reading the mouse button state */ static int default_mouse_b(void) { if (mouse_needs_poll()) poll_mouse(); return mouse_b; } END_OF_STATIC_FUNCTION(default_mouse_b); /* hook functions for reading the mouse state */ int (*gui_mouse_x)(void) = default_mouse_x; int (*gui_mouse_y)(void) = default_mouse_y; int (*gui_mouse_z)(void) = default_mouse_z; int (*gui_mouse_b)(void) = default_mouse_b; /* timer to handle menu auto-opening */ static int gui_timer; static int gui_menu_opening_delay; /* Checking for double clicks is complicated. The user could release the * mouse button at almost any point, and I might miss it if I am doing some * other processing at the same time (eg. sending the single-click message). * To get around this I install a timer routine to do the checking for me, * so it will notice double clicks whenever they happen. */ static volatile int dclick_status, dclick_time; static int gui_install_count = 0; static int gui_install_time = 0; #define DCLICK_START 0 #define DCLICK_RELEASE 1 #define DCLICK_AGAIN 2 #define DCLICK_NOT 3 /* dclick_check: * Double click checking user timer routine. */ static void dclick_check(void) { gui_timer++; if (dclick_status==DCLICK_START) { /* first click... */ if (!gui_mouse_b()) { dclick_status = DCLICK_RELEASE; /* aah! released first */ dclick_time = 0; return; } } else if (dclick_status==DCLICK_RELEASE) { /* wait for second click */ if (gui_mouse_b()) { dclick_status = DCLICK_AGAIN; /* yes! the second click */ dclick_time = 0; return; } } else return; /* timeout? */ if (dclick_time++ > 10) dclick_status = DCLICK_NOT; } END_OF_STATIC_FUNCTION(dclick_check); /* gui_switch_callback: * Sets the dirty flags whenever a display switch occurs. */ static void gui_switch_callback(void) { if (active_dialog_player) active_dialog_player->res |= D_REDRAW_ALL; } /* position_dialog: * Moves all the objects in a dialog to the specified position. */ void position_dialog(DIALOG *dialog, int x, int y) { int min_x = INT_MAX; int min_y = INT_MAX; int xc, yc; int c; ASSERT(dialog); /* locate the upper-left corner */ for (c=0; dialog[c].proc; c++) { if (dialog[c].x < min_x) min_x = dialog[c].x; if (dialog[c].y < min_y) min_y = dialog[c].y; } /* move the dialog */ xc = min_x - x; yc = min_y - y; for (c=0; dialog[c].proc; c++) { dialog[c].x -= xc; dialog[c].y -= yc; } } /* centre_dialog: * Moves all the objects in a dialog so that the dialog is centered in * the screen. */ void centre_dialog(DIALOG *dialog) { int min_x = INT_MAX; int min_y = INT_MAX; int max_x = INT_MIN; int max_y = INT_MIN; int xc, yc; int c; ASSERT(dialog); /* find the extents of the dialog (done in many loops due to a bug * in MSVC that prevents the more sensible version from working) */ for (c=0; dialog[c].proc; c++) { if (dialog[c].x < min_x) min_x = dialog[c].x; } for (c=0; dialog[c].proc; c++) { if (dialog[c].y < min_y) min_y = dialog[c].y; } for (c=0; dialog[c].proc; c++) { if (dialog[c].x + dialog[c].w > max_x) max_x = dialog[c].x + dialog[c].w; } for (c=0; dialog[c].proc; c++) { if (dialog[c].y + dialog[c].h > max_y) max_y = dialog[c].y + dialog[c].h; } /* how much to move by? */ xc = (SCREEN_W - (max_x - min_x)) / 2 - min_x; yc = (SCREEN_H - (max_y - min_y)) / 2 - min_y; /* move it */ for (c=0; dialog[c].proc; c++) { dialog[c].x += xc; dialog[c].y += yc; } } /* set_dialog_color: * Sets the foreground and background colors of all the objects in a dialog. */ void set_dialog_color(DIALOG *dialog, int fg, int bg) { int c; ASSERT(dialog); for (c=0; dialog[c].proc; c++) { dialog[c].fg = fg; dialog[c].bg = bg; } } /* find_dialog_focus: * Searches the dialog for the object which has the input focus, returning * its index, or -1 if the focus is not set. Useful after do_dialog() exits * if you need to know which object was selected. */ int find_dialog_focus(DIALOG *dialog) { int c; ASSERT(dialog); for (c=0; dialog[c].proc; c++) if (dialog[c].flags & D_GOTFOCUS) return c; return -1; } /* object_message: * Sends a message to a widget, automatically scaring and unscaring * the mouse if the message is MSG_DRAW. */ int object_message(DIALOG *dialog, int msg, int c) { #ifdef ALLEGRO_WINDOWS /* exported address of d_clear_proc */ extern int (*_d_clear_proc)(int, DIALOG *, int); #endif int ret; ASSERT(dialog); if (msg == MSG_DRAW) { if (dialog->flags & D_HIDDEN) return D_O_K; #ifdef ALLEGRO_WINDOWS if (dialog->proc == _d_clear_proc) #else if (dialog->proc == d_clear_proc) #endif scare_mouse(); else scare_mouse_area(dialog->x, dialog->y, dialog->w, dialog->h); acquire_screen(); } ret = dialog->proc(msg, dialog, c); if (msg == MSG_DRAW) { release_screen(); unscare_mouse(); } if (ret & D_REDRAWME) { dialog->flags |= D_DIRTY; ret &= ~D_REDRAWME; } return ret; } /* dialog_message: * Sends a message to all the objects in a dialog. If any of the objects * return values other than D_O_K, returns the value and sets obj to the * object which produced it. */ int dialog_message(DIALOG *dialog, int msg, int c, int *obj) { int count, res, r, force, try; DIALOG *menu_dialog = NULL; ASSERT(dialog); /* Note: don't acquire the screen in here. A deadlock develops in a * situation like this: * * 1. this thread: acquires the screen; * 2. timer thread: wakes up, locks the timer_mutex, and calls a * callback to redraw the software mouse cursor, which tries to * acquire the screen; * 3. this thread: object_message(MSG_DRAW) calls scare_mouse() * which calls remove_int(). * * So this thread has the screen acquired and wants the timer_mutex, * whereas the timer thread holds the timer_mutex, but wants to acquire * the screen. The problem is calling scare_mouse() with the screen * acquired. */ force = ((msg == MSG_START) || (msg == MSG_END) || (msg >= MSG_USER)); res = D_O_K; /* If a menu spawned by a d_menu_proc object is active, the dialog engine * has effectively been shutdown for the sake of safety. This means that * we can't send the message to the other objects in the dialog. So try * first to send the message to the d_menu_proc object and, if the menu * is then not active anymore, send it to the other objects as well. */ if (active_menu_player) { try = 2; menu_dialog = active_menu_player->dialog; } else try = 1; for (; try > 0; try--) { for (count=0; dialog[count].proc; count++) { if ((try == 2) && (&dialog[count] != menu_dialog)) continue; if ((force) || (!(dialog[count].flags & D_HIDDEN))) { r = object_message(&dialog[count], msg, c); if (r != D_O_K) { res |= r; if (obj) *obj = count; } if ((msg == MSG_IDLE) && (dialog[count].flags & (D_DIRTY | D_HIDDEN)) == D_DIRTY) { dialog[count].flags &= ~D_DIRTY; object_message(dialog+count, MSG_DRAW, 0); } } } if (active_menu_player) break; } return res; } /* broadcast_dialog_message: * Broadcasts a message to all the objects in the active dialog. If any of * the dialog procedures return values other than D_O_K, it returns that * value. */ int broadcast_dialog_message(int msg, int c) { int nowhere; if (active_dialog) return dialog_message(active_dialog, msg, c, &nowhere); else return D_O_K; } /* find_mouse_object: * Finds which object the mouse is on top of. */ static int find_mouse_object(DIALOG *d) { int mouse_object = -1; int c; int res; ASSERT(d); for (c=0; d[c].proc; c++) { if ((gui_mouse_x() >= d[c].x) && (gui_mouse_y() >= d[c].y) && (gui_mouse_x() < d[c].x + d[c].w) && (gui_mouse_y() < d[c].y + d[c].h) && (!(d[c].flags & (D_HIDDEN | D_DISABLED)))) { /* check if this object wants the mouse */ res = object_message(d+c, MSG_WANTMOUSE, 0); if (!(res & D_DONTWANTMOUSE)) { mouse_object = c; } } } return mouse_object; } /* offer_focus: * Offers the input focus to a particular object. */ int offer_focus(DIALOG *dialog, int obj, int *focus_obj, int force) { int res = D_O_K; ASSERT(dialog); ASSERT(focus_obj); if ((obj == *focus_obj) || ((obj >= 0) && (dialog[obj].flags & (D_HIDDEN | D_DISABLED)))) return D_O_K; /* check if object wants the focus */ if (obj >= 0) { res = object_message(dialog+obj, MSG_WANTFOCUS, 0); if (res & D_WANTFOCUS) res ^= D_WANTFOCUS; else obj = -1; } if ((obj >= 0) || (force)) { /* take focus away from old object */ if (*focus_obj >= 0) { res |= object_message(dialog+*focus_obj, MSG_LOSTFOCUS, 0); if (res & D_WANTFOCUS) { if (obj < 0) return D_O_K; else res &= ~D_WANTFOCUS; } dialog[*focus_obj].flags &= ~D_GOTFOCUS; res |= object_message(dialog+*focus_obj, MSG_DRAW, 0); } *focus_obj = obj; /* give focus to new object */ if (obj >= 0) { dialog[obj].flags |= D_GOTFOCUS; res |= object_message(dialog+obj, MSG_GOTFOCUS, 0); res |= object_message(dialog+obj, MSG_DRAW, 0); } } return res; } #define MAX_OBJECTS 512 typedef struct OBJ_LIST { int index; int diff; } OBJ_LIST; /* Weight ratio between the orthogonal direction and the main direction when calculating the distance for the focus algorithm. */ #define DISTANCE_RATIO 8 /* Maximum size (in bytes) of a dialog array. */ #define MAX_SIZE 0x10000 /* 64 kb */ enum axis { X_AXIS, Y_AXIS }; /* obj_list_cmp: * Callback function for qsort(). */ static int obj_list_cmp(AL_CONST void *e1, AL_CONST void *e2) { return (((OBJ_LIST *)e1)->diff - ((OBJ_LIST *)e2)->diff); } /* cmp_tab: * Comparison function for tab key movement. */ static int cmp_tab(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2) { int ret = (int)((AL_CONST unsigned long)d2 - (AL_CONST unsigned long)d1); /* Wrap around if d2 is before d1 in the dialog array. */ if (ret < 0) ret += MAX_SIZE; return ret; } /* cmp_shift_tab: * Comparison function for shift+tab key movement. */ static int cmp_shift_tab(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2) { int ret = (int)((AL_CONST unsigned long)d1 - (AL_CONST unsigned long)d2); /* Wrap around if d2 is after d1 in the dialog array. */ if (ret < 0) ret += MAX_SIZE; return ret; } /* min_dist: * Returns the minimum distance between dialogs 'd1' and 'd2'. 'main_axis' * is taken account to give different weights to the axes in the distance * formula, as well as to shift the actual position of 'd2' along the axis * by the amount specified by 'bias'. */ static int min_dist(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2, enum axis main_axis, int bias) { int x_left = d1->x - d2->x - d2->w + 1; int x_right = d2->x - d1->x - d1->w + 1; int y_top = d1->y - d2->y - d2->h + 1; int y_bottom = d2->y - d1->y - d1->h + 1; if (main_axis == X_AXIS) { x_left -= bias; x_right += bias; y_top *= DISTANCE_RATIO; y_bottom *= DISTANCE_RATIO; } else { x_left *= DISTANCE_RATIO; x_right *= DISTANCE_RATIO; y_top -= bias; y_bottom += bias; } if (x_left > 0) { /* d2 is left of d1 */ if (y_top > 0) /* d2 is above d1 */ return x_left + y_top; else if (y_bottom > 0) /* d2 is below d1 */ return x_left + y_bottom; else /* vertically overlapping */ return x_left; } else if (x_right > 0) { /* d2 is right of d1 */ if (y_top > 0) /* d2 is above d1 */ return x_right + y_top; else if (y_bottom > 0) /* d2 is below d1 */ return x_right + y_bottom; else /* vertically overlapping */ return x_right; } /* horizontally overlapping */ else if (y_top > 0) /* d2 is above d1 */ return y_top; else if (y_bottom > 0) /* d2 is below d1 */ return y_bottom; else /* overlapping */ return 0; } /* cmp_right: * Comparison function for right arrow key movement. */ static int cmp_right(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2) { int bias; /* Wrap around if d2 is not fully contained in the half-plan delimited by d1's right edge and not containing it. */ if (d2->x < d1->x + d1->w) bias = +SCREEN_W; else bias = 0; return min_dist(d1, d2, X_AXIS, bias); } /* cmp_left: * Comparison function for left arrow key movement. */ static int cmp_left(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2) { int bias; /* Wrap around if d2 is not fully contained in the half-plan delimited by d1's left edge and not containing it. */ if (d2->x + d2->w > d1->x) bias = -SCREEN_W; else bias = 0; return min_dist(d1, d2, X_AXIS, bias); } /* cmp_down: * Comparison function for down arrow key movement. */ static int cmp_down(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2) { int bias; /* Wrap around if d2 is not fully contained in the half-plan delimited by d1's bottom edge and not containing it. */ if (d2->y < d1->y + d1->h) bias = +SCREEN_H; else bias = 0; return min_dist(d1, d2, Y_AXIS, bias); } /* cmp_up: * Comparison function for up arrow key movement. */ static int cmp_up(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2) { int bias; /* Wrap around if d2 is not fully contained in the half-plan delimited by d1's top edge and not containing it. */ if (d2->y + d2->h > d1->y) bias = -SCREEN_H; else bias = 0; return min_dist(d1, d2, Y_AXIS, bias); } /* move_focus: * Handles arrow key and tab movement through a dialog, deciding which * object should be given the input focus. */ static int move_focus(DIALOG *d, int ascii, int scan, int *focus_obj) { int (*cmp)(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2); OBJ_LIST obj[MAX_OBJECTS]; int obj_count = 0; int fobj, c; int res = D_O_K; /* choose a comparison function */ switch (scan) { case KEY_TAB: cmp = (ascii == '\t') ? cmp_tab : cmp_shift_tab; break; case KEY_RIGHT: cmp = cmp_right; break; case KEY_LEFT: cmp = cmp_left; break; case KEY_DOWN: cmp = cmp_down; break; case KEY_UP: cmp = cmp_up; break; default: return D_O_K; } /* fill temporary table */ for (c=0; d[c].proc; c++) { if (((*focus_obj < 0) || (c != *focus_obj)) && !(d[c].flags & (D_DISABLED | D_HIDDEN))) { obj[obj_count].index = c; if (*focus_obj >= 0) obj[obj_count].diff = cmp(d+*focus_obj, d+c); else obj[obj_count].diff = c; obj_count++; if (obj_count >= MAX_OBJECTS) break; } } /* sort table */ qsort(obj, obj_count, sizeof(OBJ_LIST), obj_list_cmp); /* find an object to give the focus to */ fobj = *focus_obj; for (c=0; cdialog+i, msg, c); \ if (r != D_O_K) { \ player->res |= r; \ player->obj = i; \ } \ } /* do_dialog: * The basic dialog manager. The list of dialog objects should be * terminated by one with a null dialog procedure. Returns the index of * the object which caused it to exit. */ int do_dialog(DIALOG *dialog, int focus_obj) { BITMAP *mouse_screen = _mouse_screen; BITMAP *gui_bmp = gui_get_screen(); int screen_count = _gfx_mode_set_count; void *player; ASSERT(dialog); if (!is_same_bitmap(_mouse_screen, gui_bmp) && !(gfx_capabilities&GFX_HW_CURSOR)) show_mouse(gui_bmp); player = init_dialog(dialog, focus_obj); while (update_dialog(player)) { /* If a menu is active, we yield here, since the dialog * engine is shut down so no user code can be running. */ if (active_menu_player) rest(1); } if (_gfx_mode_set_count == screen_count && !(gfx_capabilities&GFX_HW_CURSOR)) show_mouse(mouse_screen); return shutdown_dialog(player); } /* popup_dialog: * Like do_dialog(), but it stores the data on the screen before drawing * the dialog and restores it when the dialog is closed. The screen area * to be stored is calculated from the dimensions of the first object in * the dialog, so all the other objects should lie within this one. */ int popup_dialog(DIALOG *dialog, int focus_obj) { BITMAP *bmp; BITMAP *gui_bmp; int ret; ASSERT(dialog); bmp = create_bitmap(dialog->w, dialog->h); gui_bmp = gui_get_screen(); if (bmp) { scare_mouse_area(dialog->x, dialog->y, dialog->w, dialog->h); blit(gui_bmp, bmp, dialog->x, dialog->y, 0, 0, dialog->w, dialog->h); unscare_mouse(); } else *allegro_errno = ENOMEM; ret = do_dialog(dialog, focus_obj); if (bmp) { scare_mouse_area(dialog->x, dialog->y, dialog->w, dialog->h); blit(bmp, gui_bmp, 0, 0, dialog->x, dialog->y, dialog->w, dialog->h); unscare_mouse(); destroy_bitmap(bmp); } return ret; } /* init_dialog: * Sets up a dialog, returning a player object that can be used with * the update_dialog() and shutdown_dialog() functions. */ DIALOG_PLAYER *init_dialog(DIALOG *dialog, int focus_obj) { DIALOG_PLAYER *player; BITMAP *gui_bmp = gui_get_screen(); struct al_active_dialog_player *n; char tmp1[64], tmp2[64]; int c; ASSERT(dialog); /* close any menu opened by a d_menu_proc */ if (active_menu_player) object_message(active_menu_player->dialog, MSG_LOSTMOUSE, 0); player = malloc(sizeof(DIALOG_PLAYER)); if (!player) { *allegro_errno = ENOMEM; return NULL; } /* append player to the list */ n = malloc(sizeof(struct al_active_dialog_player)); if (!n) { *allegro_errno = ENOMEM; free (player); return NULL; } n->next = NULL; n->player = player; if (!current_active_dialog_player) { current_active_dialog_player = first_active_dialog_player = n; } else { current_active_dialog_player->next = n; current_active_dialog_player = n; } player->res = D_REDRAW; player->joy_on = TRUE; player->click_wait = FALSE; player->dialog = dialog; player->obj = -1; player->mouse_obj = -1; player->mouse_oz = gui_mouse_z(); player->mouse_b = gui_mouse_b(); /* set up the global dialog pointer */ active_dialog_player = player; active_dialog = dialog; /* set up dclick checking code */ if (gui_install_count <= 0) { LOCK_VARIABLE(gui_timer); LOCK_VARIABLE(dclick_status); LOCK_VARIABLE(dclick_time); LOCK_VARIABLE(gui_mouse_x); LOCK_VARIABLE(gui_mouse_y); LOCK_VARIABLE(gui_mouse_z); LOCK_VARIABLE(gui_mouse_b); LOCK_FUNCTION(default_mouse_x); LOCK_FUNCTION(default_mouse_y); LOCK_FUNCTION(default_mouse_z); LOCK_FUNCTION(default_mouse_b); LOCK_FUNCTION(dclick_check); install_int(dclick_check, 20); switch (get_display_switch_mode()) { case SWITCH_AMNESIA: case SWITCH_BACKAMNESIA: set_display_switch_callback(SWITCH_IN, gui_switch_callback); } /* gets menu auto-opening delay (in milliseconds) from config file */ gui_menu_opening_delay = get_config_int(uconvert_ascii("system", tmp1), uconvert_ascii("menu_opening_delay", tmp2), 300); if (gui_menu_opening_delay >= 0) { /* adjust for actual timer speed */ gui_menu_opening_delay /= 20; } else { /* no auto opening */ gui_menu_opening_delay = -1; } gui_install_count = 1; gui_install_time = _allegro_count; } else gui_install_count++; /* initialise the dialog */ set_clip_rect(gui_bmp, 0, 0, SCREEN_W-1, SCREEN_H-1); set_clip_state(gui_bmp, TRUE); player->res |= dialog_message(dialog, MSG_START, 0, &player->obj); player->mouse_obj = find_mouse_object(dialog); if (player->mouse_obj >= 0) dialog[player->mouse_obj].flags |= D_GOTMOUSE; for (c=0; dialog[c].proc; c++) dialog[c].flags &= ~D_GOTFOCUS; if (focus_obj >= 0) c = focus_obj; else c = player->mouse_obj; if ((c >= 0) && ((object_message(dialog+c, MSG_WANTFOCUS, 0)) & D_WANTFOCUS)) { dialog[c].flags |= D_GOTFOCUS; player->focus_obj = c; } else player->focus_obj = -1; return player; } /* gui_set_screen: * Changes the target bitmap for GUI drawing operations */ void gui_set_screen(BITMAP *bmp) { gui_screen = bmp; } /* gui_get_screen: * Returns the gui_screen, or the default surface if gui_screen is NULL */ BITMAP *gui_get_screen(void) { return gui_screen?gui_screen:screen; } /* check_for_redraw: * Checks whether any parts of the current dialog need to be redraw. */ static void check_for_redraw(DIALOG_PLAYER *player) { struct al_active_dialog_player *iter; int c, r; ASSERT(player); /* need to redraw all active dialogs? */ if (player->res & D_REDRAW_ALL) { for (iter = first_active_dialog_player; iter != current_active_dialog_player; iter = iter->next) dialog_message(iter->player->dialog, MSG_DRAW, 0, NULL); player->res &= ~D_REDRAW_ALL; player->res |= D_REDRAW; } /* need to draw it? */ if (player->res & D_REDRAW) { player->res ^= D_REDRAW; player->res |= dialog_message(player->dialog, MSG_DRAW, 0, &player->obj); } /* check if any widget has to be redrawn */ for (c=0; player->dialog[c].proc; c++) { if ((player->dialog[c].flags & (D_DIRTY | D_HIDDEN)) == D_DIRTY) { player->dialog[c].flags &= ~D_DIRTY; MESSAGE(c, MSG_DRAW, 0); } } } /* update_dialog: * Updates the status of a dialog object returned by init_dialog(), * returning TRUE if it is still active or FALSE if it has finished. */ int update_dialog(DIALOG_PLAYER *player) { int c, cascii, cscan, ccombo, r, ret, nowhere, z; int new_mouse_b; ASSERT(player); /* redirect to update_menu() whenever a menu is activated */ if (active_menu_player) { if (!active_menu_player_zombie) { if (update_menu(active_menu_player)) return TRUE; } /* make sure all buttons are released before folding the menu */ if (gui_mouse_b()) { active_menu_player_zombie = TRUE; return TRUE; } else { active_menu_player_zombie = FALSE; for (c=0; player->dialog[c].proc; c++) if (&player->dialog[c] == active_menu_player->dialog) break; ASSERT(player->dialog[c].proc); MESSAGE(c, MSG_LOSTMOUSE, 0); goto getout; } } if (player->res & D_CLOSE) return FALSE; /* deal with mouse buttons presses and releases */ new_mouse_b = gui_mouse_b(); if (new_mouse_b != player->mouse_b) { player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, FALSE); if (player->mouse_obj >= 0) { /* send press and release messages */ if ((new_mouse_b & 1) && !(player->mouse_b & 1)) MESSAGE(player->mouse_obj, MSG_LPRESS, new_mouse_b); if (!(new_mouse_b & 1) && (player->mouse_b & 1)) MESSAGE(player->mouse_obj, MSG_LRELEASE, new_mouse_b); if ((new_mouse_b & 4) && !(player->mouse_b & 4)) MESSAGE(player->mouse_obj, MSG_MPRESS, new_mouse_b); if (!(new_mouse_b & 4) && (player->mouse_b & 4)) MESSAGE(player->mouse_obj, MSG_MRELEASE, new_mouse_b); if ((new_mouse_b & 2) && !(player->mouse_b & 2)) MESSAGE(player->mouse_obj, MSG_RPRESS, new_mouse_b); if (!(new_mouse_b & 2) && (player->mouse_b & 2)) MESSAGE(player->mouse_obj, MSG_RRELEASE, new_mouse_b); player->mouse_b = new_mouse_b; } else player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere); } /* need to reinstall the dclick and switch handlers? */ if (gui_install_time != _allegro_count) { install_int(dclick_check, 20); switch (get_display_switch_mode()) { case SWITCH_AMNESIA: case SWITCH_BACKAMNESIA: set_display_switch_callback(SWITCH_IN, gui_switch_callback); } gui_install_time = _allegro_count; } /* are we dealing with a mouse click? */ if (player->click_wait) { if ((ABS(player->mouse_ox-gui_mouse_x()) > 8) || (ABS(player->mouse_oy-gui_mouse_y()) > 8)) dclick_status = DCLICK_NOT; /* waiting... */ if ((dclick_status != DCLICK_AGAIN) && (dclick_status != DCLICK_NOT)) { player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere); check_for_redraw(player); return TRUE; } player->click_wait = FALSE; /* double click! */ if ((dclick_status==DCLICK_AGAIN) && (gui_mouse_x() >= player->dialog[player->mouse_obj].x) && (gui_mouse_y() >= player->dialog[player->mouse_obj].y) && (gui_mouse_x() <= player->dialog[player->mouse_obj].x + player->dialog[player->mouse_obj].w) && (gui_mouse_y() <= player->dialog[player->mouse_obj].y + player->dialog[player->mouse_obj].h)) { MESSAGE(player->mouse_obj, MSG_DCLICK, 0); } goto getout; } player->res &= ~D_USED_CHAR; /* need to give the input focus to someone? */ if (player->res & D_WANTFOCUS) { player->res ^= D_WANTFOCUS; player->res |= offer_focus(player->dialog, player->obj, &player->focus_obj, FALSE); } /* has mouse object changed? */ c = find_mouse_object(player->dialog); if (c != player->mouse_obj) { if (player->mouse_obj >= 0) { player->dialog[player->mouse_obj].flags &= ~D_GOTMOUSE; MESSAGE(player->mouse_obj, MSG_LOSTMOUSE, 0); } if (c >= 0) { player->dialog[c].flags |= D_GOTMOUSE; MESSAGE(c, MSG_GOTMOUSE, 0); } player->mouse_obj = c; /* move the input focus as well? */ if ((gui_mouse_focus) && (player->mouse_obj != player->focus_obj)) player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, TRUE); } /* deal with mouse button clicks */ if (new_mouse_b) { player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, FALSE); if (player->mouse_obj >= 0) { dclick_time = 0; dclick_status = DCLICK_START; player->mouse_ox = gui_mouse_x(); player->mouse_oy = gui_mouse_y(); /* send click message */ MESSAGE(player->mouse_obj, MSG_CLICK, new_mouse_b); if (player->res == D_O_K) player->click_wait = TRUE; } else player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere); /* goto getout; */ /* to avoid an updating delay */ } /* deal with mouse wheel clicks */ z = gui_mouse_z(); if (z != player->mouse_oz) { player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, FALSE); if (player->mouse_obj >= 0) { MESSAGE(player->mouse_obj, MSG_WHEEL, z-player->mouse_oz); } else player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere); player->mouse_oz = z; /* goto getout; */ /* to avoid an updating delay */ } /* fake joystick input by converting it to key presses */ if (player->joy_on) rest(20); poll_joystick(); if (player->joy_on) { if ((!joy[0].stick[0].axis[0].d1) && (!joy[0].stick[0].axis[0].d2) && (!joy[0].stick[0].axis[1].d1) && (!joy[0].stick[0].axis[1].d2) && (!joy[0].button[0].b) && (!joy[0].button[1].b)) { player->joy_on = FALSE; rest(20); } cascii = cscan = 0; } else { if (joy[0].stick[0].axis[0].d1) { cascii = 0; cscan = KEY_LEFT; player->joy_on = TRUE; } else if (joy[0].stick[0].axis[0].d2) { cascii = 0; cscan = KEY_RIGHT; player->joy_on = TRUE; } else if (joy[0].stick[0].axis[1].d1) { cascii = 0; cscan = KEY_UP; player->joy_on = TRUE; } else if (joy[0].stick[0].axis[1].d2) { cascii = 0; cscan = KEY_DOWN; player->joy_on = TRUE; } else if ((joy[0].button[0].b) || (joy[0].button[1].b)) { cascii = ' '; cscan = KEY_SPACE; player->joy_on = TRUE; } else cascii = cscan = 0; } /* deal with keyboard input */ if ((cascii) || (cscan) || (keypressed())) { if ((!cascii) && (!cscan)) cascii = ureadkey(&cscan); ccombo = (cscan<<8) | ((cascii <= 255) ? cascii : '^'); /* let object deal with the key */ if (player->focus_obj >= 0) { MESSAGE(player->focus_obj, MSG_CHAR, ccombo); if (player->res & (D_USED_CHAR | D_CLOSE)) goto getout; MESSAGE(player->focus_obj, MSG_UCHAR, cascii); if (player->res & (D_USED_CHAR | D_CLOSE)) goto getout; } /* keyboard shortcut? */ for (c=0; player->dialog[c].proc; c++) { if ((((cascii > 0) && (cascii <= 255) && (utolower(player->dialog[c].key) == utolower((cascii)))) || ((!cascii) && (player->dialog[c].key == (cscan<<8)))) && (!(player->dialog[c].flags & (D_HIDDEN | D_DISABLED)))) { MESSAGE(c, MSG_KEY, ccombo); goto getout; } } /* broadcast in case any other objects want it */ for (c=0; player->dialog[c].proc; c++) { if (!(player->dialog[c].flags & (D_HIDDEN | D_DISABLED))) { MESSAGE(c, MSG_XCHAR, ccombo); if (player->res & D_USED_CHAR) goto getout; } } /* pass or to selected object */ if (((cascii == '\r') || (cascii == '\n') || (cascii == ' ')) && (player->focus_obj >= 0)) { MESSAGE(player->focus_obj, MSG_KEY, ccombo); goto getout; } /* ESC closes dialog */ if (cascii == 27) { player->res |= D_CLOSE; player->obj = -1; goto getout; } /* move focus around the dialog */ player->res |= move_focus(player->dialog, cascii, cscan, &player->focus_obj); } /* redraw? */ check_for_redraw(player); /* send idle messages */ player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &player->obj); getout: ret = (!(player->res & D_CLOSE)); player->res &= ~D_CLOSE; return ret; } /* shutdown_dialog: * Destroys a dialog object returned by init_dialog(), returning the index * of the object that caused it to exit. */ int shutdown_dialog(DIALOG_PLAYER *player) { struct al_active_dialog_player *iter, *prev; int obj; ASSERT(player); /* send the finish messages */ dialog_message(player->dialog, MSG_END, 0, &player->obj); /* remove the double click handler */ gui_install_count--; if (gui_install_count <= 0) { remove_int(dclick_check); remove_display_switch_callback(gui_switch_callback); } if (player->mouse_obj >= 0) player->dialog[player->mouse_obj].flags &= ~D_GOTMOUSE; /* remove dialog player from the list of active players */ for (iter = first_active_dialog_player, prev = 0; iter != 0; prev = iter, iter = iter->next) { if (iter->player == player) { if (prev) prev->next = iter->next; else first_active_dialog_player = iter->next; if (iter == current_active_dialog_player) current_active_dialog_player = prev; free (iter); break; } } if (current_active_dialog_player) active_dialog_player = current_active_dialog_player->player; else active_dialog_player = NULL; if (active_dialog_player) active_dialog = active_dialog_player->dialog; else active_dialog = NULL; obj = player->obj; free(player); return obj; } void (*gui_menu_draw_menu)(int x, int y, int w, int h) = NULL; void (*gui_menu_draw_menu_item)(MENU *m, int x, int y, int w, int h, int bar, int sel) = NULL; /* split_around_tab: * Helper function for splitting a string into two tokens * delimited by the first TAB character. */ static char* split_around_tab(const char *s, char **tok1, char **tok2) { char *buf, *last; char tmp[16]; buf = ustrdup(s); *tok1 = ustrtok_r(buf, uconvert_ascii("\t", tmp), &last); *tok2 = ustrtok_r(NULL, empty_string, &last); return buf; } /* bar_entry_lengh: * Helper function for calculating the length of a menu bar entry. */ static int bar_entry_length(const char *text) { char *buf, *tok1, *tok2; int len; buf = split_around_tab(text, &tok1, &tok2); len = gui_strlen(tok1) + 16; if (tok2) len += gui_strlen(tok2) + 16; free(buf); return len; } /* get_menu_pos: * Calculates the coordinates of an object within a top level menu bar. */ static void get_menu_pos(MENU_PLAYER *m, int c, int *x, int *y, int *w) { int c2; if (m->bar) { *x = m->x+1; for (c2=0; c2menu[c2].text); *y = m->y+1; *w = bar_entry_length(m->menu[c].text); } else { *x = m->x+1; *y = m->y+c*(text_height(font)+4)+1; *w = m->w-3; } } /* draw_menu_item: * Draws an item from a popup menu onto the screen. */ static void draw_menu_item(MENU_PLAYER *m, int c) { int fg, bg; int x, y, w; char *buf, *tok1, *tok2; int my; BITMAP *gui_bmp = gui_get_screen(); get_menu_pos(m, c, &x, &y, &w); if (gui_menu_draw_menu_item) { gui_menu_draw_menu_item(&m->menu[c], x, y, w, text_height(font)+4, m->bar, (c == m->sel) ? TRUE : FALSE); return; } if (m->menu[c].flags & D_DISABLED) { if (c == m->sel) { fg = gui_mg_color; bg = gui_fg_color; } else { fg = gui_mg_color; bg = gui_bg_color; } } else { if (c == m->sel) { fg = gui_bg_color; bg = gui_fg_color; } else { fg = gui_fg_color; bg = gui_bg_color; } } rectfill(gui_bmp, x, y, x+w-1, y+text_height(font)+3, bg); if (ugetc(m->menu[c].text)) { buf = split_around_tab(m->menu[c].text, &tok1, &tok2); gui_textout_ex(gui_bmp, tok1, x+8, y+1, fg, bg, FALSE); if (tok2) gui_textout_ex(gui_bmp, tok2, x+w-gui_strlen(tok2)-10, y+1, fg, bg, FALSE); if ((m->menu[c].child) && (!m->bar)) { my = y + text_height(font)/2; hline(gui_bmp, x+w-8, my+1, x+w-4, fg); hline(gui_bmp, x+w-8, my+0, x+w-5, fg); hline(gui_bmp, x+w-8, my-1, x+w-6, fg); hline(gui_bmp, x+w-8, my-2, x+w-7, fg); putpixel(gui_bmp, x+w-8, my-3, fg); hline(gui_bmp, x+w-8, my+2, x+w-5, fg); hline(gui_bmp, x+w-8, my+3, x+w-6, fg); hline(gui_bmp, x+w-8, my+4, x+w-7, fg); putpixel(gui_bmp, x+w-8, my+5, fg); } free(buf); } else hline(gui_bmp, x, y+text_height(font)/2+2, x+w, fg); if (m->menu[c].flags & D_SELECTED) { line(gui_bmp, x+1, y+text_height(font)/2+1, x+3, y+text_height(font)+1, fg); line(gui_bmp, x+3, y+text_height(font)+1, x+6, y+2, fg); } } /* draw_menu: * Draws a popup menu onto the screen. */ static void draw_menu(MENU_PLAYER *m) { int c; if (gui_menu_draw_menu) gui_menu_draw_menu(m->x, m->y, m->w, m->h); else { BITMAP *gui_bmp = gui_get_screen(); rect(gui_bmp, m->x, m->y, m->x+m->w-2, m->y+m->h-2, gui_fg_color); vline(gui_bmp, m->x+m->w-1, m->y+1, m->y+m->h-1, gui_fg_color); hline(gui_bmp, m->x+1, m->y+m->h-1, m->x+m->w-1, gui_fg_color); } for (c=0; m->menu[c].text; c++) draw_menu_item(m, c); } /* menu_mouse_object: * Returns the index of the object the mouse is currently on top of. */ static int menu_mouse_object(MENU_PLAYER *m) { int c; int x, y, w; for (c=0; csize; c++) { get_menu_pos(m, c, &x, &y, &w); if ((gui_mouse_x() >= x) && (gui_mouse_x() < x+w) && (gui_mouse_y() >= y) && (gui_mouse_y() < y+(text_height(font)+4))) return (ugetc(m->menu[c].text)) ? c : -1; } return -1; } /* mouse_in_single_menu: * Checks if the mouse is inside a single menu. */ static INLINE int mouse_in_single_menu(MENU_PLAYER *m) { if ((gui_mouse_x() >= m->x) && (gui_mouse_x() < m->x+m->w) && (gui_mouse_y() >= m->y) && (gui_mouse_y() < m->y+m->h)) return TRUE; else return FALSE; } /* mouse_in_parent_menu: * Recursively checks if the mouse is inside a menu (or any of its parents) * and simultaneously not on the selected item of the menu. */ static int mouse_in_parent_menu(MENU_PLAYER *m) { int c; if (!m) return FALSE; c = menu_mouse_object(m); if ((c >= 0) && (c != m->sel)) return TRUE; return mouse_in_parent_menu(m->parent); } /* layout_menu: * Calculates the layout of the menu. */ static void layout_menu(MENU_PLAYER *m, MENU *menu, int bar, int x, int y, int minw, int minh) { char *buf, *tok1, *tok2; int extra = 0; int c; int child = FALSE; m->menu = menu; m->bar = bar; m->x = x; m->y = y; m->w = 3; m->h = (m->bar) ? (text_height(font)+7) : 3; m->proc = NULL; m->sel = -1; /* calculate size of the menu */ for (m->size=0; m->menu[m->size].text; m->size++) { if (m->bar) { m->w += bar_entry_length(m->menu[m->size].text); } else { if (m->menu[m->size].child) child = TRUE; if (ugetc(m->menu[m->size].text)) { buf = split_around_tab(m->menu[m->size].text, &tok1, &tok2); c = gui_strlen(tok1); } else { buf = NULL; c = 0; } m->h += text_height(font)+4; m->w = MAX(m->w, c+16); if (buf) { if (tok2) { c = gui_strlen(tok2); extra = MAX(extra, c); } free(buf); } } } if (extra) m->w += extra+16; if (child) m->w += 22; m->w = MAX(m->w, minw); m->h = MAX(m->h, minh); } /* menu_key_shortcut: * Returns true if c is indicated as a keyboard shortcut by a '&' character * in the specified string. */ static int menu_key_shortcut(int c, AL_CONST char *s) { int d; while ((d = ugetxc(&s)) != 0) { if (d == '&') { d = ugetc(s); if ((d != '&') && (utolower(d) == utolower(c & 0xFF))) return TRUE; } } return FALSE; } /* menu_alt_key: * Searches a menu for keyboard shortcuts, for the alt+letter to bring * up a menu. */ static int menu_alt_key(int k, MENU *m) { static unsigned char alt_table[] = { KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z }; AL_CONST char *s; int c, d; if (k & 0xFF) return 0; k >>= 8; c = scancode_to_ascii(k); if (c) { k = c; } else { for (c=0; c<(int)sizeof(alt_table); c++) { if (k == alt_table[c]) { k = c + 'a'; break; } } if (c >= (int)sizeof(alt_table)) return 0; } for (c=0; m[c].text; c++) { s = m[c].text; while ((d = ugetxc(&s)) != 0) { if (d == '&') { d = ugetc(s); if ((d != '&') && (utolower(d) == utolower(k))) return k; } } } return 0; } /* do_menu: * Displays and animates a popup menu at the specified screen position, * returning the index of the item that was selected, or -1 if it was * dismissed. If the menu crosses the edge of the screen it will be moved. */ int do_menu(MENU *menu, int x, int y) { MENU_PLAYER *player; int ret; ASSERT(menu); player = init_menu(menu, x ,y); while (update_menu(player)) rest(1); ret = shutdown_menu(player); do { } while (gui_mouse_b()); return ret; } /* init_single_menu: * Worker function for initialising a menu. */ static MENU_PLAYER *init_single_menu(MENU *menu, MENU_PLAYER *parent, DIALOG *dialog, int bar, int x, int y, int repos, int minw, int minh) { BITMAP *gui_bmp = gui_get_screen(); int scare = is_same_bitmap(gui_bmp, _mouse_screen); MENU_PLAYER *player; ASSERT(menu); player = malloc(sizeof(MENU_PLAYER)); if (!player) { *allegro_errno = ENOMEM; return NULL; } layout_menu(player, menu, bar, x, y, minw, minh); if (repos) { player->x = MID(0, player->x, SCREEN_W-player->w-1); player->y = MID(0, player->y, SCREEN_H-player->h-1); } if (scare) scare_mouse_area(player->x, player->y, player->w, player->h); /* save screen under the menu */ player->saved = create_bitmap(player->w, player->h); if (player->saved) blit(gui_bmp, player->saved, player->x, player->y, 0, 0, player->w, player->h); else *allegro_errno = ENOMEM; /* setup state variables */ player->sel = menu_mouse_object(player); if (scare) unscare_mouse(); player->mouse_button_was_pressed = gui_mouse_b(); player->back_from_child = FALSE; player->timestamp = gui_timer; player->mouse_sel = player->sel; player->redraw = TRUE; player->auto_open = TRUE; player->ret = -1; player->dialog = dialog; player->parent = parent; player->child = NULL; return player; } /* init_menu: * Sets up a menu, returning a menu player object that can be used * with the update_menu() and shutdown_menu() functions. */ MENU_PLAYER *init_menu(MENU *menu, int x, int y) { return init_single_menu(menu, NULL, NULL, FALSE, x, y, TRUE, 0, 0); } /* update_menu: * Updates the status of a menu player object returned by init_menu(), * returning TRUE if it is still active or FALSE if it has finished. * * The navigation through the arborescence of menus can be done: * - with the arrow keys, * - with mouse point-and-clicks, * - with mouse movements when the mouse button is being held down, * - with mouse movements only if gui_menu_opening_delay is non negative. */ int update_menu(MENU_PLAYER *player) { MENU_PLAYER *i; int c, c2; int old_sel, child_ret; int child_x, child_y; ASSERT(player); /* find activated menu */ while (player->child) player = player->child; old_sel = player->sel; c = menu_mouse_object(player); if ((gui_mouse_b()) || (c != player->mouse_sel)) { player->sel = player->mouse_sel = c; player->auto_open = TRUE; } if (gui_mouse_b()) { /* button pressed? */ /* Dismiss menu if: * - the mouse cursor is outside the menu and inside the parent menu, or * - the mouse cursor is outside the menu and the button has just been pressed. */ if (!mouse_in_single_menu(player)) { if (mouse_in_parent_menu(player->parent) || (!player->mouse_button_was_pressed)) { player->ret = -2; goto End; } } if ((player->sel >= 0) && (player->menu[player->sel].child)) /* bring up child menu? */ player->ret = player->sel; /* don't trigger the 'select' event on button press for non menu item */ player->mouse_button_was_pressed = TRUE; clear_keybuf(); } else { /* button not pressed */ /* trigger the 'select' event only on button release for non menu item */ if (player->mouse_button_was_pressed) { player->ret = player->sel; player->mouse_button_was_pressed = FALSE; } if (keypressed()) { /* keyboard input */ player->timestamp = gui_timer; player->auto_open = FALSE; c = readkey(); if ((c & 0xFF) == 27) { player->ret = -2; goto End; } switch (c >> 8) { case KEY_LEFT: if (player->parent) { if (player->parent->bar) { simulate_keypress(KEY_LEFT<<8); simulate_keypress(KEY_DOWN<<8); } player->ret = -2; goto End; } /* fall through */ case KEY_UP: if ((((c >> 8) == KEY_LEFT) && (player->bar)) || (((c >> 8) == KEY_UP) && (!player->bar))) { c = player->sel; do { c--; if (c < 0) c = player->size - 1; } while ((!ugetc(player->menu[c].text)) && (c != player->sel)); player->sel = c; } break; case KEY_RIGHT: if (((player->sel < 0) || (!player->menu[player->sel].child)) && (player->parent) && (player->parent->bar)) { simulate_keypress(KEY_RIGHT<<8); simulate_keypress(KEY_DOWN<<8); player->ret = -2; goto End; } /* fall through */ case KEY_DOWN: if ((player->sel >= 0) && (player->menu[player->sel].child) && ((((c >> 8) == KEY_RIGHT) && (!player->bar)) || (((c >> 8) == KEY_DOWN) && (player->bar)))) { player->ret = player->sel; } else if ((((c >> 8) == KEY_RIGHT) && (player->bar)) || (((c >> 8) == KEY_DOWN) && (!player->bar))) { c = player->sel; do { c++; if (c >= player->size) c = 0; } while ((!ugetc(player->menu[c].text)) && (c != player->sel)); player->sel = c; } break; case KEY_SPACE: case KEY_ENTER: if (player->sel >= 0) player->ret = player->sel; break; default: if ((!player->parent) && ((c & 0xFF) == 0)) c = menu_alt_key(c, player->menu); for (c2=0; player->menu[c2].text; c2++) { if (menu_key_shortcut(c, player->menu[c2].text)) { player->ret = player->sel = c2; break; } } if (player->parent) { i = player->parent; for (c2=0; i->parent; c2++) i = i->parent; c = menu_alt_key(c, i->menu); if (c) { while (c2-- > 0) simulate_keypress(27); simulate_keypress(c); player->ret = -2; goto End; } } break; } } } /* end of input processing */ if ((player->redraw) || (player->sel != old_sel)) { /* selection changed? */ BITMAP *gui_bmp = gui_get_screen(); int scare = is_same_bitmap(gui_bmp, _mouse_screen); player->timestamp = gui_timer; if (scare) scare_mouse_area(player->x, player->y, player->w, player->h); acquire_bitmap(gui_bmp); if (player->redraw) { draw_menu(player); player->redraw = FALSE; } else { if (old_sel >= 0) draw_menu_item(player, old_sel); if (player->sel >= 0) draw_menu_item(player, player->sel); } release_bitmap(gui_bmp); if (scare) unscare_mouse(); } if (player->auto_open && (gui_menu_opening_delay >= 0)) { /* menu auto-opening on? */ if (!mouse_in_single_menu(player)) { if (mouse_in_parent_menu(player->parent)) { /* automatically goes back to parent */ player->ret = -3; goto End; } } if ((player->mouse_sel >= 0) && (player->menu[player->mouse_sel].child)) { if (player->bar) { /* top level menu auto-opening if back from child */ if (player->back_from_child) { player->timestamp = gui_timer; player->ret = player->mouse_sel; } } else { /* sub menu auto-opening if enough time has passed */ if ((gui_timer - player->timestamp) > gui_menu_opening_delay) player->ret = player->mouse_sel; } } player->back_from_child = FALSE; } End: if (player->ret >= 0) { /* item selected? */ if (player->menu[player->ret].flags & D_DISABLED) { return TRUE; /* continue */ } else if (player->menu[player->ret].child) { /* child menu? */ if (player->bar) { get_menu_pos(player, player->ret, &child_x, &child_y, &c); child_x += 6; child_y += text_height(font) + 7; } else { child_x = player->x+player->w - 3; child_y = player->y + (text_height(font)+4)*player->ret + text_height(font)/4 + 1; } /* recursively call child menu */ player->child = init_single_menu(player->menu[player->ret].child, player, NULL, FALSE, child_x, child_y, TRUE, 0, 0); return TRUE; /* continue */ } while (player->parent) { /* parent menu? */ player = player->parent; shutdown_single_menu(player->child, NULL); player->child = NULL; } return FALSE; /* item selected */ } if (player->ret < -1) { /* dismiss menu ? */ if (player->parent) { child_ret = player->ret; /* needed below */ player = player->parent; shutdown_single_menu(player->child, NULL); player->child = NULL; player->ret = -1; player->mouse_button_was_pressed = FALSE; player->mouse_sel = menu_mouse_object(player); if (child_ret == -3) { /* return caused by mouse movement? */ player->sel = player->mouse_sel; player->redraw = TRUE; player->timestamp = gui_timer; player->back_from_child = TRUE; } return TRUE; /* return to parent */ } return FALSE; /* menu dismissed */ } /* special kludge for menu bar */ if ((player->bar) && (!gui_mouse_b()) && (!keypressed()) && (!mouse_in_single_menu(player))) return FALSE; return TRUE; } /* shutdown_single_menu: * Worker function for shutting down a menu. */ static int shutdown_single_menu(MENU_PLAYER *player, int *dret) { int ret; ASSERT(player); if (dret) *dret = 0; if ((!player->proc) && (player->ret >= 0)) { /* callback function? */ active_menu = &player->menu[player->ret]; player->proc = active_menu->proc; } if (player->ret >= 0) { if (player->parent) player->parent->proc = player->proc; else { if (player->proc) { ret = player->proc(); if (dret) *dret = ret; } } } /* restore screen */ if (player->saved) { BITMAP *gui_bmp = gui_get_screen(); int scare = is_same_bitmap(gui_bmp, _mouse_screen); if (scare) scare_mouse_area(player->x, player->y, player->w, player->h); blit(player->saved, gui_bmp, 0, 0, player->x, player->y, player->w, player->h); if (scare) unscare_mouse(); destroy_bitmap(player->saved); } ret = player->ret; free(player); return ret; } /* shutdown_tree_menu: * Destroys a menu player object returned by init_single_menu(), after * recursively closing all the sub-menus if necessary, and returns the * index of the item that was selected, or -1 if it was dismissed. */ static int shutdown_tree_menu(MENU_PLAYER *player, int *dret) { ASSERT(player); if (player->child) { shutdown_tree_menu(player->child, dret); player->child = NULL; } return shutdown_single_menu(player, dret); } /* shutdown_menu: * Destroys a menu player object returned by init_menu() and returns * the index of the item that was selected, or -1 if it was dismissed. */ int shutdown_menu(MENU_PLAYER *player) { return shutdown_tree_menu(player, NULL); } /* d_menu_proc: * Dialog procedure for adding drop down menus to a GUI dialog. This * displays the top level menu items as a horizontal bar (eg. across the * top of the screen), and pops up child menus when they are clicked. * When it executes one of the menu callback routines, it passes the * return value back to the dialog manager, so these can return D_O_K, * D_CLOSE, D_REDRAW, etc. */ int d_menu_proc(int msg, DIALOG *d, int c) { MENU_PLAYER m, *mp; int ret = D_O_K; int x, i; ASSERT(d); switch (msg) { case MSG_START: layout_menu(&m, d->dp, TRUE, d->x, d->y, d->w, d->h); d->w = m.w; d->h = m.h; break; case MSG_DRAW: layout_menu(&m, d->dp, TRUE, d->x, d->y, d->w, d->h); draw_menu(&m); break; case MSG_XCHAR: x = menu_alt_key(c, d->dp); if (!x) break; ret |= D_USED_CHAR; simulate_keypress(x); /* fall through */ case MSG_GOTMOUSE: case MSG_CLICK: /* steal the mouse */ for (i=0; active_dialog[i].proc; i++) if (active_dialog[i].flags & D_GOTMOUSE) { active_dialog[i].flags &= ~D_GOTMOUSE; object_message(active_dialog+i, MSG_LOSTMOUSE, 0); break; } /* initialize the menu */ active_menu_player = init_single_menu(d->dp, NULL, d, TRUE, d->x, d->y, FALSE, d->w, d->h); break; case MSG_LOSTMOUSE: case MSG_END: if (active_menu_player) { /* shutdown_tree_menu may call nested dialogs */ mp = active_menu_player; active_menu_player = NULL; shutdown_tree_menu(mp, &x); ret |= x; /* put the mouse */ i = find_mouse_object(active_dialog); if ((i >= 0) && (&active_dialog[i] != d)) { active_dialog[i].flags |= D_GOTMOUSE; object_message(active_dialog+i, MSG_GOTMOUSE, 0); } } break; } return ret; } static DIALOG alert_dialog[] = { /* (dialog proc) (x) (y) (w) (h) (fg) (bg) (key) (flags) (d1) (d2) (dp) (dp2) (dp3) */ { _gui_shadow_box_proc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL }, { _gui_ctext_proc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL }, { _gui_ctext_proc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL }, { _gui_ctext_proc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL }, { _gui_button_proc, 0, 0, 0, 0, 0, 0, 0, D_EXIT, 0, 0, NULL, NULL, NULL }, { _gui_button_proc, 0, 0, 0, 0, 0, 0, 0, D_EXIT, 0, 0, NULL, NULL, NULL }, { _gui_button_proc, 0, 0, 0, 0, 0, 0, 0, D_EXIT, 0, 0, NULL, NULL, NULL }, { d_yield_proc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL }, { NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL } }; #define A_S1 1 #define A_S2 2 #define A_S3 3 #define A_B1 4 #define A_B2 5 #define A_B3 6 /* alert3: * Displays a simple alert box, containing three lines of text (s1-s3), * and with either one, two, or three buttons. The text for these buttons * is passed in b1, b2, and b3 (NULL for buttons which are not used), and * the keyboard shortcuts in c1 and c2. Returns 1, 2, or 3 depending on * which button was selected. */ int alert3(AL_CONST char *s1, AL_CONST char *s2, AL_CONST char *s3, AL_CONST char *b1, AL_CONST char *b2, AL_CONST char *b3, int c1, int c2, int c3) { char tmp[16]; int avg_w, avg_h; int len1, len2, len3; int maxlen = 0; int buttons = 0; int b[3]; int c; #define SORT_OUT_BUTTON(x) { \ if (b##x) { \ alert_dialog[A_B##x].flags &= ~D_HIDDEN; \ alert_dialog[A_B##x].key = c##x; \ alert_dialog[A_B##x].dp = (char *)b##x; \ len##x = gui_strlen(b##x); \ b[buttons++] = A_B##x; \ } \ else { \ alert_dialog[A_B##x].flags |= D_HIDDEN; \ len##x = 0; \ } \ } usetc(tmp+usetc(tmp, ' '), 0); avg_w = text_length(font, tmp); avg_h = text_height(font); alert_dialog[A_S1].dp = alert_dialog[A_S2].dp = alert_dialog[A_S3].dp = alert_dialog[A_B1].dp = alert_dialog[A_B2].dp = empty_string; if (s1) { alert_dialog[A_S1].dp = (char *)s1; maxlen = text_length(font, s1); } if (s2) { alert_dialog[A_S2].dp = (char *)s2; len1 = text_length(font, s2); if (len1 > maxlen) maxlen = len1; } if (s3) { alert_dialog[A_S3].dp = (char *)s3; len1 = text_length(font, s3); if (len1 > maxlen) maxlen = len1; } SORT_OUT_BUTTON(1); SORT_OUT_BUTTON(2); SORT_OUT_BUTTON(3); len1 = MAX(len1, MAX(len2, len3)) + avg_w*3; if (len1*buttons > maxlen) maxlen = len1*buttons; maxlen += avg_w*4; alert_dialog[0].w = maxlen; alert_dialog[A_S1].w = alert_dialog[A_S2].w = alert_dialog[A_S3].w = maxlen - avg_w*2; alert_dialog[A_S1].x = alert_dialog[A_S2].x = alert_dialog[A_S3].x = alert_dialog[0].x + avg_w; alert_dialog[A_B1].w = alert_dialog[A_B2].w = alert_dialog[A_B3].w = len1; alert_dialog[A_B1].x = alert_dialog[A_B2].x = alert_dialog[A_B3].x = alert_dialog[0].x + maxlen/2 - len1/2; if (buttons == 3) { alert_dialog[b[0]].x = alert_dialog[0].x + maxlen/2 - len1*3/2 - avg_w; alert_dialog[b[2]].x = alert_dialog[0].x + maxlen/2 + len1/2 + avg_w; } else if (buttons == 2) { alert_dialog[b[0]].x = alert_dialog[0].x + maxlen/2 - len1 - avg_w; alert_dialog[b[1]].x = alert_dialog[0].x + maxlen/2 + avg_w; } alert_dialog[0].h = avg_h*8; alert_dialog[A_S1].y = alert_dialog[0].y + avg_h; alert_dialog[A_S2].y = alert_dialog[0].y + avg_h*2; alert_dialog[A_S3].y = alert_dialog[0].y + avg_h*3; alert_dialog[A_S1].h = alert_dialog[A_S2].h = alert_dialog[A_S3].h = avg_h; alert_dialog[A_B1].y = alert_dialog[A_B2].y = alert_dialog[A_B3].y = alert_dialog[0].y + avg_h*5; alert_dialog[A_B1].h = alert_dialog[A_B2].h = alert_dialog[A_B3].h = avg_h*2; centre_dialog(alert_dialog); set_dialog_color(alert_dialog, gui_fg_color, gui_bg_color); for (c = 0; alert_dialog[c].proc; c++) if (alert_dialog[c].proc == _gui_ctext_proc) alert_dialog[c].bg = -1; clear_keybuf(); do { } while (gui_mouse_b()); c = popup_dialog(alert_dialog, A_B1); if (c == A_B1) return 1; else if (c == A_B2) return 2; else return 3; } /* alert: * Displays a simple alert box, containing three lines of text (s1-s3), * and with either one or two buttons. The text for these buttons is passed * in b1 and b2 (b2 may be null), and the keyboard shortcuts in c1 and c2. * Returns 1 or 2 depending on which button was selected. */ int alert(AL_CONST char *s1, AL_CONST char *s2, AL_CONST char *s3, AL_CONST char *b1, AL_CONST char *b2, int c1, int c2) { int ret; ret = alert3(s1, s2, s3, b1, b2, NULL, c1, c2, 0); if (ret > 2) ret = 2; return ret; }