Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * Portions of this file are copyright Rebirth contributors and licensed as
3
 * described in COPYING.txt.
4
 * Portions of this file are copyright Parallax Software and licensed
5
 * according to the Parallax license below.
6
 * See COPYING.txt for license details.
7
 
8
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18
*/
19
 
20
#include <stdlib.h>
21
#include <string.h>
22
#include <ctype.h>
23
#include "physfsx.h"
24
#include "u_mem.h"
25
#include "maths.h"
26
#include "pstypes.h"
27
#include "gr.h"
28
#include "strutil.h"
29
#include "event.h"
30
#include "window.h"
31
#include "ui.h"
32
#include "mouse.h"
33
#include "key.h"
34
#include "func.h"
35
#include "dxxerror.h"
36
 
37
#include "compiler-range_for.h"
38
#include "partial_range.h"
39
#include <utility>
40
 
41
namespace dcx {
42
 
43
#define MAXMENUS 30
44
#define MAXITEMS 32
45
 
46
namespace {
47
 
48
struct ITEM {
49
        short                   x, y, w, h;
50
        RAIIdmem<char[]> Text;
51
        RAIIdmem<char[]> InactiveText;
52
        short                   Hotkey;
53
        int                     (*user_function)(void);
54
};
55
 
56
struct MENU : embed_window_pointer_t {
57
        short                   x, y, w, h;
58
        short                           ShowBar;
59
        short                           CurrentItem;
60
        uint16_t                        NumItems;
61
        short                           Displayed;
62
        short                           Active;
63
        std::array<ITEM, MAXITEMS> Item;
64
};
65
 
66
}
67
 
68
static std::array<MENU, MAXMENUS> Menu;
69
 
70
static unsigned num_menus;
71
static int state;
72
 
73
#define CMENU (Menu[0].CurrentItem+1)
74
 
75
static window_event_result menubar_handler(window *wind,const d_event &event, MENU *menu);
76
static window_event_result menu_handler(window *wind,const d_event &event, MENU *menu);
77
 
78
//------------------------- Show a menu item -------------------
79
 
80
static void item_show( MENU * menu, int n )
81
{
82
        ITEM * item = &menu->Item[n];
83
 
84
        gr_set_default_canvas();
85
        auto &canvas = *grd_curcanv;
86
        // If this is a seperator, then draw it.
87
        if ( item->Text[0] == '-'  )
88
        {
89
                const auto color = CBLACK;
90
                gr_urect(canvas, item->x, item->y+item->h/2, item->x+item->w-1, item->y+item->h/2, color);
91
                return;
92
        }      
93
 
94
        if ( menu->CurrentItem==n && menu->ShowBar )
95
        {
96
                if ( menu != &Menu[0] )
97
                {
98
                        const auto color = CBLACK;
99
                        gr_urect(canvas, item->x+1, item->y+1, item->x+menu->w-2, item->y+item->h-2, color);
100
                }
101
                gr_set_fontcolor(canvas, CWHITE, CBLACK);
102
        }else {
103
                if ( menu != &Menu[0] )
104
                {
105
                        const auto color = CGREY;
106
                        gr_urect(canvas, item->x+1, item->y+1, item->x+menu->w-2, item->y+item->h-2, color);
107
                }
108
                gr_set_fontcolor(canvas, CBLACK, CGREY);
109
        }
110
 
111
        if ( menu != &Menu[0] )
112
        {
113
                if ( menu->Active)
114
                        gr_ustring(canvas, *canvas.cv_font, item->x + 1, item->y + 1, item->Text.get());
115
                else
116
                        gr_ustring(canvas, *canvas.cv_font, item->x + 1, item->y + 1, item->InactiveText.get());
117
        } else {
118
                if ( menu->Active)
119
                        gr_ustring(canvas, *canvas.cv_font, item->x, item->y, item->Text.get());
120
                else
121
                        gr_ustring(canvas, *canvas.cv_font, item->x, item->y, item->InactiveText.get());
122
        }
123
}
124
 
125
static void menu_draw(MENU *menu)
126
{
127
        int i;
128
 
129
        gr_set_default_canvas();
130
        auto &canvas = *grd_curcanv;
131
 
132
        // Draw the menu background
133
        gr_urect(canvas, menu->x, menu->y, menu->x + menu->w - 1, menu->y + menu->h - 1, CGREY);
134
        if ( menu != &Menu[0] )
135
        {
136
                gr_ubox(canvas, menu->x, menu->y, menu->x + menu->w - 1, menu->y + menu->h - 1, CBLACK);
137
        }
138
 
139
        // Draw the items
140
 
141
        for (i=0; i< menu->NumItems; i++ )
142
                item_show( menu, i );
143
}
144
 
145
//---------------------------- Show a menu ---------------------
146
 
147
static void menu_show( MENU * menu )
148
{
149
        if (!menu->wind)
150
        {
151
                menu->wind = window_create(grd_curscreen->sc_canvas, menu->x, menu->y, menu->w, menu->h,
152
                                                                   ((menu == &Menu[0]) ? menubar_handler : menu_handler), menu);
153
                if (!menu->wind)
154
                        return;
155
 
156
                if (menu == &Menu[0])
157
                        window_set_modal(Menu[0].wind, 0);      // allow windows behind the menubar to accept events (e.g. the keypad dialogs)
158
        }
159
 
160
        window_set_visible(menu->wind, 1);
161
 
162
        // Mark as displayed.
163
        menu->Displayed = 1;
164
}
165
 
166
//-------------------------- Hide a menu -----------------------
167
 
168
static void menu_hide( MENU * menu )
169
{
170
 
171
        // Can't hide if it's not already drawn
172
        if (!menu->Displayed) return;
173
 
174
        if ((menu != &Menu[0]) && menu->wind)
175
                window_set_visible(menu->wind, 0);      // don't draw or receive events
176
 
177
        menu->Active = 0;
178
        if (Menu[0].wind && menu == &Menu[0])
179
                window_set_modal(Menu[0].wind, 0);
180
 
181
        // Mark as hidden.
182
        menu->Displayed = 0;
183
}
184
 
185
 
186
//------------------------- Move the menu bar ------------------
187
 
188
static void menu_move_bar_to( MENU * menu, int number )
189
{
190
        int old_item;
191
 
192
        old_item = menu->CurrentItem;
193
        menu->CurrentItem = number;
194
 
195
        if (menu->Displayed && (number != old_item))
196
        {
197
                item_show( menu, old_item );
198
                item_show( menu, number );
199
        }
200
}
201
 
202
//------------------------ Match keypress to item ------------------
203
static int menu_match_keypress( MENU * menu, int keypress )
204
{
205
        int i;
206
        char c;
207
        if ((keypress & KEY_CTRLED) || (keypress & KEY_SHIFTED))
208
                return -1;
209
 
210
        keypress &= 0xFF;
211
 
212
        c = key_ascii();
213
 
214
        for (i=0; i< menu->NumItems; i++ )
215
        {
216
                auto letter = strrchr(menu->Item[i].Text.get(), CC_UNDERLINE);
217
                if (letter)
218
                {
219
                        letter++;
220
                        if (c==tolower(*letter))
221
                                return i;
222
                }
223
        }
224
        return -1;
225
}
226
 
227
 
228
static int menu_is_mouse_on( ITEM * item )
229
{
230
        int x, y, z;
231
 
232
        mouse_get_pos(&x, &y, &z);
233
 
234
        if ((x >= item->x) &&
235
                (x < item->x + item->w ) &&
236
                (y >= item->y) &&
237
                (y <= item->y + item->h ) )
238
                return 1;
239
        else
240
                return 0;
241
}
242
 
243
static int menu_check_mouse_item( MENU * menu )
244
{
245
        int i;
246
 
247
        for (i=0; i<menu->NumItems; i++ )
248
        {
249
                if (menu_is_mouse_on( &menu->Item[i] ))
250
                {
251
                        if (menu->Item[i].Text[0] == '-')
252
                                return -1;
253
                        else
254
                                return i;
255
                }
256
        }
257
        return -1;
258
}
259
 
260
 
261
static void menu_hide_all()
262
{
263
        range_for (auto &i, partial_range(Menu, 1u, num_menus))
264
                menu_hide(&i);
265
 
266
        Menu[0].ShowBar = 0;
267
        Menu[0].Active = 0;
268
        if (Menu[0].wind)
269
                window_set_modal(Menu[0].wind, 0);
270
        menu_show( &Menu[0] );
271
 
272
}
273
 
274
 
275
static int state2_alt_down;
276
 
277
static window_event_result do_state_0(const d_event &event)
278
{
279
        int keypress = 0;
280
 
281
        if (event.type == EVENT_KEY_COMMAND)
282
                keypress = event_key_get(event);
283
 
284
        Menu[0].Active = 0;
285
        if (Menu[0].wind)
286
                window_set_modal(Menu[0].wind, 0);
287
        Menu[0].ShowBar = 0;
288
 
289
        if ( keypress & KEY_ALTED )     {
290
                const auto i = menu_match_keypress( &Menu[0], keypress );
291
                if (i > -1 )
292
                {
293
                        Menu[0].CurrentItem = i;
294
                        Menu[0].Active = 0;
295
                        if (Menu[0].wind)
296
                                window_set_modal(Menu[0].wind, 0);
297
 
298
                        state = 3;     
299
                        Menu[ CMENU ].ShowBar = 1;
300
                        Menu[ CMENU ].Active = 1;
301
                        Menu[0].ShowBar = 1;
302
 
303
                        menu_show( &Menu[ CMENU ] );
304
                        menu_show( &Menu[0] );
305
                        return window_event_result::handled;
306
                }
307
        }
308
 
309
        range_for (auto &i, partial_const_range(Menu, num_menus))
310
                range_for (auto &j, partial_const_range(i.Item, i.NumItems))
311
                {
312
                        if ( j.Hotkey == keypress )
313
                        {
314
                                if (j.user_function)
315
                                        j.user_function();
316
                                return window_event_result::handled;
317
                        }
318
                }
319
 
320
        if (keypress & KEY_ALTED)
321
        //if ( (keypress & 0xFF) == KEY_LALT )
322
        {
323
                // Make sure the menubar receives events exclusively
324
                state = 1;
325
                Menu[0].Active = 1;
326
 
327
                // Put the menubar in front - hope this doesn't mess anything up by leaving it there
328
                // If it does, will need to remember the previous front window and restore it.
329
                // (Personally, I just use either the mouse or 'hotkeys' for menus)
330
                window_select(*Menu[0].wind);
331
 
332
                window_set_modal(Menu[0].wind, 1);
333
                menu_show( &Menu[0] );
334
                return window_event_result::handled;
335
        }
336
 
337
        const auto i = menu_check_mouse_item( &Menu[0] );
338
 
339
        if ( B1_JUST_PRESSED && (i > -1))
340
        {
341
                Menu[0].CurrentItem = i;
342
                state = 3;     
343
                Menu[ CMENU ].ShowBar = 1;
344
                Menu[0].ShowBar = 1;
345
                Menu[ CMENU ].Active = 1;
346
                Menu[0].Active = 0;
347
                window_set_modal(Menu[0].wind, 0);
348
                menu_show( &Menu[ CMENU ] );
349
                menu_show( &Menu[0] );
350
                return window_event_result::handled;
351
        }
352
        return window_event_result::ignored;
353
}
354
 
355
static window_event_result do_state_1(const d_event &event)
356
{
357
        int i;
358
        int keypress = 0;
359
        window_event_result rval = window_event_result::ignored;
360
 
361
        if (event.type == EVENT_KEY_COMMAND)
362
                keypress = event_key_get(event);
363
 
364
        if ((event.type == EVENT_KEY_RELEASE) && !(event_key_get(event) & KEY_ALTED))
365
        {
366
                state = 2;
367
                state2_alt_down = 0;
368
                Menu[0].ShowBar = 1;
369
                Menu[0].Active = 1;
370
                menu_show( &Menu[0] );
371
                rval = window_event_result::handled;
372
        }
373
 
374
        i = menu_match_keypress( &Menu[0], keypress );
375
 
376
        if (i > -1 )
377
        {
378
                Menu[0].CurrentItem = i;
379
                Menu[0].Active = 0;
380
                window_set_modal(Menu[0].wind, 0);
381
                state = 3;     
382
                Menu[ CMENU ].ShowBar = 1;
383
                Menu[ CMENU ].Active = 1;
384
                Menu[0].ShowBar = 1;
385
 
386
                menu_show( &Menu[ CMENU ] );
387
                menu_show( &Menu[0] );
388
                rval = window_event_result::handled;
389
        }
390
 
391
        i = menu_check_mouse_item( &Menu[0] );
392
 
393
        if ( (i == -1) && B1_JUST_RELEASED )
394
        {
395
                state = 0;
396
                menu_hide_all();
397
                return window_event_result::handled;
398
        }
399
 
400
        if ( B1_JUST_PRESSED && (i > -1))
401
        {
402
                Menu[0].CurrentItem = i;
403
                state = 3;     
404
                Menu[ CMENU ].ShowBar = 1;
405
                Menu[ CMENU ].Active = 1;
406
                Menu[0].ShowBar = 1;
407
                Menu[0].Active = 0;
408
                window_set_modal(Menu[0].wind, 0);
409
                menu_show( &Menu[ CMENU ] );
410
                menu_show( &Menu[0] );
411
                return window_event_result::handled;
412
        }
413
 
414
        return rval;
415
}
416
 
417
static window_event_result do_state_2(const d_event &event)
418
{
419
        int i;
420
        int keypress = 0;
421
        window_event_result rval = window_event_result::ignored;
422
        if (event.type == EVENT_KEY_COMMAND)
423
                keypress = event_key_get(event);
424
 
425
        if (keypress & KEY_ALTED)
426
        {
427
                state2_alt_down = 1;
428
                rval = window_event_result::handled;
429
        }
430
 
431
        if ((event.type == EVENT_KEY_RELEASE) && !(event_key_get(event) & KEY_ALTED) && state2_alt_down)
432
        {
433
                state = 0;
434
                menu_hide_all();
435
                rval = window_event_result::handled;
436
        }                      
437
 
438
        switch( keypress )
439
        {
440
        case KEY_ESC:
441
                state = 0;
442
                menu_hide_all();
443
                return window_event_result::handled;
444
        case KEY_LEFT:
445
        case KEY_PAD4:
446
                i = Menu[0].CurrentItem-1;
447
                if (i < 0 ) i = Menu[0].NumItems-1;
448
                menu_move_bar_to( &Menu[0], i );
449
                return window_event_result::handled;
450
        case KEY_RIGHT:
451
        case KEY_PAD6:
452
                i = Menu[0].CurrentItem+1;
453
                if (i >= Menu[0].NumItems ) i = 0;
454
                menu_move_bar_to( &Menu[0], i );
455
                return window_event_result::handled;
456
        case KEY_ENTER:
457
        case KEY_PADENTER:
458
        case KEY_DOWN:
459
        case KEY_PAD2:
460
                state = 3;     
461
                Menu[ CMENU ].ShowBar = 1;
462
                Menu[ CMENU ].Active = 1;
463
                Menu[0].Active = 0;
464
                window_set_modal(Menu[0].wind, 0);
465
                menu_show( &Menu[ 0 ] );
466
                menu_show( &Menu[ CMENU ] );
467
                return window_event_result::handled;
468
 
469
        default:
470
                i = menu_match_keypress( &Menu[0], keypress );
471
 
472
                if (i > -1 )
473
                {
474
                        Menu[0].CurrentItem = i;
475
                        Menu[0].Active = 0;
476
                        window_set_modal(Menu[0].wind, 0);
477
                        state = 3;     
478
                        Menu[ CMENU ].ShowBar = 1;
479
                        Menu[ CMENU ].Active = 1;
480
                        Menu[0].ShowBar = 1;
481
                        menu_show( &Menu[ CMENU ] );
482
                        menu_show( &Menu[0] );
483
                        return window_event_result::handled;
484
                }
485
 
486
                i = menu_check_mouse_item( &Menu[0] );
487
 
488
                if ( (i == -1) && B1_JUST_RELEASED )
489
                {
490
                        state = 0;
491
                        menu_hide_all();
492
                        return window_event_result::handled;
493
                }
494
 
495
                if (i > -1)
496
                {
497
                        Menu[0].CurrentItem = i;
498
                        Menu[0].Active = 0;
499
                        window_set_modal(Menu[0].wind, 0);
500
                        state = 3;     
501
                        Menu[ CMENU ].ShowBar = 1;
502
                        Menu[ CMENU ].Active = 1;
503
                        Menu[0].ShowBar = 1;
504
                        menu_show( &Menu[ CMENU ] );
505
                        menu_show( &Menu[0] );
506
                        return window_event_result::handled;
507
                }
508
        }
509
 
510
        return rval;
511
}
512
 
513
 
514
 
515
static window_event_result menu_handler(window *, const d_event &event, MENU *menu)
516
{
517
        int i;
518
        int keypress = 0;
519
 
520
        if (state != 3)
521
                return window_event_result::ignored;
522
 
523
        if (event.type == EVENT_KEY_COMMAND)
524
                keypress = event_key_get(event);
525
        else if (event.type == EVENT_WINDOW_CLOSE)      // quitting
526
        {
527
                state = 0;
528
                menu_hide_all();
529
                menu->wind = nullptr;
530
                return window_event_result::ignored;
531
        }
532
        window_event_result rval = window_event_result::ignored;
533
        switch( keypress )
534
        {
535
                case 0:
536
                        break;
537
                case KEY_ESC:
538
                        state = 0;
539
                        menu_hide_all();
540
                        rval = window_event_result::handled;
541
                        break;
542
                case KEY_DOWN:
543
                case KEY_PAD2:
544
                        i = Menu[ CMENU ].CurrentItem;
545
                        do {
546
                                i++;           
547
                                if ( i >= Menu[ CMENU ].NumItems )
548
                                        i = 0;
549
                        } while( Menu[CMENU].Item[i].Text[0] == '-');
550
                        menu_move_bar_to( &Menu[ CMENU ], i );
551
                        rval = window_event_result::handled;
552
                        break;
553
                case KEY_UP:
554
                case KEY_PAD8:
555
                        i = Menu[ CMENU ].CurrentItem;
556
                        do
557
                        {
558
                                i--;
559
                                if ( i < 0 )
560
                                        i = Menu[ CMENU ].NumItems-1;
561
                        } while( Menu[CMENU].Item[i].Text[0] == '-');
562
                        menu_move_bar_to( &Menu[ CMENU ], i );
563
                        rval = window_event_result::handled;
564
                        break;
565
                case KEY_RIGHT:
566
                case KEY_PAD6:
567
                        menu_hide( &Menu[ CMENU ] );
568
                        i = Menu[0].CurrentItem+1;
569
                        if (i >= Menu[0].NumItems ) i = 0;
570
                        menu_move_bar_to( &Menu[0], i );
571
                        Menu[CMENU].ShowBar = 1;
572
                        Menu[CMENU].Active = 1;
573
                        menu_show( &Menu[CMENU] );
574
                        rval = window_event_result::handled;
575
                        break;
576
                case KEY_LEFT:
577
                case KEY_PAD4:
578
                        menu_hide( &Menu[ CMENU ] );
579
                        i = Menu[0].CurrentItem-1;
580
                        if (i < 0 ) i = Menu[0].NumItems-1;
581
                        menu_move_bar_to( &Menu[0], i );
582
                        Menu[ CMENU ].ShowBar = 1;
583
                        Menu[CMENU].Active = 1;
584
                        menu_show( &Menu[ CMENU ] );
585
                        rval = window_event_result::handled;
586
                        break;
587
                case KEY_ENTER:
588
                case KEY_PADENTER:
589
                        state = 0;
590
                        menu_hide_all();
591
 
592
                        if (Menu[CMENU].Item[ Menu[CMENU].CurrentItem ].user_function)
593
                                Menu[CMENU].Item[ Menu[CMENU].CurrentItem ].user_function();
594
 
595
                        rval = window_event_result::handled;
596
                        break;
597
 
598
                default:
599
                        i = menu_match_keypress( &Menu[ CMENU ], keypress );
600
 
601
                        if (i > -1 )
602
                        {
603
                                menu_move_bar_to( &Menu[ CMENU ], i );
604
                                state = 0;
605
                                menu_hide_all();
606
 
607
                                if (Menu[CMENU].Item[ Menu[CMENU].CurrentItem ].user_function)
608
                                        Menu[CMENU].Item[ Menu[CMENU].CurrentItem ].user_function();
609
                                rval = window_event_result::handled;
610
                                break;
611
                        }
612
        }
613
 
614
        if (event.type == EVENT_MOUSE_MOVED || B1_JUST_RELEASED)
615
        {
616
                i = menu_check_mouse_item( &Menu[CMENU] );
617
 
618
                if (i > -1 )
619
                {
620
                        if ( B1_JUST_RELEASED )
621
                        {
622
                                menu_move_bar_to( &Menu[ CMENU ], i );
623
                                state = 0;
624
                                menu_hide_all();
625
 
626
                                if (Menu[CMENU].Item[ Menu[CMENU].CurrentItem ].user_function)
627
                                        Menu[CMENU].Item[ Menu[CMENU].CurrentItem ].user_function();
628
                                rval = window_event_result::handled;
629
                        }
630
                        else
631
                        {
632
                                menu_move_bar_to( &Menu[ CMENU ], i );
633
                                rval = window_event_result::handled;
634
                        }
635
                } else {
636
                        i = menu_check_mouse_item( &Menu[0] );
637
 
638
                        if (i > -1)
639
                        {
640
                                if ( Menu[0].CurrentItem != i)  {
641
                                        menu_hide( &Menu[ CMENU ] );
642
                                        menu_move_bar_to( &Menu[0], i );
643
                                        Menu[ CMENU ].ShowBar = 1;
644
                                        Menu[CMENU].Active = 1;
645
                                        menu_show( &Menu[ CMENU ] );
646
                                }
647
 
648
                                rval = window_event_result::handled;
649
                        }
650
 
651
                        if ( B1_JUST_RELEASED )
652
                        {
653
                                state = 0;
654
                                menu_hide_all();
655
                                rval = window_event_result::handled;
656
                        }
657
                }
658
        }
659
 
660
        if (event.type == EVENT_WINDOW_DRAW)
661
        {
662
                menu_draw(&Menu[CMENU]);
663
                return window_event_result::handled;
664
        }
665
 
666
        return rval;
667
}
668
 
669
static window_event_result menubar_handler(window *, const d_event &event, MENU *)
670
{
671
        if (event.type == EVENT_WINDOW_DRAW)
672
        {
673
                menu_draw(&Menu[0]);
674
                return window_event_result::handled;
675
        }
676
        else if (event.type == EVENT_WINDOW_CLOSE)
677
        {
678
                //menu_hide_all();
679
                //menu_hide( &Menu[0] );
680
 
681
                range_for (auto &i, partial_range(Menu, 1u, num_menus))
682
                {
683
                        if (i.wind)
684
                        {
685
                                window_close(std::exchange(i.wind, nullptr));
686
                        }
687
                }
688
 
689
                Menu[0].wind = nullptr;
690
        }
691
 
692
        switch (state)
693
        {
694
                case 0:
695
                        return do_state_0(event);
696
                case 1:
697
                        return do_state_1(event);
698
                case 2:
699
                        return do_state_2(event);
700
                case 3:
701
                        break;
702
                default:
703
                        state = 0;
704
                        menu_hide_all();
705
        }
706
        return window_event_result::ignored;
707
}
708
 
709
static void CommaParse(uint_fast32_t n, char * dest, const PHYSFSX_gets_line_t<200>::line_t &source)
710
{
711
        int i = 0, j=0, cn = 0;
712
 
713
        // Go to the n'th comma
714
        while (cn < n )
715
                if (source[i++] == ',' )
716
                        cn++;
717
        // Read all the whitespace
718
        while ( source[i]==' ' || source[i]=='\t' || source[i]==13 || source[i]==10 )
719
                i++;
720
 
721
        // Read up until the next comma
722
        while ( source[i] != ',' )
723
        {
724
                dest[j] = source[i++];
725
                j++;           
726
        }
727
 
728
        // Null-terminate       
729
        dest[j++] = 0;
730
}
731
 
732
//translate '&' characters to the underline character
733
static void ul_xlate(char *s)
734
{
735
        while ((s=strchr(s,'&'))!=NULL)
736
                *s = CC_UNDERLINE;
737
}
738
 
739
 
740
int menubar_init(grs_canvas &canvas, const char *const file)
741
{
742
        int np;
743
        char buf1[200];
744
        char buf2[204];
745
 
746
        num_menus = state = 0;
747
 
748
        // This method should be faster than explicitly setting all the variables (I think)
749
        Menu = {};
750
 
751
        range_for (auto &i, Menu)
752
                range_for (auto &j, i.Item)
753
                        j.Hotkey = -1;
754
        auto infile = PHYSFSX_openReadBuffered(file);
755
 
756
        if (!infile)
757
        {
758
                Warning("Could not find %s\n", file);
759
                return 0;
760
        }
761
 
762
        PHYSFSX_gets_line_t<200> buffer;
763
        while ( PHYSFSX_fgets( buffer, infile) != NULL )
764
        {
765
                if ( buffer[0] == ';' ) continue;
766
 
767
                CommaParse( 0, buf1, buffer );
768
                char *p;
769
                const auto mi = strtoul(buf1, &p, 10);
770
                if (mi >= MAXMENUS)
771
                {
772
                        con_printf(CON_URGENT, "%s:%u: too many menus defined: max=%u, requested=%lu in \"%s\"", __FILE__, __LINE__, MAXMENUS, mi, file);
773
                        break;
774
                }
775
 
776
                CommaParse( 1, buf1, buffer );
777
                const auto ii = strtoul(buf1, &p, 10);
778
                if (ii >= MAXITEMS)
779
                {
780
                        con_printf(CON_URGENT, "%s:%u: too many items defined: max=%u, requested=%lu in \"%s\"", __FILE__, __LINE__, MAXITEMS, ii, file);
781
                        break;
782
                }
783
                auto &menu = Menu[mi];
784
                auto &item = menu.Item[ii];
785
 
786
                CommaParse( 2, buf1, buffer );
787
                ul_xlate(buf1);
788
 
789
                item.Text.reset(d_strdup(buf1[0] == '-' ? buf1 : (snprintf(buf2, sizeof(buf2), " %.197s ", buf1), buf2)));
790
 
791
                item.InactiveText.reset(d_strdup(item.Text.get()));
792
 
793
                for (int i = 0, j = 0;; i++ )
794
                {
795
                        np = item.Text[i];
796
                        if (np != CC_UNDERLINE)
797
                                item.InactiveText[j++] = np;
798
                        if (!np)
799
                                break;
800
                }
801
 
802
                CommaParse( 3, buf1, buffer );
803
                if (buf1[0]=='{' && buf1[1] =='}')
804
                        item.Hotkey = -1;
805
                else                    {
806
                        const auto i = DecodeKeyText(buf1);
807
                        if (i<1) {
808
                                UserError("Unknown key, %s, in %s\n", buf1, file );
809
                        } else {
810
                                item.Hotkey = i;
811
                        }
812
                }
813
                CommaParse( 4, buf1, buffer );
814
 
815
                if (buf1[0])
816
                {
817
                        item.user_function = func_get(buf1, &np);
818
 
819
                        if (!item.user_function)
820
                        {
821
                                con_printf(CON_URGENT, "%s:%u: unknown function \"%s\" in \"%s\"", __FILE__, __LINE__, buf1, file);
822
                        }
823
                }
824
 
825
                item.x = menu.x;
826
                item.y = menu.y;
827
 
828
                int w, h;
829
                if (item.Text[0] == '-')
830
                {
831
                        w = 1; h = 3;
832
                } else {
833
                        gr_get_string_size(*canvas.cv_font, item.Text.get(), &w, &h, nullptr);
834
                        w += 2;
835
                        h += 2;
836
                }
837
 
838
                if (mi == 0)
839
                {
840
                        menu.h = h;
841
 
842
                        item.x = menu.x + menu.w;
843
 
844
                        item.y = menu.y;
845
 
846
                        Menu[ii+1].x = menu.x + menu.w;
847
                        Menu[ii+1].y = menu.h - 2;
848
 
849
                        item.w = w;
850
                        item.h = h;
851
 
852
                        menu.w += w;
853
 
854
                }else   {
855
                        if (w > menu.w)
856
                        {
857
                                menu.w = w;
858
                                range_for (auto &i, partial_range(menu.Item, menu.NumItems))
859
                                        i.w = w;
860
                        }
861
                        item.w = menu.w;
862
                        item.x = menu.x;
863
                        item.y = menu.y+menu.h;
864
                        item.h = h;
865
                        menu.h += h;
866
                }
867
 
868
                if (ii >= menu.NumItems)
869
                {
870
                        menu.NumItems = ii + 1;
871
                }
872
 
873
                if (mi >= num_menus)
874
                        num_menus = mi + 1;
875
 
876
        }
877
        Menu[0].w = 700;
878
 
879
        return 1;
880
}
881
 
882
void menubar_hide()
883
{
884
        state = 0;
885
        menu_hide_all();
886
        menu_hide( &Menu[0] );
887
}
888
 
889
void menubar_show()
890
{
891
        menu_show( &Menu[0] );
892
}
893
 
894
void menubar_close()
895
{
896
        if (!Menu[0].wind)
897
                return;
898
        window_close(std::exchange(Menu[0].wind, nullptr));
899
}
900
 
901
}