1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      The core GUI routines.
12  *
13  *      By Shawn Hargreaves.
14  *
15  *      Peter Pavlovic modified the drawing and positioning of menus.
16  *
17  *      Menu auto-opening added by Angelo Mottola.
18  *
19  *      Eric Botcazou added the support for non-blocking menus.
20  *
21  *      Elias Pschernig and Sven Sandberg improved the focus algorithm.
22  *
23  *      See readme.txt for copyright information.
24  */
25 
26 
27 #include <limits.h>
28 
29 #include "allegro.h"
30 #include "allegro/internal/aintern.h"
31 
32 
33 
34 /* if set, the input focus follows the mouse pointer */
35 int gui_mouse_focus = TRUE;
36 
37 
38 /* font alignment value */
39 int gui_font_baseline = 0;
40 
41 
42 /* Pointers to the currently active dialog and menu objects.
43  *
44  * Note: active_dialog_player always points to the currently active dialog
45  * player. However, active_menu_player only ever points to menu players
46  * started by a d_menu_proc. The code also assumes that that d_menu_proc can
47  * be found in the currently active dialog.
48  *
49  * Note: active_dialog points to the whole dialog currently running. However,
50  * active_menu points to the *current item* of the currently running menu,
51  * and should really have been called active_menu_item.
52  */
53 static DIALOG_PLAYER *active_dialog_player = NULL;
54 static MENU_PLAYER *active_menu_player = NULL;
55 static int active_menu_player_zombie = FALSE;
56 DIALOG *active_dialog = NULL;
57 MENU *active_menu = NULL;
58 
59 
60 static BITMAP *gui_screen = NULL;
61 
62 
63 /* list of currently active (initialized) dialog players */
64 struct al_active_dialog_player {
65    DIALOG_PLAYER *player;
66    struct al_active_dialog_player *next;
67 };
68 
69 static struct al_active_dialog_player *first_active_dialog_player = 0;
70 static struct al_active_dialog_player *current_active_dialog_player = 0;
71 
72 
73 /* forward declarations */
74 static int shutdown_single_menu(MENU_PLAYER *, int *);
75 
76 
77 
78 /* hook function for reading the mouse x position */
default_mouse_x(void)79 static int default_mouse_x(void)
80 {
81    if (mouse_needs_poll())
82       poll_mouse();
83 
84    return mouse_x;
85 }
86 
87 END_OF_STATIC_FUNCTION(default_mouse_x);
88 
89 
90 
91 /* hook function for reading the mouse y position */
default_mouse_y(void)92 static int default_mouse_y(void)
93 {
94    if (mouse_needs_poll())
95       poll_mouse();
96 
97    return mouse_y;
98 }
99 
100 END_OF_STATIC_FUNCTION(default_mouse_y);
101 
102 
103 
104 /* hook function for reading the mouse z position */
default_mouse_z(void)105 static int default_mouse_z(void)
106 {
107    if (mouse_needs_poll())
108       poll_mouse();
109 
110    return mouse_z;
111 }
112 
113 END_OF_STATIC_FUNCTION(default_mouse_z);
114 
115 
116 
117 /* hook function for reading the mouse button state */
default_mouse_b(void)118 static int default_mouse_b(void)
119 {
120    if (mouse_needs_poll())
121       poll_mouse();
122 
123    return mouse_b;
124 }
125 
126 END_OF_STATIC_FUNCTION(default_mouse_b);
127 
128 
129 
130 /* hook functions for reading the mouse state */
131 int (*gui_mouse_x)(void) = default_mouse_x;
132 int (*gui_mouse_y)(void) = default_mouse_y;
133 int (*gui_mouse_z)(void) = default_mouse_z;
134 int (*gui_mouse_b)(void) = default_mouse_b;
135 
136 
137 /* timer to handle menu auto-opening */
138 static int gui_timer;
139 
140 static int gui_menu_opening_delay;
141 
142 
143 /* Checking for double clicks is complicated. The user could release the
144  * mouse button at almost any point, and I might miss it if I am doing some
145  * other processing at the same time (eg. sending the single-click message).
146  * To get around this I install a timer routine to do the checking for me,
147  * so it will notice double clicks whenever they happen.
148  */
149 
150 static volatile int dclick_status, dclick_time;
151 
152 static int gui_install_count = 0;
153 static int gui_install_time = 0;
154 
155 #define DCLICK_START      0
156 #define DCLICK_RELEASE    1
157 #define DCLICK_AGAIN      2
158 #define DCLICK_NOT        3
159 
160 
161 
162 /* dclick_check:
163  *  Double click checking user timer routine.
164  */
dclick_check(void)165 static void dclick_check(void)
166 {
167    gui_timer++;
168 
169    if (dclick_status==DCLICK_START) {              /* first click... */
170       if (!gui_mouse_b()) {
171 	 dclick_status = DCLICK_RELEASE;           /* aah! released first */
172 	 dclick_time = 0;
173 	 return;
174       }
175    }
176    else if (dclick_status==DCLICK_RELEASE) {       /* wait for second click */
177       if (gui_mouse_b()) {
178 	 dclick_status = DCLICK_AGAIN;             /* yes! the second click */
179 	 dclick_time = 0;
180 	 return;
181       }
182    }
183    else
184       return;
185 
186    /* timeout? */
187    if (dclick_time++ > 10)
188       dclick_status = DCLICK_NOT;
189 }
190 
191 END_OF_STATIC_FUNCTION(dclick_check);
192 
193 
194 
195 /* gui_switch_callback:
196  *  Sets the dirty flags whenever a display switch occurs.
197  */
gui_switch_callback(void)198 static void gui_switch_callback(void)
199 {
200    if (active_dialog_player)
201       active_dialog_player->res |= D_REDRAW_ALL;
202 }
203 
204 
205 
206 /* position_dialog:
207  *  Moves all the objects in a dialog to the specified position.
208  */
position_dialog(DIALOG * dialog,int x,int y)209 void position_dialog(DIALOG *dialog, int x, int y)
210 {
211    int min_x = INT_MAX;
212    int min_y = INT_MAX;
213    int xc, yc;
214    int c;
215    ASSERT(dialog);
216 
217    /* locate the upper-left corner */
218    for (c=0; dialog[c].proc; c++) {
219       if (dialog[c].x < min_x)
220 	 min_x = dialog[c].x;
221 
222       if (dialog[c].y < min_y)
223 	 min_y = dialog[c].y;
224    }
225 
226    /* move the dialog */
227    xc = min_x - x;
228    yc = min_y - y;
229 
230    for (c=0; dialog[c].proc; c++) {
231       dialog[c].x -= xc;
232       dialog[c].y -= yc;
233    }
234 }
235 
236 
237 
238 /* centre_dialog:
239  *  Moves all the objects in a dialog so that the dialog is centered in
240  *  the screen.
241  */
centre_dialog(DIALOG * dialog)242 void centre_dialog(DIALOG *dialog)
243 {
244    int min_x = INT_MAX;
245    int min_y = INT_MAX;
246    int max_x = INT_MIN;
247    int max_y = INT_MIN;
248    int xc, yc;
249    int c;
250    ASSERT(dialog);
251 
252    /* find the extents of the dialog (done in many loops due to a bug
253     * in MSVC that prevents the more sensible version from working)
254     */
255    for (c=0; dialog[c].proc; c++) {
256       if (dialog[c].x < min_x)
257 	 min_x = dialog[c].x;
258    }
259 
260    for (c=0; dialog[c].proc; c++) {
261       if (dialog[c].y < min_y)
262 	 min_y = dialog[c].y;
263    }
264 
265    for (c=0; dialog[c].proc; c++) {
266       if (dialog[c].x + dialog[c].w > max_x)
267 	 max_x = dialog[c].x + dialog[c].w;
268    }
269 
270    for (c=0; dialog[c].proc; c++) {
271       if (dialog[c].y + dialog[c].h > max_y)
272 	 max_y = dialog[c].y + dialog[c].h;
273    }
274 
275    /* how much to move by? */
276    xc = (SCREEN_W - (max_x - min_x)) / 2 - min_x;
277    yc = (SCREEN_H - (max_y - min_y)) / 2 - min_y;
278 
279    /* move it */
280    for (c=0; dialog[c].proc; c++) {
281       dialog[c].x += xc;
282       dialog[c].y += yc;
283    }
284 }
285 
286 
287 
288 /* set_dialog_color:
289  *  Sets the foreground and background colors of all the objects in a dialog.
290  */
set_dialog_color(DIALOG * dialog,int fg,int bg)291 void set_dialog_color(DIALOG *dialog, int fg, int bg)
292 {
293    int c;
294    ASSERT(dialog);
295 
296    for (c=0; dialog[c].proc; c++) {
297       dialog[c].fg = fg;
298       dialog[c].bg = bg;
299    }
300 }
301 
302 
303 
304 /* find_dialog_focus:
305  *  Searches the dialog for the object which has the input focus, returning
306  *  its index, or -1 if the focus is not set. Useful after do_dialog() exits
307  *  if you need to know which object was selected.
308  */
find_dialog_focus(DIALOG * dialog)309 int find_dialog_focus(DIALOG *dialog)
310 {
311    int c;
312    ASSERT(dialog);
313 
314    for (c=0; dialog[c].proc; c++)
315       if (dialog[c].flags & D_GOTFOCUS)
316 	 return c;
317 
318    return -1;
319 }
320 
321 
322 
323 /* object_message:
324  *  Sends a message to a widget, automatically scaring and unscaring
325  *  the mouse if the message is MSG_DRAW.
326  */
object_message(DIALOG * dialog,int msg,int c)327 int object_message(DIALOG *dialog, int msg, int c)
328 {
329 #ifdef ALLEGRO_WINDOWS
330    /* exported address of d_clear_proc */
331    extern int (*_d_clear_proc)(int, DIALOG *, int);
332 #endif
333 
334    int ret;
335    ASSERT(dialog);
336 
337    if (msg == MSG_DRAW) {
338       if (dialog->flags & D_HIDDEN)
339 	 return D_O_K;
340 
341 #ifdef ALLEGRO_WINDOWS
342       if (dialog->proc == _d_clear_proc)
343 #else
344       if (dialog->proc == d_clear_proc)
345 #endif
346 	 scare_mouse();
347       else
348 	 scare_mouse_area(dialog->x, dialog->y, dialog->w, dialog->h);
349 
350       acquire_screen();
351    }
352 
353    ret = dialog->proc(msg, dialog, c);
354 
355    if (msg == MSG_DRAW) {
356       release_screen();
357       unscare_mouse();
358    }
359 
360    if (ret & D_REDRAWME) {
361       dialog->flags |= D_DIRTY;
362       ret &= ~D_REDRAWME;
363    }
364 
365    return ret;
366 }
367 
368 
369 
370 /* dialog_message:
371  *  Sends a message to all the objects in a dialog. If any of the objects
372  *  return values other than D_O_K, returns the value and sets obj to the
373  *  object which produced it.
374  */
dialog_message(DIALOG * dialog,int msg,int c,int * obj)375 int dialog_message(DIALOG *dialog, int msg, int c, int *obj)
376 {
377    int count, res, r, force, try;
378    DIALOG *menu_dialog = NULL;
379    ASSERT(dialog);
380 
381    /* Note: don't acquire the screen in here.  A deadlock develops in a
382     * situation like this:
383     *
384     * 1. this thread: acquires the screen;
385     * 2. timer thread: wakes up, locks the timer_mutex, and calls a
386     *    callback to redraw the software mouse cursor, which tries to
387     *    acquire the screen;
388     * 3. this thread: object_message(MSG_DRAW) calls scare_mouse()
389     *    which calls remove_int().
390     *
391     * So this thread has the screen acquired and wants the timer_mutex,
392     * whereas the timer thread holds the timer_mutex, but wants to acquire
393     * the screen.  The problem is calling scare_mouse() with the screen
394     * acquired.
395     */
396 
397    force = ((msg == MSG_START) || (msg == MSG_END) || (msg >= MSG_USER));
398 
399    res = D_O_K;
400 
401    /* If a menu spawned by a d_menu_proc object is active, the dialog engine
402     * has effectively been shutdown for the sake of safety. This means that
403     * we can't send the message to the other objects in the dialog. So try
404     * first to send the message to the d_menu_proc object and, if the menu
405     * is then not active anymore, send it to the other objects as well.
406     */
407    if (active_menu_player) {
408       try = 2;
409       menu_dialog = active_menu_player->dialog;
410    }
411    else
412       try = 1;
413 
414    for (; try > 0; try--) {
415       for (count=0; dialog[count].proc; count++) {
416          if ((try == 2) && (&dialog[count] != menu_dialog))
417 	    continue;
418 
419 	 if ((force) || (!(dialog[count].flags & D_HIDDEN))) {
420 	    r = object_message(&dialog[count], msg, c);
421 
422 	    if (r != D_O_K) {
423 	       res |= r;
424 	       if (obj)
425 		  *obj = count;
426 	    }
427 
428 	    if ((msg == MSG_IDLE) && (dialog[count].flags & (D_DIRTY | D_HIDDEN)) == D_DIRTY) {
429 	       dialog[count].flags &= ~D_DIRTY;
430 	       object_message(dialog+count, MSG_DRAW, 0);
431 	    }
432 	 }
433       }
434 
435       if (active_menu_player)
436 	 break;
437    }
438 
439    return res;
440 }
441 
442 
443 
444 /* broadcast_dialog_message:
445  *  Broadcasts a message to all the objects in the active dialog. If any of
446  *  the dialog procedures return values other than D_O_K, it returns that
447  *  value.
448  */
broadcast_dialog_message(int msg,int c)449 int broadcast_dialog_message(int msg, int c)
450 {
451    int nowhere;
452 
453    if (active_dialog)
454       return dialog_message(active_dialog, msg, c, &nowhere);
455    else
456       return D_O_K;
457 }
458 
459 
460 
461 /* find_mouse_object:
462  *  Finds which object the mouse is on top of.
463  */
find_mouse_object(DIALOG * d)464 static int find_mouse_object(DIALOG *d)
465 {
466    int mouse_object = -1;
467    int c;
468    int res;
469    ASSERT(d);
470 
471    for (c=0; d[c].proc; c++) {
472       if ((gui_mouse_x() >= d[c].x) && (gui_mouse_y() >= d[c].y) &&
473          (gui_mouse_x() < d[c].x + d[c].w) && (gui_mouse_y() < d[c].y + d[c].h) &&
474          (!(d[c].flags & (D_HIDDEN | D_DISABLED)))) {
475          /* check if this object wants the mouse */
476          res = object_message(d+c, MSG_WANTMOUSE, 0);
477          if (!(res & D_DONTWANTMOUSE)) {
478             mouse_object = c;
479          }
480       }
481    }
482 
483    return mouse_object;
484 }
485 
486 
487 
488 /* offer_focus:
489  *  Offers the input focus to a particular object.
490  */
offer_focus(DIALOG * dialog,int obj,int * focus_obj,int force)491 int offer_focus(DIALOG *dialog, int obj, int *focus_obj, int force)
492 {
493    int res = D_O_K;
494    ASSERT(dialog);
495    ASSERT(focus_obj);
496 
497    if ((obj == *focus_obj) ||
498        ((obj >= 0) && (dialog[obj].flags & (D_HIDDEN | D_DISABLED))))
499       return D_O_K;
500 
501    /* check if object wants the focus */
502    if (obj >= 0) {
503       res = object_message(dialog+obj, MSG_WANTFOCUS, 0);
504       if (res & D_WANTFOCUS)
505 	 res ^= D_WANTFOCUS;
506       else
507 	 obj = -1;
508    }
509 
510    if ((obj >= 0) || (force)) {
511       /* take focus away from old object */
512       if (*focus_obj >= 0) {
513 	 res |= object_message(dialog+*focus_obj, MSG_LOSTFOCUS, 0);
514 	 if (res & D_WANTFOCUS) {
515 	    if (obj < 0)
516 	       return D_O_K;
517 	    else
518 	       res &= ~D_WANTFOCUS;
519 	 }
520 	 dialog[*focus_obj].flags &= ~D_GOTFOCUS;
521 	 res |= object_message(dialog+*focus_obj, MSG_DRAW, 0);
522       }
523 
524       *focus_obj = obj;
525 
526       /* give focus to new object */
527       if (obj >= 0) {
528 	 dialog[obj].flags |= D_GOTFOCUS;
529 	 res |= object_message(dialog+obj, MSG_GOTFOCUS, 0);
530 	 res |= object_message(dialog+obj, MSG_DRAW, 0);
531       }
532    }
533 
534    return res;
535 }
536 
537 
538 
539 #define MAX_OBJECTS     512
540 
541 typedef struct OBJ_LIST
542 {
543    int index;
544    int diff;
545 } OBJ_LIST;
546 
547 
548 /* Weight ratio between the orthogonal direction and the main direction
549    when calculating the distance for the focus algorithm. */
550 #define DISTANCE_RATIO  8
551 
552 /* Maximum size (in bytes) of a dialog array. */
553 #define MAX_SIZE  0x10000  /* 64 kb */
554 
555 enum axis { X_AXIS, Y_AXIS };
556 
557 
558 
559 /* obj_list_cmp:
560  *  Callback function for qsort().
561  */
obj_list_cmp(AL_CONST void * e1,AL_CONST void * e2)562 static int obj_list_cmp(AL_CONST void *e1, AL_CONST void *e2)
563 {
564    return (((OBJ_LIST *)e1)->diff - ((OBJ_LIST *)e2)->diff);
565 }
566 
567 
568 
569 /* cmp_tab:
570  *  Comparison function for tab key movement.
571  */
cmp_tab(AL_CONST DIALOG * d1,AL_CONST DIALOG * d2)572 static int cmp_tab(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2)
573 {
574    int ret = (int)((AL_CONST unsigned long)d2 - (AL_CONST unsigned long)d1);
575 
576    /* Wrap around if d2 is before d1 in the dialog array. */
577    if (ret < 0)
578       ret += MAX_SIZE;
579 
580    return ret;
581 }
582 
583 
584 
585 /* cmp_shift_tab:
586  *  Comparison function for shift+tab key movement.
587  */
cmp_shift_tab(AL_CONST DIALOG * d1,AL_CONST DIALOG * d2)588 static int cmp_shift_tab(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2)
589 {
590    int ret = (int)((AL_CONST unsigned long)d1 - (AL_CONST unsigned long)d2);
591 
592    /* Wrap around if d2 is after d1 in the dialog array. */
593    if (ret < 0)
594       ret += MAX_SIZE;
595 
596    return ret;
597 }
598 
599 
600 
601 /* min_dist:
602  *  Returns the minimum distance between dialogs 'd1' and 'd2'. 'main_axis'
603  *  is taken account to give different weights to the axes in the distance
604  *  formula, as well as to shift the actual position of 'd2' along the axis
605  *  by the amount specified by 'bias'.
606  */
min_dist(AL_CONST DIALOG * d1,AL_CONST DIALOG * d2,enum axis main_axis,int bias)607 static int min_dist(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2, enum axis main_axis, int bias)
608 {
609    int x_left = d1->x - d2->x - d2->w + 1;
610    int x_right = d2->x - d1->x - d1->w + 1;
611    int y_top = d1->y - d2->y - d2->h + 1;
612    int y_bottom = d2->y - d1->y - d1->h + 1;
613 
614    if (main_axis == X_AXIS) {
615       x_left -= bias;
616       x_right += bias;
617       y_top *= DISTANCE_RATIO;
618       y_bottom *= DISTANCE_RATIO;
619    }
620    else {
621       x_left *= DISTANCE_RATIO;
622       x_right *= DISTANCE_RATIO;
623       y_top -= bias;
624       y_bottom += bias;
625    }
626 
627    if (x_left > 0) { /* d2 is left of d1 */
628       if (y_top > 0)  /* d2 is above d1 */
629          return x_left + y_top;
630       else if (y_bottom > 0)  /* d2 is below d1 */
631          return x_left + y_bottom;
632       else  /* vertically overlapping */
633          return x_left;
634    }
635    else if (x_right > 0) { /* d2 is right of d1 */
636       if (y_top > 0)  /* d2 is above d1 */
637          return x_right + y_top;
638       else if (y_bottom > 0)  /* d2 is below d1 */
639          return x_right + y_bottom;
640       else  /* vertically overlapping */
641          return x_right;
642    }
643    /* horizontally overlapping */
644    else if (y_top > 0)  /* d2 is above d1 */
645       return y_top;
646    else if (y_bottom > 0)  /* d2 is below d1 */
647       return y_bottom;
648    else  /* overlapping */
649       return 0;
650 }
651 
652 
653 
654 /* cmp_right:
655  *  Comparison function for right arrow key movement.
656  */
cmp_right(AL_CONST DIALOG * d1,AL_CONST DIALOG * d2)657 static int cmp_right(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2)
658 {
659    int bias;
660 
661    /* Wrap around if d2 is not fully contained in the half-plan
662       delimited by d1's right edge and not containing it. */
663    if (d2->x < d1->x + d1->w)
664       bias = +SCREEN_W;
665    else
666       bias = 0;
667 
668    return min_dist(d1, d2, X_AXIS, bias);
669 }
670 
671 
672 
673 /* cmp_left:
674  *  Comparison function for left arrow key movement.
675  */
cmp_left(AL_CONST DIALOG * d1,AL_CONST DIALOG * d2)676 static int cmp_left(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2)
677 {
678    int bias;
679 
680    /* Wrap around if d2 is not fully contained in the half-plan
681       delimited by d1's left edge and not containing it. */
682    if (d2->x + d2->w > d1->x)
683       bias = -SCREEN_W;
684    else
685       bias = 0;
686 
687    return min_dist(d1, d2, X_AXIS, bias);
688 }
689 
690 
691 
692 /* cmp_down:
693  *  Comparison function for down arrow key movement.
694  */
cmp_down(AL_CONST DIALOG * d1,AL_CONST DIALOG * d2)695 static int cmp_down(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2)
696 {
697    int bias;
698 
699    /* Wrap around if d2 is not fully contained in the half-plan
700       delimited by d1's bottom edge and not containing it. */
701    if (d2->y < d1->y + d1->h)
702       bias = +SCREEN_H;
703    else
704       bias = 0;
705 
706    return min_dist(d1, d2, Y_AXIS, bias);
707 }
708 
709 
710 
711 /* cmp_up:
712  *  Comparison function for up arrow key movement.
713  */
cmp_up(AL_CONST DIALOG * d1,AL_CONST DIALOG * d2)714 static int cmp_up(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2)
715 {
716    int bias;
717 
718    /* Wrap around if d2 is not fully contained in the half-plan
719       delimited by d1's top edge and not containing it. */
720    if (d2->y + d2->h > d1->y)
721       bias = -SCREEN_H;
722    else
723       bias = 0;
724 
725    return min_dist(d1, d2, Y_AXIS, bias);
726 }
727 
728 
729 
730 /* move_focus:
731  *  Handles arrow key and tab movement through a dialog, deciding which
732  *  object should be given the input focus.
733  */
move_focus(DIALOG * d,int ascii,int scan,int * focus_obj)734 static int move_focus(DIALOG *d, int ascii, int scan, int *focus_obj)
735 {
736    int (*cmp)(AL_CONST DIALOG *d1, AL_CONST DIALOG *d2);
737    OBJ_LIST obj[MAX_OBJECTS];
738    int obj_count = 0;
739    int fobj, c;
740    int res = D_O_K;
741 
742    /* choose a comparison function */
743    switch (scan) {
744       case KEY_TAB:   cmp = (ascii == '\t') ? cmp_tab : cmp_shift_tab; break;
745       case KEY_RIGHT: cmp = cmp_right; break;
746       case KEY_LEFT:  cmp = cmp_left;  break;
747       case KEY_DOWN:  cmp = cmp_down;  break;
748       case KEY_UP:    cmp = cmp_up;    break;
749       default:        return D_O_K;
750    }
751 
752    /* fill temporary table */
753    for (c=0; d[c].proc; c++) {
754       if (((*focus_obj < 0) || (c != *focus_obj))
755 	  && !(d[c].flags & (D_DISABLED | D_HIDDEN))) {
756 	 obj[obj_count].index = c;
757 	 if (*focus_obj >= 0)
758 	    obj[obj_count].diff = cmp(d+*focus_obj, d+c);
759 	 else
760 	    obj[obj_count].diff = c;
761 	 obj_count++;
762 	 if (obj_count >= MAX_OBJECTS)
763 	    break;
764       }
765    }
766 
767    /* sort table */
768    qsort(obj, obj_count, sizeof(OBJ_LIST), obj_list_cmp);
769 
770    /* find an object to give the focus to */
771    fobj = *focus_obj;
772    for (c=0; c<obj_count; c++) {
773       res |= offer_focus(d, obj[c].index, focus_obj, FALSE);
774       if (fobj != *focus_obj)
775 	 break;
776    }
777 
778    return res;
779 }
780 
781 
782 
783 #define MESSAGE(i, msg, c) {                       \
784    r = object_message(player->dialog+i, msg, c);   \
785    if (r != D_O_K) {                               \
786       player->res |= r;                            \
787       player->obj = i;                             \
788    }                                               \
789 }
790 
791 
792 
793 /* do_dialog:
794  *  The basic dialog manager. The list of dialog objects should be
795  *  terminated by one with a null dialog procedure. Returns the index of
796  *  the object which caused it to exit.
797  */
do_dialog(DIALOG * dialog,int focus_obj)798 int do_dialog(DIALOG *dialog, int focus_obj)
799 {
800    BITMAP *mouse_screen = _mouse_screen;
801    BITMAP *gui_bmp = gui_get_screen();
802    int screen_count = _gfx_mode_set_count;
803    void *player;
804    ASSERT(dialog);
805 
806    if (!is_same_bitmap(_mouse_screen, gui_bmp) && !(gfx_capabilities&GFX_HW_CURSOR))
807       show_mouse(gui_bmp);
808 
809    player = init_dialog(dialog, focus_obj);
810 
811    while (update_dialog(player)) {
812       /* If a menu is active, we yield here, since the dialog
813        * engine is shut down so no user code can be running.
814        */
815       if (active_menu_player)
816          rest(1);
817    }
818 
819    if (_gfx_mode_set_count == screen_count && !(gfx_capabilities&GFX_HW_CURSOR))
820       show_mouse(mouse_screen);
821 
822    return shutdown_dialog(player);
823 }
824 
825 
826 
827 /* popup_dialog:
828  *  Like do_dialog(), but it stores the data on the screen before drawing
829  *  the dialog and restores it when the dialog is closed. The screen area
830  *  to be stored is calculated from the dimensions of the first object in
831  *  the dialog, so all the other objects should lie within this one.
832  */
popup_dialog(DIALOG * dialog,int focus_obj)833 int popup_dialog(DIALOG *dialog, int focus_obj)
834 {
835    BITMAP *bmp;
836    BITMAP *gui_bmp;
837    int ret;
838    ASSERT(dialog);
839 
840    bmp = create_bitmap(dialog->w, dialog->h);
841    gui_bmp = gui_get_screen();
842    if (bmp) {
843       scare_mouse_area(dialog->x, dialog->y, dialog->w, dialog->h);
844       blit(gui_bmp, bmp, dialog->x, dialog->y, 0, 0, dialog->w, dialog->h);
845       unscare_mouse();
846    }
847    else
848       *allegro_errno = ENOMEM;
849 
850    ret = do_dialog(dialog, focus_obj);
851 
852    if (bmp) {
853       scare_mouse_area(dialog->x, dialog->y, dialog->w, dialog->h);
854       blit(bmp, gui_bmp, 0, 0, dialog->x, dialog->y, dialog->w, dialog->h);
855       unscare_mouse();
856       destroy_bitmap(bmp);
857    }
858 
859    return ret;
860 }
861 
862 
863 
864 /* init_dialog:
865  *  Sets up a dialog, returning a player object that can be used with
866  *  the update_dialog() and shutdown_dialog() functions.
867  */
init_dialog(DIALOG * dialog,int focus_obj)868 DIALOG_PLAYER *init_dialog(DIALOG *dialog, int focus_obj)
869 {
870    DIALOG_PLAYER *player;
871    BITMAP *gui_bmp = gui_get_screen();
872    struct al_active_dialog_player *n;
873    char tmp1[64], tmp2[64];
874    int c;
875    ASSERT(dialog);
876 
877    /* close any menu opened by a d_menu_proc */
878    if (active_menu_player)
879       object_message(active_menu_player->dialog, MSG_LOSTMOUSE, 0);
880 
881    player = _AL_MALLOC(sizeof(DIALOG_PLAYER));
882    if (!player) {
883       *allegro_errno = ENOMEM;
884       return NULL;
885    }
886 
887    /* append player to the list */
888    n = _AL_MALLOC(sizeof(struct al_active_dialog_player));
889    if (!n) {
890       *allegro_errno = ENOMEM;
891       _AL_FREE (player);
892       return NULL;
893    }
894 
895    n->next = NULL;
896    n->player = player;
897 
898    if (!current_active_dialog_player) {
899       current_active_dialog_player = first_active_dialog_player = n;
900    }
901    else {
902       current_active_dialog_player->next = n;
903       current_active_dialog_player = n;
904    }
905 
906    player->res = D_REDRAW;
907    player->joy_on = TRUE;
908    player->click_wait = FALSE;
909    player->dialog = dialog;
910    player->obj = -1;
911    player->mouse_obj = -1;
912    player->mouse_oz = gui_mouse_z();
913    player->mouse_b = gui_mouse_b();
914 
915    /* set up the global  dialog pointer */
916    active_dialog_player = player;
917    active_dialog = dialog;
918 
919    /* set up dclick checking code */
920    if (gui_install_count <= 0) {
921       LOCK_VARIABLE(gui_timer);
922       LOCK_VARIABLE(dclick_status);
923       LOCK_VARIABLE(dclick_time);
924       LOCK_VARIABLE(gui_mouse_x);
925       LOCK_VARIABLE(gui_mouse_y);
926       LOCK_VARIABLE(gui_mouse_z);
927       LOCK_VARIABLE(gui_mouse_b);
928       LOCK_FUNCTION(default_mouse_x);
929       LOCK_FUNCTION(default_mouse_y);
930       LOCK_FUNCTION(default_mouse_z);
931       LOCK_FUNCTION(default_mouse_b);
932       LOCK_FUNCTION(dclick_check);
933 
934       install_int(dclick_check, 20);
935 
936       switch (get_display_switch_mode()) {
937          case SWITCH_AMNESIA:
938          case SWITCH_BACKAMNESIA:
939             set_display_switch_callback(SWITCH_IN, gui_switch_callback);
940       }
941 
942       /* gets menu auto-opening delay (in milliseconds) from config file */
943       gui_menu_opening_delay = get_config_int(uconvert_ascii("system", tmp1), uconvert_ascii("menu_opening_delay", tmp2), 300);
944       if (gui_menu_opening_delay >= 0) {
945          /* adjust for actual timer speed */
946          gui_menu_opening_delay /= 20;
947       }
948       else {
949          /* no auto opening */
950          gui_menu_opening_delay = -1;
951       }
952 
953       gui_install_count = 1;
954       gui_install_time = _allegro_count;
955    }
956    else
957       gui_install_count++;
958 
959    /* initialise the dialog */
960    set_clip_rect(gui_bmp, 0, 0, SCREEN_W-1, SCREEN_H-1);
961    set_clip_state(gui_bmp, TRUE);
962    player->res |= dialog_message(dialog, MSG_START, 0, &player->obj);
963 
964    player->mouse_obj = find_mouse_object(dialog);
965    if (player->mouse_obj >= 0)
966       dialog[player->mouse_obj].flags |= D_GOTMOUSE;
967 
968    for (c=0; dialog[c].proc; c++)
969       dialog[c].flags &= ~D_GOTFOCUS;
970 
971    if (focus_obj >= 0)
972       c = focus_obj;
973    else
974       c = player->mouse_obj;
975 
976    if ((c >= 0) && ((object_message(dialog+c, MSG_WANTFOCUS, 0)) & D_WANTFOCUS)) {
977       dialog[c].flags |= D_GOTFOCUS;
978       player->focus_obj = c;
979    }
980    else
981       player->focus_obj = -1;
982 
983    return player;
984 }
985 
986 
987 
988 /* gui_set_screen:
989  *  Changes the target bitmap for GUI drawing operations
990  */
gui_set_screen(BITMAP * bmp)991 void gui_set_screen(BITMAP *bmp)
992 {
993    gui_screen = bmp;
994 }
995 
996 
997 
998 /* gui_get_screen:
999  *  Returns the gui_screen, or the default surface if gui_screen is NULL
1000  */
gui_get_screen(void)1001 BITMAP *gui_get_screen(void)
1002 {
1003    return gui_screen?gui_screen:screen;
1004 }
1005 
1006 
1007 
1008 /* check_for_redraw:
1009  *  Checks whether any parts of the current dialog need to be redraw.
1010  */
check_for_redraw(DIALOG_PLAYER * player)1011 static void check_for_redraw(DIALOG_PLAYER *player)
1012 {
1013    struct al_active_dialog_player *iter;
1014    int c, r;
1015    ASSERT(player);
1016 
1017    /* need to redraw all active dialogs? */
1018    if (player->res & D_REDRAW_ALL) {
1019       for (iter = first_active_dialog_player; iter != current_active_dialog_player; iter = iter->next)
1020 	 dialog_message(iter->player->dialog, MSG_DRAW, 0, NULL);
1021 
1022       player->res &= ~D_REDRAW_ALL;
1023       player->res |= D_REDRAW;
1024    }
1025 
1026    /* need to draw it? */
1027    if (player->res & D_REDRAW) {
1028       player->res ^= D_REDRAW;
1029       player->res |= dialog_message(player->dialog, MSG_DRAW, 0, &player->obj);
1030    }
1031 
1032    /* check if any widget has to be redrawn */
1033    for (c=0; player->dialog[c].proc; c++) {
1034       if ((player->dialog[c].flags & (D_DIRTY | D_HIDDEN)) == D_DIRTY) {
1035 	 player->dialog[c].flags &= ~D_DIRTY;
1036 	 MESSAGE(c, MSG_DRAW, 0);
1037       }
1038    }
1039 }
1040 
1041 
1042 
1043 /* update_dialog:
1044  *  Updates the status of a dialog object returned by init_dialog(),
1045  *  returning TRUE if it is still active or FALSE if it has finished.
1046  */
update_dialog(DIALOG_PLAYER * player)1047 int update_dialog(DIALOG_PLAYER *player)
1048 {
1049    int c, cascii, cscan, ccombo, r, ret, nowhere, z;
1050    int new_mouse_b;
1051    ASSERT(player);
1052 
1053    /* redirect to update_menu() whenever a menu is activated */
1054    if (active_menu_player) {
1055       if (!active_menu_player_zombie) {
1056 	 if (update_menu(active_menu_player))
1057 	    return TRUE;
1058       }
1059 
1060       /* make sure all buttons are released before folding the menu */
1061       if (gui_mouse_b()) {
1062 	 active_menu_player_zombie = TRUE;
1063 	 return TRUE;
1064       }
1065       else {
1066 	 active_menu_player_zombie = FALSE;
1067 
1068 	 for (c=0; player->dialog[c].proc; c++)
1069 	    if (&player->dialog[c] == active_menu_player->dialog)
1070 	       break;
1071 	 ASSERT(player->dialog[c].proc);
1072 
1073 	 MESSAGE(c, MSG_LOSTMOUSE, 0);
1074 	 goto getout;
1075       }
1076    }
1077 
1078    if (player->res & D_CLOSE)
1079       return FALSE;
1080 
1081    /* deal with mouse buttons presses and releases */
1082    new_mouse_b = gui_mouse_b();
1083    if (new_mouse_b != player->mouse_b) {
1084       player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, FALSE);
1085 
1086       if (player->mouse_obj >= 0) {
1087 	 /* send press and release messages */
1088          if ((new_mouse_b & 1) && !(player->mouse_b & 1))
1089 	    MESSAGE(player->mouse_obj, MSG_LPRESS, new_mouse_b);
1090          if (!(new_mouse_b & 1) && (player->mouse_b & 1))
1091 	    MESSAGE(player->mouse_obj, MSG_LRELEASE, new_mouse_b);
1092 
1093          if ((new_mouse_b & 4) && !(player->mouse_b & 4))
1094 	    MESSAGE(player->mouse_obj, MSG_MPRESS, new_mouse_b);
1095          if (!(new_mouse_b & 4) && (player->mouse_b & 4))
1096 	    MESSAGE(player->mouse_obj, MSG_MRELEASE, new_mouse_b);
1097 
1098          if ((new_mouse_b & 2) && !(player->mouse_b & 2))
1099 	    MESSAGE(player->mouse_obj, MSG_RPRESS, new_mouse_b);
1100          if (!(new_mouse_b & 2) && (player->mouse_b & 2))
1101 	    MESSAGE(player->mouse_obj, MSG_RRELEASE, new_mouse_b);
1102 
1103          player->mouse_b = new_mouse_b;
1104       }
1105       else
1106 	 player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere);
1107    }
1108 
1109    /* need to reinstall the dclick and switch handlers? */
1110    if (gui_install_time != _allegro_count) {
1111       install_int(dclick_check, 20);
1112 
1113       switch (get_display_switch_mode()) {
1114          case SWITCH_AMNESIA:
1115          case SWITCH_BACKAMNESIA:
1116             set_display_switch_callback(SWITCH_IN, gui_switch_callback);
1117       }
1118 
1119       gui_install_time = _allegro_count;
1120    }
1121 
1122    /* are we dealing with a mouse click? */
1123    if (player->click_wait) {
1124       if ((ABS(player->mouse_ox-gui_mouse_x()) > 8) ||
1125 	  (ABS(player->mouse_oy-gui_mouse_y()) > 8))
1126 	 dclick_status = DCLICK_NOT;
1127 
1128       /* waiting... */
1129       if ((dclick_status != DCLICK_AGAIN) && (dclick_status != DCLICK_NOT)) {
1130 	 player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere);
1131 	 check_for_redraw(player);
1132 	 return TRUE;
1133       }
1134 
1135       player->click_wait = FALSE;
1136 
1137       /* double click! */
1138       if ((dclick_status==DCLICK_AGAIN) &&
1139 	  (gui_mouse_x() >= player->dialog[player->mouse_obj].x) &&
1140 	  (gui_mouse_y() >= player->dialog[player->mouse_obj].y) &&
1141 	  (gui_mouse_x() <= player->dialog[player->mouse_obj].x + player->dialog[player->mouse_obj].w) &&
1142 	  (gui_mouse_y() <= player->dialog[player->mouse_obj].y + player->dialog[player->mouse_obj].h)) {
1143 	 MESSAGE(player->mouse_obj, MSG_DCLICK, 0);
1144       }
1145 
1146       goto getout;
1147    }
1148 
1149    player->res &= ~D_USED_CHAR;
1150 
1151    /* need to give the input focus to someone? */
1152    if (player->res & D_WANTFOCUS) {
1153       player->res ^= D_WANTFOCUS;
1154       player->res |= offer_focus(player->dialog, player->obj, &player->focus_obj, FALSE);
1155    }
1156 
1157    /* has mouse object changed? */
1158    c = find_mouse_object(player->dialog);
1159    if (c != player->mouse_obj) {
1160       if (player->mouse_obj >= 0) {
1161 	 player->dialog[player->mouse_obj].flags &= ~D_GOTMOUSE;
1162 	 MESSAGE(player->mouse_obj, MSG_LOSTMOUSE, 0);
1163       }
1164       if (c >= 0) {
1165 	 player->dialog[c].flags |= D_GOTMOUSE;
1166 	 MESSAGE(c, MSG_GOTMOUSE, 0);
1167       }
1168       player->mouse_obj = c;
1169 
1170       /* move the input focus as well? */
1171       if ((gui_mouse_focus) && (player->mouse_obj != player->focus_obj))
1172 	 player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, TRUE);
1173    }
1174 
1175    /* deal with mouse button clicks */
1176    if (new_mouse_b) {
1177       player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, FALSE);
1178 
1179       if (player->mouse_obj >= 0) {
1180 	 dclick_time = 0;
1181 	 dclick_status = DCLICK_START;
1182 	 player->mouse_ox = gui_mouse_x();
1183 	 player->mouse_oy = gui_mouse_y();
1184 
1185 	 /* send click message */
1186 	 MESSAGE(player->mouse_obj, MSG_CLICK, new_mouse_b);
1187 
1188 	 if (player->res == D_O_K)
1189 	    player->click_wait = TRUE;
1190       }
1191       else
1192 	 player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere);
1193 
1194       /* goto getout; */  /* to avoid an updating delay */
1195    }
1196 
1197    /* deal with mouse wheel clicks */
1198    z = gui_mouse_z();
1199 
1200    if (z != player->mouse_oz) {
1201       player->res |= offer_focus(player->dialog, player->mouse_obj, &player->focus_obj, FALSE);
1202 
1203       if (player->mouse_obj >= 0) {
1204 	 MESSAGE(player->mouse_obj, MSG_WHEEL, z-player->mouse_oz);
1205       }
1206       else
1207 	 player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &nowhere);
1208 
1209       player->mouse_oz = z;
1210 
1211       /* goto getout; */  /* to avoid an updating delay */
1212    }
1213 
1214    /* fake joystick input by converting it to key presses */
1215    if (player->joy_on)
1216       rest(20);
1217 
1218    poll_joystick();
1219 
1220    if (player->joy_on) {
1221       if ((!joy[0].stick[0].axis[0].d1) && (!joy[0].stick[0].axis[0].d2) &&
1222 	  (!joy[0].stick[0].axis[1].d1) && (!joy[0].stick[0].axis[1].d2) &&
1223 	  (!joy[0].button[0].b) && (!joy[0].button[1].b)) {
1224 	 player->joy_on = FALSE;
1225 	 rest(20);
1226       }
1227       cascii = cscan = 0;
1228    }
1229    else {
1230       if (joy[0].stick[0].axis[0].d1) {
1231 	 cascii = 0;
1232 	 cscan = KEY_LEFT;
1233 	 player->joy_on = TRUE;
1234       }
1235       else if (joy[0].stick[0].axis[0].d2) {
1236 	 cascii = 0;
1237 	 cscan = KEY_RIGHT;
1238 	 player->joy_on = TRUE;
1239       }
1240       else if (joy[0].stick[0].axis[1].d1) {
1241 	 cascii = 0;
1242 	 cscan = KEY_UP;
1243 	 player->joy_on = TRUE;
1244       }
1245       else if (joy[0].stick[0].axis[1].d2) {
1246 	 cascii = 0;
1247 	 cscan = KEY_DOWN;
1248 	 player->joy_on = TRUE;
1249       }
1250       else if ((joy[0].button[0].b) || (joy[0].button[1].b)) {
1251 	 cascii = ' ';
1252 	 cscan = KEY_SPACE;
1253 	 player->joy_on = TRUE;
1254       }
1255       else
1256 	 cascii = cscan = 0;
1257    }
1258 
1259    /* deal with keyboard input */
1260    if ((cascii) || (cscan) || (keypressed())) {
1261       if ((!cascii) && (!cscan))
1262 	 cascii = ureadkey(&cscan);
1263 
1264       ccombo = (cscan<<8) | ((cascii <= 255) ? cascii : '^');
1265 
1266       /* let object deal with the key */
1267       if (player->focus_obj >= 0) {
1268 	 MESSAGE(player->focus_obj, MSG_CHAR, ccombo);
1269 	 if (player->res & (D_USED_CHAR | D_CLOSE))
1270 	    goto getout;
1271 
1272 	 MESSAGE(player->focus_obj, MSG_UCHAR, cascii);
1273 	 if (player->res & (D_USED_CHAR | D_CLOSE))
1274 	    goto getout;
1275       }
1276 
1277       /* keyboard shortcut? */
1278       for (c=0; player->dialog[c].proc; c++) {
1279 	 if ((((cascii > 0) && (cascii <= 255) &&
1280 	       (utolower(player->dialog[c].key) == utolower((cascii)))) ||
1281 	      ((!cascii) && (player->dialog[c].key == (cscan<<8)))) &&
1282 	     (!(player->dialog[c].flags & (D_HIDDEN | D_DISABLED)))) {
1283 	    MESSAGE(c, MSG_KEY, ccombo);
1284 	    goto getout;
1285 	 }
1286       }
1287 
1288       /* broadcast in case any other objects want it */
1289       for (c=0; player->dialog[c].proc; c++) {
1290 	 if (!(player->dialog[c].flags & (D_HIDDEN | D_DISABLED))) {
1291 	    MESSAGE(c, MSG_XCHAR, ccombo);
1292 	    if (player->res & D_USED_CHAR)
1293 	       goto getout;
1294 	 }
1295       }
1296 
1297       /* pass <CR> or <SPACE> to selected object */
1298       if (((cascii == '\r') || (cascii == '\n') || (cascii == ' ')) &&
1299 	  (player->focus_obj >= 0)) {
1300 	 MESSAGE(player->focus_obj, MSG_KEY, ccombo);
1301 	 goto getout;
1302       }
1303 
1304       /* ESC closes dialog */
1305       if (cascii == 27) {
1306 	 player->res |= D_CLOSE;
1307 	 player->obj = -1;
1308 	 goto getout;
1309       }
1310 
1311       /* move focus around the dialog */
1312       player->res |= move_focus(player->dialog, cascii, cscan, &player->focus_obj);
1313    }
1314 
1315    /* redraw? */
1316    check_for_redraw(player);
1317 
1318    /* send idle messages */
1319    player->res |= dialog_message(player->dialog, MSG_IDLE, 0, &player->obj);
1320 
1321    getout:
1322 
1323    ret = (!(player->res & D_CLOSE));
1324    player->res &= ~D_CLOSE;
1325    return ret;
1326 }
1327 
1328 
1329 
1330 /* shutdown_dialog:
1331  *  Destroys a dialog object returned by init_dialog(), returning the index
1332  *  of the object that caused it to exit.
1333  */
shutdown_dialog(DIALOG_PLAYER * player)1334 int shutdown_dialog(DIALOG_PLAYER *player)
1335 {
1336    struct al_active_dialog_player *iter, *prev;
1337    int obj;
1338    ASSERT(player);
1339 
1340    /* send the finish messages */
1341    dialog_message(player->dialog, MSG_END, 0, &player->obj);
1342 
1343    /* remove the double click handler */
1344    gui_install_count--;
1345 
1346    if (gui_install_count <= 0) {
1347       remove_int(dclick_check);
1348       remove_display_switch_callback(gui_switch_callback);
1349    }
1350 
1351    if (player->mouse_obj >= 0)
1352       player->dialog[player->mouse_obj].flags &= ~D_GOTMOUSE;
1353 
1354    /* remove dialog player from the list of active players */
1355    for (iter = first_active_dialog_player, prev = 0; iter != 0; prev = iter, iter = iter->next) {
1356       if (iter->player == player) {
1357 	 if (prev)
1358 	    prev->next = iter->next;
1359 	 else
1360 	    first_active_dialog_player = iter->next;
1361 
1362 	 if (iter == current_active_dialog_player)
1363 	    current_active_dialog_player = prev;
1364 
1365 	 _AL_FREE (iter);
1366 	 break;
1367       }
1368    }
1369 
1370    if (current_active_dialog_player)
1371       active_dialog_player = current_active_dialog_player->player;
1372    else
1373       active_dialog_player = NULL;
1374 
1375    if (active_dialog_player)
1376       active_dialog = active_dialog_player->dialog;
1377    else
1378       active_dialog = NULL;
1379 
1380    obj = player->obj;
1381 
1382    _AL_FREE(player);
1383 
1384    return obj;
1385 }
1386 
1387 
1388 
1389 void (*gui_menu_draw_menu)(int x, int y, int w, int h) = NULL;
1390 void (*gui_menu_draw_menu_item)(MENU *m, int x, int y, int w, int h, int bar, int sel) = NULL;
1391 
1392 
1393 
1394 /* split_around_tab:
1395  *  Helper function for splitting a string into two tokens
1396  *  delimited by the first TAB character.
1397  */
split_around_tab(const char * s,char ** tok1,char ** tok2)1398 static char* split_around_tab(const char *s, char **tok1, char **tok2)
1399 {
1400    char *buf, *last;
1401    char tmp[16];
1402 
1403    buf = _al_ustrdup(s);
1404    *tok1 = ustrtok_r(buf, uconvert_ascii("\t", tmp), &last);
1405    *tok2 = ustrtok_r(NULL, empty_string, &last);
1406 
1407    return buf;
1408 }
1409 
1410 
1411 
1412 /* bar_entry_lengh:
1413  *  Helper function for calculating the length of a menu bar entry.
1414  */
bar_entry_length(const char * text)1415 static int bar_entry_length(const char *text)
1416 {
1417    char *buf, *tok1, *tok2;
1418    int len;
1419 
1420    buf = split_around_tab(text, &tok1, &tok2);
1421    len = gui_strlen(tok1) + 16;
1422    if (tok2)
1423       len += gui_strlen(tok2) + 16;
1424    _AL_FREE(buf);
1425 
1426    return len;
1427 }
1428 
1429 
1430 
1431 /* get_menu_pos:
1432  *  Calculates the coordinates of an object within a top level menu bar.
1433  */
get_menu_pos(MENU_PLAYER * m,int c,int * x,int * y,int * w)1434 static void get_menu_pos(MENU_PLAYER *m, int c, int *x, int *y, int *w)
1435 {
1436    int c2;
1437 
1438    if (m->bar) {
1439       *x = m->x+1;
1440 
1441       for (c2=0; c2<c; c2++)
1442 	 *x += bar_entry_length(m->menu[c2].text);
1443 
1444       *y = m->y+1;
1445       *w = bar_entry_length(m->menu[c].text);
1446    }
1447    else {
1448       *x = m->x+1;
1449       *y = m->y+c*(text_height(font)+4)+1;
1450       *w = m->w-3;
1451    }
1452 }
1453 
1454 
1455 
1456 /* draw_menu_item:
1457  *  Draws an item from a popup menu onto the screen.
1458  */
draw_menu_item(MENU_PLAYER * m,int c)1459 static void draw_menu_item(MENU_PLAYER *m, int c)
1460 {
1461    int fg, bg;
1462    int x, y, w;
1463    char *buf, *tok1, *tok2;
1464    int my;
1465    BITMAP *gui_bmp = gui_get_screen();
1466 
1467    get_menu_pos(m, c, &x, &y, &w);
1468 
1469    if (gui_menu_draw_menu_item) {
1470       gui_menu_draw_menu_item(&m->menu[c], x, y, w, text_height(font)+4,
1471 			      m->bar, (c == m->sel) ? TRUE : FALSE);
1472       return;
1473    }
1474 
1475    if (m->menu[c].flags & D_DISABLED) {
1476       if (c == m->sel) {
1477 	 fg = gui_mg_color;
1478 	 bg = gui_fg_color;
1479       }
1480       else {
1481 	 fg = gui_mg_color;
1482 	 bg = gui_bg_color;
1483       }
1484    }
1485    else {
1486       if (c == m->sel) {
1487 	 fg = gui_bg_color;
1488 	 bg = gui_fg_color;
1489       }
1490       else {
1491 	 fg = gui_fg_color;
1492 	 bg = gui_bg_color;
1493       }
1494    }
1495 
1496    rectfill(gui_bmp, x, y, x+w-1, y+text_height(font)+3, bg);
1497 
1498    if (ugetc(m->menu[c].text)) {
1499       buf = split_around_tab(m->menu[c].text, &tok1, &tok2);
1500       gui_textout_ex(gui_bmp, tok1, x+8, y+1, fg, bg, FALSE);
1501       if (tok2)
1502  	 gui_textout_ex(gui_bmp, tok2, x+w-gui_strlen(tok2)-10, y+1, fg, bg, FALSE);
1503 
1504       if ((m->menu[c].child) && (!m->bar)) {
1505          my = y + text_height(font)/2;
1506          hline(gui_bmp, x+w-8, my+1, x+w-4, fg);
1507          hline(gui_bmp, x+w-8, my+0, x+w-5, fg);
1508          hline(gui_bmp, x+w-8, my-1, x+w-6, fg);
1509          hline(gui_bmp, x+w-8, my-2, x+w-7, fg);
1510          putpixel(gui_bmp, x+w-8, my-3, fg);
1511          hline(gui_bmp, x+w-8, my+2, x+w-5, fg);
1512          hline(gui_bmp, x+w-8, my+3, x+w-6, fg);
1513          hline(gui_bmp, x+w-8, my+4, x+w-7, fg);
1514          putpixel(gui_bmp, x+w-8, my+5, fg);
1515       }
1516 
1517       _AL_FREE(buf);
1518    }
1519    else
1520       hline(gui_bmp, x, y+text_height(font)/2+2, x+w, fg);
1521 
1522    if (m->menu[c].flags & D_SELECTED) {
1523       line(gui_bmp, x+1, y+text_height(font)/2+1, x+3, y+text_height(font)+1, fg);
1524       line(gui_bmp, x+3, y+text_height(font)+1, x+6, y+2, fg);
1525    }
1526 }
1527 
1528 
1529 
1530 /* draw_menu:
1531  *  Draws a popup menu onto the screen.
1532  */
draw_menu(MENU_PLAYER * m)1533 static void draw_menu(MENU_PLAYER *m)
1534 {
1535    int c;
1536 
1537    if (gui_menu_draw_menu)
1538       gui_menu_draw_menu(m->x, m->y, m->w, m->h);
1539    else {
1540       BITMAP *gui_bmp = gui_get_screen();
1541       rectfill(gui_bmp, m->x, m->y, m->x+m->w-2, m->y+m->h-2, gui_bg_color);
1542       rect(gui_bmp, m->x, m->y, m->x+m->w-2, m->y+m->h-2, gui_fg_color);
1543       vline(gui_bmp, m->x+m->w-1, m->y+1, m->y+m->h-1, gui_fg_color);
1544       hline(gui_bmp, m->x+1, m->y+m->h-1, m->x+m->w-1, gui_fg_color);
1545    }
1546 
1547    for (c=0; m->menu[c].text; c++)
1548       draw_menu_item(m, c);
1549 }
1550 
1551 
1552 
1553 /* menu_mouse_object:
1554  *  Returns the index of the object the mouse is currently on top of.
1555  */
menu_mouse_object(MENU_PLAYER * m)1556 static int menu_mouse_object(MENU_PLAYER *m)
1557 {
1558    int c;
1559    int x, y, w;
1560 
1561    for (c=0; c<m->size; c++) {
1562       get_menu_pos(m, c, &x, &y, &w);
1563 
1564       if ((gui_mouse_x() >= x) && (gui_mouse_x() < x+w) &&
1565 	  (gui_mouse_y() >= y) && (gui_mouse_y() < y+(text_height(font)+4)))
1566 	 return (ugetc(m->menu[c].text)) ? c : -1;
1567    }
1568 
1569    return -1;
1570 }
1571 
1572 
1573 
1574 /* mouse_in_single_menu:
1575  *  Checks if the mouse is inside a single menu.
1576  */
mouse_in_single_menu(MENU_PLAYER * m)1577 static INLINE int mouse_in_single_menu(MENU_PLAYER *m)
1578 {
1579    if ((gui_mouse_x() >= m->x) && (gui_mouse_x() < m->x+m->w) &&
1580        (gui_mouse_y() >= m->y) && (gui_mouse_y() < m->y+m->h))
1581       return TRUE;
1582    else
1583       return FALSE;
1584 }
1585 
1586 
1587 
1588 /* mouse_in_parent_menu:
1589  *  Recursively checks if the mouse is inside a menu (or any of its parents)
1590  *  and simultaneously not on the selected item of the menu.
1591  */
mouse_in_parent_menu(MENU_PLAYER * m)1592 static int mouse_in_parent_menu(MENU_PLAYER *m)
1593 {
1594    int c;
1595 
1596    if (!m)
1597       return FALSE;
1598 
1599    c = menu_mouse_object(m);
1600    if ((c >= 0) && (c != m->sel))
1601       return TRUE;
1602 
1603    return mouse_in_parent_menu(m->parent);
1604 }
1605 
1606 
1607 
1608 /* layout_menu:
1609  *  Calculates the layout of the menu.
1610  */
layout_menu(MENU_PLAYER * m,MENU * menu,int bar,int x,int y,int minw,int minh)1611 static void layout_menu(MENU_PLAYER *m, MENU *menu, int bar, int x, int y, int minw, int minh)
1612 {
1613    char *buf, *tok1, *tok2;
1614    int extra = 0;
1615    int c;
1616    int child = FALSE;
1617 
1618    m->menu = menu;
1619    m->bar = bar;
1620    m->x = x;
1621    m->y = y;
1622    m->w = 3;
1623    m->h = (m->bar) ? (text_height(font)+7) : 3;
1624    m->proc = NULL;
1625    m->sel = -1;
1626 
1627    /* calculate size of the menu */
1628    for (m->size=0; m->menu[m->size].text; m->size++) {
1629 
1630       if (m->bar) {
1631 	 m->w += bar_entry_length(m->menu[m->size].text);
1632       }
1633       else {
1634 	 if (m->menu[m->size].child)
1635 	    child = TRUE;
1636 
1637 	 if (ugetc(m->menu[m->size].text)) {
1638 	    buf = split_around_tab(m->menu[m->size].text, &tok1, &tok2);
1639 	    c = gui_strlen(tok1);
1640 	 }
1641 	 else {
1642 	    buf = NULL;
1643 	    c = 0;
1644 	 }
1645 
1646 	 m->h += text_height(font)+4;
1647 	 m->w = MAX(m->w, c+16);
1648 
1649 	 if (buf) {
1650 	    if (tok2) {
1651 	       c = gui_strlen(tok2);
1652 	       extra = MAX(extra, c);
1653 	    }
1654 
1655 	    _AL_FREE(buf);
1656 	 }
1657       }
1658    }
1659 
1660    if (extra)
1661       m->w += extra+16;
1662 
1663    if (child)
1664       m->w += 22;
1665 
1666    m->w = MAX(m->w, minw);
1667    m->h = MAX(m->h, minh);
1668 }
1669 
1670 
1671 
1672 /* menu_key_shortcut:
1673  *  Returns true if c is indicated as a keyboard shortcut by a '&' character
1674  *  in the specified string.
1675  */
menu_key_shortcut(int c,AL_CONST char * s)1676 static int menu_key_shortcut(int c, AL_CONST char *s)
1677 {
1678    int d;
1679 
1680    while ((d = ugetxc(&s)) != 0) {
1681       if (d == '&') {
1682 	 d = ugetc(s);
1683 	 if ((d != '&') && (utolower(d) == utolower(c & 0xFF)))
1684 	    return TRUE;
1685       }
1686    }
1687 
1688    return FALSE;
1689 }
1690 
1691 
1692 
1693 /* menu_alt_key:
1694  *  Searches a menu for keyboard shortcuts, for the alt+letter to bring
1695  *  up a menu.
1696  */
menu_alt_key(int k,MENU * m)1697 static int menu_alt_key(int k, MENU *m)
1698 {
1699    static unsigned char alt_table[] =
1700    {
1701       KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I,
1702       KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R,
1703       KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z
1704    };
1705 
1706    AL_CONST char *s;
1707    int c, d;
1708 
1709    if (k & 0xFF)
1710       return 0;
1711 
1712    k >>= 8;
1713 
1714    c = scancode_to_ascii(k);
1715    if (c) {
1716       k = c;
1717    }
1718    else {
1719       for (c=0; c<(int)sizeof(alt_table); c++) {
1720 	 if (k == alt_table[c]) {
1721 	    k = c + 'a';
1722 	    break;
1723 	 }
1724       }
1725 
1726       if (c >= (int)sizeof(alt_table))
1727 	 return 0;
1728    }
1729 
1730    for (c=0; m[c].text; c++) {
1731       s = m[c].text;
1732       while ((d = ugetxc(&s)) != 0) {
1733 	 if (d == '&') {
1734 	    d = ugetc(s);
1735 	    if ((d != '&') && (utolower(d) == utolower(k)))
1736 	       return k;
1737 	 }
1738       }
1739    }
1740 
1741    return 0;
1742 }
1743 
1744 
1745 
1746 /* do_menu:
1747  *  Displays and animates a popup menu at the specified screen position,
1748  *  returning the index of the item that was selected, or -1 if it was
1749  *  dismissed. If the menu crosses the edge of the screen it will be moved.
1750  */
do_menu(MENU * menu,int x,int y)1751 int do_menu(MENU *menu, int x, int y)
1752 {
1753    MENU_PLAYER *player;
1754    int ret;
1755    ASSERT(menu);
1756 
1757    player = init_menu(menu, x ,y);
1758 
1759    while (update_menu(player))
1760       rest(1);
1761 
1762    ret = shutdown_menu(player);
1763 
1764    do {
1765    } while (gui_mouse_b());
1766 
1767    return ret;
1768 }
1769 
1770 
1771 
1772 /* init_single_menu:
1773  *  Worker function for initialising a menu.
1774  */
init_single_menu(MENU * menu,MENU_PLAYER * parent,DIALOG * dialog,int bar,int x,int y,int repos,int minw,int minh)1775 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)
1776 {
1777    BITMAP *gui_bmp = gui_get_screen();
1778    int scare = is_same_bitmap(gui_bmp, _mouse_screen);
1779    MENU_PLAYER *player;
1780    ASSERT(menu);
1781 
1782    player = _AL_MALLOC(sizeof(MENU_PLAYER));
1783    if (!player) {
1784       *allegro_errno = ENOMEM;
1785       return NULL;
1786    }
1787 
1788    layout_menu(player, menu, bar, x, y, minw, minh);
1789 
1790    if (repos) {
1791       if(parent && !parent->bar) {
1792          if(player->x + player->w >= SCREEN_W)
1793             player->x = parent->x - player->w + 1;
1794       }
1795       player->x = CLAMP(0, player->x, SCREEN_W-player->w-1);
1796       player->y = CLAMP(0, player->y, SCREEN_H-player->h-1);
1797    }
1798 
1799    if (scare)
1800       scare_mouse_area(player->x, player->y, player->w, player->h);
1801 
1802    /* save screen under the menu */
1803    player->saved = create_bitmap(player->w, player->h);
1804 
1805    if (player->saved)
1806       blit(gui_bmp, player->saved, player->x, player->y, 0, 0, player->w, player->h);
1807    else
1808       *allegro_errno = ENOMEM;
1809 
1810    /* setup state variables */
1811    player->sel = menu_mouse_object(player);
1812 
1813    if (scare)
1814       unscare_mouse();
1815 
1816    player->mouse_button_was_pressed = gui_mouse_b();
1817    player->back_from_child = FALSE;
1818    player->timestamp = gui_timer;
1819    player->mouse_sel = player->sel;
1820    player->redraw = TRUE;
1821    player->auto_open = TRUE;
1822    player->ret = -1;
1823 
1824    player->dialog = dialog;
1825 
1826    player->parent = parent;
1827    player->child = NULL;
1828 
1829    return player;
1830 }
1831 
1832 
1833 
1834 /* init_menu:
1835  *  Sets up a menu, returning a menu player object that can be used
1836  *  with the update_menu() and shutdown_menu() functions.
1837  */
init_menu(MENU * menu,int x,int y)1838 MENU_PLAYER *init_menu(MENU *menu, int x, int y)
1839 {
1840    return init_single_menu(menu, NULL, NULL, FALSE, x, y, TRUE, 0, 0);
1841 }
1842 
1843 
1844 
1845 /* update_menu:
1846  *  Updates the status of a menu player object returned by init_menu(),
1847  *  returning TRUE if it is still active or FALSE if it has finished.
1848  *
1849  *  The navigation through the arborescence of menus can be done:
1850  *   - with the arrow keys,
1851  *   - with mouse point-and-clicks,
1852  *   - with mouse movements when the mouse button is being held down,
1853  *   - with mouse movements only if gui_menu_opening_delay is non negative.
1854  */
update_menu(MENU_PLAYER * player)1855 int update_menu(MENU_PLAYER *player)
1856 {
1857    MENU_PLAYER *i;
1858    int c, c2;
1859    int old_sel, child_ret;
1860    int child_x, child_y;
1861    ASSERT(player);
1862 
1863    /* find activated menu */
1864    while (player->child)
1865       player = player->child;
1866 
1867    old_sel = player->sel;
1868 
1869    c = menu_mouse_object(player);
1870 
1871    if ((gui_mouse_b()) || (c != player->mouse_sel)) {
1872       player->sel = player->mouse_sel = c;
1873       player->auto_open = TRUE;
1874    }
1875 
1876    if (gui_mouse_b()) {  /* button pressed? */
1877       /* Dismiss menu if:
1878        *  - the mouse cursor is outside the menu and inside the parent menu, or
1879        *  - the mouse cursor is outside the menu and the button has just been pressed.
1880        */
1881       if (!mouse_in_single_menu(player)) {
1882 	 if (mouse_in_parent_menu(player->parent) || (!player->mouse_button_was_pressed)) {
1883 	    player->ret = -2;
1884 	    goto End;
1885 	 }
1886       }
1887 
1888       if ((player->sel >= 0) && (player->menu[player->sel].child))  /* bring up child menu? */
1889 	 player->ret = player->sel;
1890 
1891       /* don't trigger the 'select' event on button press for non menu item */
1892       player->mouse_button_was_pressed = TRUE;
1893 
1894       clear_keybuf();
1895    }
1896    else {   /* button not pressed */
1897       /* trigger the 'select' event only on button release for non menu item */
1898       if (player->mouse_button_was_pressed) {
1899 	 player->ret = player->sel;
1900 	 player->mouse_button_was_pressed = FALSE;
1901       }
1902 
1903       if (keypressed()) {  /* keyboard input */
1904 	 player->timestamp = gui_timer;
1905 	 player->auto_open = FALSE;
1906 
1907 	 c = readkey();
1908 
1909 	 if ((c & 0xFF) == 27) {
1910 	    player->ret = -2;
1911 	    goto End;
1912 	 }
1913 
1914 	 switch (c >> 8) {
1915 
1916 	    case KEY_LEFT:
1917 	       if (player->parent) {
1918 		  if (player->parent->bar) {
1919 		     simulate_keypress(KEY_LEFT<<8);
1920 		     simulate_keypress(KEY_DOWN<<8);
1921 		  }
1922 		  player->ret = -2;
1923 		  goto End;
1924 	       }
1925 	       /* fall through */
1926 
1927 	    case KEY_UP:
1928 	       if ((((c >> 8) == KEY_LEFT) && (player->bar)) ||
1929 		   (((c >> 8) == KEY_UP) && (!player->bar))) {
1930 		  c = player->sel;
1931 		  do {
1932 		     c--;
1933 		     if (c < 0)
1934 			c = player->size - 1;
1935 		  } while ((!ugetc(player->menu[c].text)) && (c != player->sel));
1936 		  player->sel = c;
1937 	       }
1938 	       break;
1939 
1940 	    case KEY_RIGHT:
1941 	       if (((player->sel < 0) || (!player->menu[player->sel].child)) &&
1942 		   (player->parent) && (player->parent->bar)) {
1943 		  simulate_keypress(KEY_RIGHT<<8);
1944 		  simulate_keypress(KEY_DOWN<<8);
1945 		  player->ret = -2;
1946 		  goto End;
1947 	       }
1948 	       /* fall through */
1949 
1950 	    case KEY_DOWN:
1951 	       if ((player->sel >= 0) && (player->menu[player->sel].child) &&
1952 		   ((((c >> 8) == KEY_RIGHT) && (!player->bar)) ||
1953 		    (((c >> 8) == KEY_DOWN) && (player->bar)))) {
1954 		  player->ret = player->sel;
1955 	       }
1956 	       else if ((((c >> 8) == KEY_RIGHT) && (player->bar)) ||
1957 			(((c >> 8) == KEY_DOWN) && (!player->bar))) {
1958 		  c = player->sel;
1959 		  do {
1960 		     c++;
1961 		     if (c >= player->size)
1962 			c = 0;
1963 		  } while ((!ugetc(player->menu[c].text)) && (c != player->sel));
1964 		  player->sel = c;
1965 	       }
1966 	       break;
1967 
1968 	    case KEY_SPACE:
1969 	    case KEY_ENTER:
1970 	       if (player->sel >= 0)
1971 		  player->ret = player->sel;
1972 	       break;
1973 
1974 	    default:
1975 	       if ((!player->parent) && ((c & 0xFF) == 0))
1976 		  c = menu_alt_key(c, player->menu);
1977 	       for (c2=0; player->menu[c2].text; c2++) {
1978 		  if (menu_key_shortcut(c, player->menu[c2].text)) {
1979 		     player->ret = player->sel = c2;
1980 		     break;
1981 		  }
1982 	       }
1983 	       if (player->parent) {
1984 		  i = player->parent;
1985 		  for (c2=0; i->parent; c2++)
1986 		     i = i->parent;
1987 		  c = menu_alt_key(c, i->menu);
1988 		  if (c) {
1989 		     while (c2-- > 0)
1990 			simulate_keypress(27);
1991 		     simulate_keypress(c);
1992 		     player->ret = -2;
1993 		     goto End;
1994 		  }
1995 	       }
1996 	       break;
1997 	 }
1998       }
1999    }  /* end of input processing */
2000 
2001    if ((player->redraw) || (player->sel != old_sel)) {  /* selection changed? */
2002       BITMAP *gui_bmp = gui_get_screen();
2003       int scare = is_same_bitmap(gui_bmp, _mouse_screen);
2004       player->timestamp = gui_timer;
2005 
2006       if (scare)
2007 	 scare_mouse_area(player->x, player->y, player->w, player->h);
2008       acquire_bitmap(gui_bmp);
2009 
2010       if (player->redraw) {
2011 	 draw_menu(player);
2012 	 player->redraw = FALSE;
2013       }
2014       else {
2015 	 if (old_sel >= 0)
2016 	    draw_menu_item(player, old_sel);
2017 
2018 	 if (player->sel >= 0)
2019 	    draw_menu_item(player, player->sel);
2020       }
2021 
2022       release_bitmap(gui_bmp);
2023       if (scare)
2024 	 unscare_mouse();
2025    }
2026 
2027    if (player->auto_open && (gui_menu_opening_delay >= 0)) {  /* menu auto-opening on? */
2028       if (!mouse_in_single_menu(player)) {
2029 	 if (mouse_in_parent_menu(player->parent)) {
2030 	    /* automatically goes back to parent */
2031 	    player->ret = -3;
2032 	    goto End;
2033 	 }
2034       }
2035 
2036       if ((player->mouse_sel >= 0) && (player->menu[player->mouse_sel].child)) {
2037 	 if (player->bar) {
2038 	    /* top level menu auto-opening if back from child */
2039 	    if (player->back_from_child) {
2040 	       player->timestamp = gui_timer;
2041 	       player->ret = player->mouse_sel;
2042 	    }
2043 	 }
2044 	 else {
2045 	    /* sub menu auto-opening if enough time has passed */
2046 	    if ((gui_timer - player->timestamp) > gui_menu_opening_delay)
2047 	       player->ret = player->mouse_sel;
2048 	 }
2049       }
2050 
2051       player->back_from_child = FALSE;
2052    }
2053 
2054  End:
2055    if (player->ret >= 0) {  /* item selected? */
2056       if (player->menu[player->ret].flags & D_DISABLED) {
2057 	 return TRUE;  /* continue */
2058       }
2059       else if (player->menu[player->ret].child) {  /* child menu? */
2060 	 if (player->bar) {
2061 	    get_menu_pos(player, player->ret, &child_x, &child_y, &c);
2062 	    child_x += 6;
2063 	    child_y += text_height(font) + 7;
2064 	 }
2065 	 else {
2066 	    child_x = player->x+player->w - 3;
2067 	    child_y = player->y + (text_height(font)+4)*player->ret + text_height(font)/4 + 1;
2068 	 }
2069 
2070 	 /* recursively call child menu */
2071 	 player->child = init_single_menu(player->menu[player->ret].child, player, NULL, FALSE, child_x, child_y, TRUE, 0, 0);
2072 	 return TRUE;  /* continue */
2073       }
2074 
2075       while (player->parent) {  /* parent menu? */
2076 	 player = player->parent;
2077 	 shutdown_single_menu(player->child, NULL);
2078 	 player->child = NULL;
2079       }
2080 
2081       return FALSE;  /* item selected */
2082    }
2083 
2084    if (player->ret < -1) {  /* dismiss menu ? */
2085       if (player->parent) {
2086 	 child_ret = player->ret;  /* needed below */
2087 	 player = player->parent;
2088 	 shutdown_single_menu(player->child, NULL);
2089 	 player->child = NULL;
2090 	 player->ret = -1;
2091 	 player->mouse_button_was_pressed = FALSE;
2092 	 player->mouse_sel = menu_mouse_object(player);
2093 
2094 	 if (child_ret == -3) {  /* return caused by mouse movement? */
2095 	    player->sel = player->mouse_sel;
2096 	    player->redraw = TRUE;
2097 	    player->timestamp = gui_timer;
2098 	    player->back_from_child = TRUE;
2099 	 }
2100 
2101 	 return TRUE;  /* return to parent */
2102       }
2103 
2104       return FALSE;  /* menu dismissed */
2105    }
2106 
2107    /* special kludge for menu bar */
2108    if ((player->bar) && (!gui_mouse_b()) && (!keypressed()) && (!mouse_in_single_menu(player)))
2109       return FALSE;
2110 
2111    return TRUE;
2112 }
2113 
2114 
2115 
2116 /* shutdown_single_menu:
2117  *  Worker function for shutting down a menu.
2118  */
shutdown_single_menu(MENU_PLAYER * player,int * dret)2119 static int shutdown_single_menu(MENU_PLAYER *player, int *dret)
2120 {
2121    int ret;
2122    ASSERT(player);
2123 
2124    if (dret)
2125       *dret = 0;
2126 
2127    if ((!player->proc) && (player->ret >= 0)) {   /* callback function? */
2128       active_menu = &player->menu[player->ret];
2129       player->proc = active_menu->proc;
2130    }
2131 
2132    if (player->ret >= 0) {
2133       if (player->parent)
2134 	 player->parent->proc = player->proc;
2135       else  {
2136 	 if (player->proc) {
2137 	    ret = player->proc();
2138 	    if (dret)
2139 	       *dret = ret;
2140 	 }
2141       }
2142    }
2143 
2144    /* restore screen */
2145    if (player->saved) {
2146       BITMAP *gui_bmp = gui_get_screen();
2147       int scare = is_same_bitmap(gui_bmp, _mouse_screen);
2148       if (scare)
2149          scare_mouse_area(player->x, player->y, player->w, player->h);
2150       blit(player->saved, gui_bmp, 0, 0, player->x, player->y, player->w, player->h);
2151       if (scare)
2152          unscare_mouse();
2153       destroy_bitmap(player->saved);
2154    }
2155 
2156    ret = player->ret;
2157 
2158    _AL_FREE(player);
2159 
2160    return ret;
2161 }
2162 
2163 
2164 
2165 /* shutdown_tree_menu:
2166  *  Destroys a menu player object returned by init_single_menu(), after
2167  *  recursively closing all the sub-menus if necessary, and returns the
2168  *  index of the item that was selected, or -1 if it was dismissed.
2169  */
shutdown_tree_menu(MENU_PLAYER * player,int * dret)2170 static int shutdown_tree_menu(MENU_PLAYER *player, int *dret)
2171 {
2172    ASSERT(player);
2173 
2174    if (player->child) {
2175       shutdown_tree_menu(player->child, dret);
2176       player->child = NULL;
2177    }
2178 
2179    return shutdown_single_menu(player, dret);
2180 }
2181 
2182 
2183 
2184 /* shutdown_menu:
2185  *  Destroys a menu player object returned by init_menu() and returns
2186  *  the index of the item that was selected, or -1 if it was dismissed.
2187  */
shutdown_menu(MENU_PLAYER * player)2188 int shutdown_menu(MENU_PLAYER *player)
2189 {
2190    return shutdown_tree_menu(player, NULL);
2191 }
2192 
2193 
2194 
2195 /* d_menu_proc:
2196  *  Dialog procedure for adding drop down menus to a GUI dialog. This
2197  *  displays the top level menu items as a horizontal bar (eg. across the
2198  *  top of the screen), and pops up child menus when they are clicked.
2199  *  When it executes one of the menu callback routines, it passes the
2200  *  return value back to the dialog manager, so these can return D_O_K,
2201  *  D_CLOSE, D_REDRAW, etc.
2202  */
d_menu_proc(int msg,DIALOG * d,int c)2203 int d_menu_proc(int msg, DIALOG *d, int c)
2204 {
2205    MENU_PLAYER m, *mp;
2206    int ret = D_O_K;
2207    int x, i;
2208    ASSERT(d);
2209 
2210    switch (msg) {
2211 
2212       case MSG_START:
2213 	 layout_menu(&m, d->dp, TRUE, d->x, d->y, d->w, d->h);
2214 	 d->w = m.w;
2215 	 d->h = m.h;
2216 	 break;
2217 
2218       case MSG_DRAW:
2219 	 layout_menu(&m, d->dp, TRUE, d->x, d->y, d->w, d->h);
2220 	 draw_menu(&m);
2221 	 break;
2222 
2223       case MSG_XCHAR:
2224 	 x = menu_alt_key(c, d->dp);
2225 	 if (!x)
2226 	    break;
2227 
2228 	 ret |= D_USED_CHAR;
2229 	 simulate_keypress(x);
2230 	 /* fall through */
2231 
2232       case MSG_GOTMOUSE:
2233       case MSG_CLICK:
2234 	 /* steal the mouse */
2235 	 for (i=0; active_dialog[i].proc; i++)
2236 	    if (active_dialog[i].flags & D_GOTMOUSE) {
2237 	       active_dialog[i].flags &= ~D_GOTMOUSE;
2238 	       object_message(active_dialog+i, MSG_LOSTMOUSE, 0);
2239 	       break;
2240 	    }
2241 
2242 	 /* initialize the menu */
2243 	 active_menu_player = init_single_menu(d->dp, NULL, d, TRUE, d->x, d->y, FALSE, d->w, d->h);
2244 	 break;
2245 
2246       case MSG_LOSTMOUSE:
2247       case MSG_END:
2248 	 if (active_menu_player) {
2249 	    /* shutdown_tree_menu may call nested dialogs */
2250 	    mp = active_menu_player;
2251 	    active_menu_player = NULL;
2252 	    shutdown_tree_menu(mp, &x);
2253 	    ret |= x;
2254 
2255 	    /* put the mouse */
2256 	    i = find_mouse_object(active_dialog);
2257 	    if ((i >= 0) && (&active_dialog[i] != d)) {
2258 	       active_dialog[i].flags |= D_GOTMOUSE;
2259 	       object_message(active_dialog+i, MSG_GOTMOUSE, 0);
2260 	    }
2261 	 }
2262 	 break;
2263    }
2264 
2265    return ret;
2266 }
2267 
2268 
2269 
2270 static DIALOG alert_dialog[] =
2271 {
2272    /* (dialog proc)        (x)   (y)   (w)   (h)   (fg)  (bg)  (key) (flags)  (d1)  (d2)  (dp)  (dp2) (dp3) */
2273    { _gui_shadow_box_proc, 0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL, NULL, NULL  },
2274    { _gui_ctext_proc,      0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL, NULL, NULL  },
2275    { _gui_ctext_proc,      0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL, NULL, NULL  },
2276    { _gui_ctext_proc,      0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL, NULL, NULL  },
2277    { _gui_button_proc,     0,    0,    0,    0,    0,    0,    0,    D_EXIT,  0,    0,    NULL, NULL, NULL  },
2278    { _gui_button_proc,     0,    0,    0,    0,    0,    0,    0,    D_EXIT,  0,    0,    NULL, NULL, NULL  },
2279    { _gui_button_proc,     0,    0,    0,    0,    0,    0,    0,    D_EXIT,  0,    0,    NULL, NULL, NULL  },
2280    { d_yield_proc,         0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL, NULL, NULL  },
2281    { NULL,                 0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL, NULL, NULL  }
2282 };
2283 
2284 
2285 #define A_S1  1
2286 #define A_S2  2
2287 #define A_S3  3
2288 #define A_B1  4
2289 #define A_B2  5
2290 #define A_B3  6
2291 
2292 
2293 
2294 /* alert3:
2295  *  Displays a simple alert box, containing three lines of text (s1-s3),
2296  *  and with either one, two, or three buttons. The text for these buttons
2297  *  is passed in b1, b2, and b3 (NULL for buttons which are not used), and
2298  *  the keyboard shortcuts in c1 and c2. Returns 1, 2, or 3 depending on
2299  *  which button was selected.
2300  */
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)2301 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)
2302 {
2303    char tmp[16];
2304    int avg_w, avg_h;
2305    int len1, len2, len3;
2306    int maxlen = 0;
2307    int buttons = 0;
2308    int b[3];
2309    int c;
2310 
2311    #define SORT_OUT_BUTTON(x) {                                            \
2312       if (b##x) {                                                          \
2313 	 alert_dialog[A_B##x].flags &= ~D_HIDDEN;                          \
2314 	 alert_dialog[A_B##x].key = c##x;                                  \
2315 	 alert_dialog[A_B##x].dp = (char *)b##x;                           \
2316 	 len##x = gui_strlen(b##x);                                        \
2317 	 b[buttons++] = A_B##x;                                            \
2318       }                                                                    \
2319       else {                                                               \
2320 	 alert_dialog[A_B##x].flags |= D_HIDDEN;                           \
2321 	 len##x = 0;                                                       \
2322       }                                                                    \
2323    }
2324 
2325    usetc(tmp+usetc(tmp, ' '), 0);
2326 
2327    avg_w = text_length(font, tmp);
2328    avg_h = text_height(font);
2329 
2330    alert_dialog[A_S1].dp = alert_dialog[A_S2].dp = alert_dialog[A_S3].dp =
2331    alert_dialog[A_B1].dp = alert_dialog[A_B2].dp = empty_string;
2332 
2333    if (s1) {
2334       alert_dialog[A_S1].dp = (char *)s1;
2335       maxlen = text_length(font, s1);
2336    }
2337 
2338    if (s2) {
2339       alert_dialog[A_S2].dp = (char *)s2;
2340       len1 = text_length(font, s2);
2341       if (len1 > maxlen)
2342 	 maxlen = len1;
2343    }
2344 
2345    if (s3) {
2346       alert_dialog[A_S3].dp = (char *)s3;
2347       len1 = text_length(font, s3);
2348       if (len1 > maxlen)
2349 	 maxlen = len1;
2350    }
2351 
2352    SORT_OUT_BUTTON(1);
2353    SORT_OUT_BUTTON(2);
2354    SORT_OUT_BUTTON(3);
2355 
2356    len1 = MAX(len1, MAX(len2, len3)) + avg_w*3;
2357    if (len1*buttons > maxlen)
2358       maxlen = len1*buttons;
2359 
2360    maxlen += avg_w*4;
2361    alert_dialog[0].w = maxlen;
2362    alert_dialog[A_S1].w = alert_dialog[A_S2].w = alert_dialog[A_S3].w = maxlen - avg_w*2;
2363    alert_dialog[A_S1].x = alert_dialog[A_S2].x = alert_dialog[A_S3].x =
2364 						alert_dialog[0].x + avg_w;
2365 
2366    alert_dialog[A_B1].w = alert_dialog[A_B2].w = alert_dialog[A_B3].w = len1;
2367 
2368    alert_dialog[A_B1].x = alert_dialog[A_B2].x = alert_dialog[A_B3].x =
2369 				       alert_dialog[0].x + maxlen/2 - len1/2;
2370 
2371    if (buttons == 3) {
2372       alert_dialog[b[0]].x = alert_dialog[0].x + maxlen/2 - len1*3/2 - avg_w;
2373       alert_dialog[b[2]].x = alert_dialog[0].x + maxlen/2 + len1/2 + avg_w;
2374    }
2375    else if (buttons == 2) {
2376       alert_dialog[b[0]].x = alert_dialog[0].x + maxlen/2 - len1 - avg_w;
2377       alert_dialog[b[1]].x = alert_dialog[0].x + maxlen/2 + avg_w;
2378    }
2379 
2380    alert_dialog[0].h = avg_h*8;
2381    alert_dialog[A_S1].y = alert_dialog[0].y + avg_h;
2382    alert_dialog[A_S2].y = alert_dialog[0].y + avg_h*2;
2383    alert_dialog[A_S3].y = alert_dialog[0].y + avg_h*3;
2384    alert_dialog[A_S1].h = alert_dialog[A_S2].h = alert_dialog[A_S3].h = avg_h;
2385    alert_dialog[A_B1].y = alert_dialog[A_B2].y = alert_dialog[A_B3].y = alert_dialog[0].y + avg_h*5;
2386    alert_dialog[A_B1].h = alert_dialog[A_B2].h = alert_dialog[A_B3].h = avg_h*2;
2387 
2388    centre_dialog(alert_dialog);
2389    set_dialog_color(alert_dialog, gui_fg_color, gui_bg_color);
2390    for (c = 0; alert_dialog[c].proc; c++)
2391       if (alert_dialog[c].proc == _gui_ctext_proc)
2392 	 alert_dialog[c].bg = -1;
2393 
2394    clear_keybuf();
2395 
2396    do {
2397    } while (gui_mouse_b());
2398 
2399    c = popup_dialog(alert_dialog, A_B1);
2400 
2401    if (c == A_B1)
2402       return 1;
2403    else if (c == A_B2)
2404       return 2;
2405    else
2406       return 3;
2407 }
2408 
2409 
2410 
2411 /* alert:
2412  *  Displays a simple alert box, containing three lines of text (s1-s3),
2413  *  and with either one or two buttons. The text for these buttons is passed
2414  *  in b1 and b2 (b2 may be null), and the keyboard shortcuts in c1 and c2.
2415  *  Returns 1 or 2 depending on which button was selected.
2416  */
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)2417 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)
2418 {
2419    int ret;
2420 
2421    ret = alert3(s1, s2, s3, b1, b2, NULL, c1, c2, 0);
2422 
2423    if (ret > 2)
2424       ret = 2;
2425 
2426    return ret;
2427 }
2428