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