]> icculus.org git repositories - taylor/freespace2.git/blob - src/popup/popup.cpp
added copyright header
[taylor/freespace2.git] / src / popup / popup.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/Popup/Popup.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Code for displaying pop-up dialog boxes
16  *
17  * $Log$
18  * Revision 1.3  2002/06/09 04:41:25  relnev
19  * added copyright header
20  *
21  * Revision 1.2  2002/05/07 03:16:51  theoddone33
22  * The Great Newline Fix
23  *
24  * Revision 1.1.1.1  2002/05/03 03:28:10  root
25  * Initial import.
26  *
27  * 
28  * 12    10/14/99 2:00p Jefff
29  * added include for os_poll to fix build error
30  * 
31  * 11    10/14/99 10:18a Daveb
32  * Fixed incorrect CD checking problem on standalone server.
33  * 
34  * 10    8/16/99 9:50a Jefff
35  * added mouseover webcursor options to user-defined popup buttons.
36  * 
37  * 9     8/11/99 5:47p Jefff
38  * fixed button bitmap loading
39  * 
40  * 8     8/04/99 10:53a Dave
41  * Added title to the user tips popup.
42  * 
43  * 7     8/02/99 9:13p Dave
44  * Added popup tips.
45  * 
46  * 6     6/18/99 5:16p Dave
47  * Added real beam weapon lighting. Fixed beam weapon sounds. Added MOTD
48  * dialog to PXO screen.
49  * 
50  * 5     6/01/99 3:52p Dave
51  * View footage screen. Fixed xstrings to not display the & symbol. Popup,
52  * dead popup, pxo find player popup, pxo private room popup.
53  * 
54  * 4     2/11/99 3:08p Dave
55  * PXO refresh button. Very preliminary squad war support.
56  * 
57  * 3     10/13/98 9:29a Dave
58  * Started neatening up freespace.h. Many variables renamed and
59  * reorganized. Added AlphaColors.[h,cpp]
60  * 
61  * 2     10/07/98 10:53a Dave
62  * Initial checkin.
63  * 
64  * 1     10/07/98 10:51a Dave
65  * 
66  * 36    5/11/98 11:39p Dave
67  * Stuff.
68  * 
69  * 35    3/22/98 10:59p Allender
70  * don't stop time in multiplayer when "in mission"
71  * 
72  * 34    3/17/98 12:41a Lawrance
73  * Support \n's between title and body
74  * 
75  * 33    3/12/98 4:11p Dave
76  * AL: Check that max popup lines isn't exceeded
77  * 
78  * 32    2/22/98 12:19p John
79  * Externalized some strings
80  * 
81  * 31    2/10/98 3:26p Hoffoss
82  * Don't check for 'dead key set' unless in the mission.
83  * 
84  * 30    2/05/98 11:21p Lawrance
85  * move death popup to below letterbox view
86  * 
87  * 29    2/05/98 11:09a Dave
88  * Fixed an ingame join bug. Fixed a read-only file problem with
89  * multiplauer file xfer.
90  * 
91  * 28    2/03/98 11:52p Lawrance
92  * Don't highlight default choice if there is only one.
93  * 
94  * 27    2/03/98 8:18p Dave
95  * More MT stats transfer stuff
96  * 
97  * 26    1/30/98 3:05p Lawrance
98  * reposition text on death popup
99  * 
100  * 25    1/29/98 6:55p Lawrance
101  * Integrate new art for the death popup
102  * 
103  * 24    1/28/98 6:24p Dave
104  * Made standalone use ~8 megs less memory. Fixed multiplayer submenu
105  * sequencing problem.
106  * 
107  * 23    1/27/98 5:52p Lawrance
108  * support new "tiny" popups
109  * 
110  * 22    1/27/98 5:01p Dave
111  * Put in support for leaving multiplayer games in the pause state. Fixed
112  * a popup bug which freed saved screens incorrectly. Reworked scoring
113  * kill and assist evaluation.
114  * 
115  * 21    1/27/98 3:25p Hoffoss
116  * Made popups use the correct button icons for default positive and
117  * negative buttons.
118  * 
119  * 20    1/26/98 6:28p Lawrance
120  * Use '&' meta char for underlining, change how keyboard usage works so
121  * fits better with mouse usage.
122  * 
123  * 19    1/23/98 12:01a Lawrance
124  * Fix bug when in single-choice popups.
125  * 
126  * 18    1/22/98 10:45p Lawrance
127  * Implment default selections and arrow key navigation.
128  * 
129  * 17    1/20/98 5:52p Lawrance
130  * center popup text in X and Y directions.  Support single affirmative
131  * icon special placement.
132  * 
133  * 16    1/19/98 11:37p Lawrance
134  * Fixing Optimization build warnings
135  * 
136  * 15    1/17/98 10:04p Lawrance
137  * fix errors in popup comments
138  * 
139  * 14    1/15/98 2:52p Lawrance
140  * Fix problem with reading bogus buttons.
141  * 
142  * 13    1/14/98 6:55p Dave
143  * Fixed a slew of multiplayer bugs. Made certain important popups ignore
144  * the escape character.
145  * 
146  * 12    1/14/98 6:42p Hoffoss
147  * Massive changes to UI code.  A lot cleaner and better now.  Did all
148  * this to get the new UI_DOT_SLIDER to work properly, which the old code
149  * wasn't flexible enough to handle.
150  * 
151  * 11    1/14/98 12:23p Lawrance
152  * Support for three choice popups.
153  * 
154  * 10    1/13/98 5:37p Dave
155  * Reworked a lot of standalone interface code. Put in single and
156  * multiplayer popups for death sequence. Solidified multiplayer kick
157  * code.
158  * 
159  * 9     1/13/98 4:06p Lawrance
160  * Add underline char for shortcuts.
161  * 
162  * 8     1/11/98 11:14p Lawrance
163  * Don't grey out background when popup is active.
164  * 
165  * 7     1/08/98 10:32a Lawrance
166  * Grey out screen when a popup appears.
167  * 
168  * 6     1/02/98 9:08p Lawrance
169  * Integrated art for popups, expanded options.
170  * 
171  * 5     12/30/97 4:30p Sandeep
172  * Added conditional popups
173  * 
174  * 4     12/26/97 10:01p Lawrance
175  * Allow keyboard shortcuts for popup buttons
176  * 
177  * 3     12/24/97 9:49p Lawrance
178  * ensure mouse gets drawn when popup menu is up
179  * 
180  * 2     12/24/97 8:54p Lawrance
181  * Integrating new popup code
182  * 
183  * 1     12/24/97 3:51p Lawrance
184  *
185  * $NoKeywords: $
186  */
187
188 #include <stdarg.h>
189 #include <string.h>
190 #include "freespace.h"
191 #include "gamesequence.h"
192 #include "key.h"
193 #include "mouse.h"
194 #include "ui.h"
195 #include "parselo.h"
196 #include "popup.h"
197 #include "gamesnd.h"
198 #include "animplay.h"
199 #include "contexthelp.h"
200 #include "keycontrol.h"
201 #include "player.h"
202 #include "font.h"
203 #include "alphacolors.h"
204 #include "osapi.h"
205
206 #define POPUP_MAX_CHOICES                       3                                       // max number of buttons allowed on popup
207
208 #define POPUP_MAX_LINE_CHARS            256                             // max chars of msg text allowed per line
209 #define POPUP_MAX_LINES                         30                                      // max lines of text allowed
210 #define POPUP_MAX_CHARS                         2048                            // total max chars 
211 #define POPUP_INPUT_MAX_CHARS           255                             // max length of input string
212
213 #define POPUP_NOCHANGE                          100
214 #define POPUP_ABORT                                     101
215
216 int Popup_max_display[GR_NUM_RESOLUTIONS] = {
217         11,
218         19
219 };
220
221 char *Popup_slider_name[GR_NUM_RESOLUTIONS] = {
222         "slider",
223         "2_slider"
224 };
225
226 int Popup_slider_coords[GR_NUM_RESOLUTIONS][4] = {
227         { // GR_640
228                 121, 109, 15, 105
229         },
230         { // GR_1024
231                 195, 177, 30, 173
232         }
233 };
234
235 ////////////////////////////////////////////////////////////////
236 // Internal popup flags
237 ////////////////////////////////////////////////////////////////
238 #define PF_INPUT                                                (1<<0)                  // contents of the box is an inputbox and a caption
239
240 ////////////////////////////////////////////////////////////////
241 // Popup data struct
242 ////////////////////////////////////////////////////////////////
243 typedef struct popup_info
244 {
245         int     nchoices;                                                                                                               // number of choices user can pick
246         char    *button_text[POPUP_MAX_CHOICES];                                                        // button text
247         int     keypress[POPUP_MAX_CHOICES];                                                            // button keypress shortcut
248         int     shortcut_index[POPUP_MAX_CHOICES];                                              // what char should be underlines for shortcut
249         char    raw_text[POPUP_MAX_CHARS];                                                                      // the unbroken text for the popup
250         char    title[POPUP_MAX_LINE_CHARS];                                                            // title text for popup (optional)
251         int     nlines;
252         char    msg_lines[POPUP_MAX_LINES][POPUP_MAX_LINE_CHARS];       // lines of text in popup
253         char    input_text[POPUP_INPUT_MAX_CHARS];                                              // input box text (if this is an inputbox popup)
254         int     max_input_text_len;
255         int     web_cursor_flag[POPUP_MAX_CHOICES];                                             // flag for using web cursor over button
256 } popup_info;
257
258 ////////////////////////////////////////////////////////////////
259 // UI Data and constants
260 ////////////////////////////////////////////////////////////////
261 UI_WINDOW       Popup_window;
262 UI_BUTTON       Popup_buttons[POPUP_MAX_CHOICES];                       // actual lit buttons
263 UI_BUTTON       Popup_button_regions[POPUP_MAX_CHOICES];        // fake buttons used for mouse detection over text
264 UI_INPUTBOX     Popup_input;                                                                            // input box for the popup
265 UI_SLIDER2      Popup_slider;                                                                           // if we have more text in the popup than can be displayed at once
266
267 // extents for message portion of popup
268 int Popup_text_coords[GR_NUM_RESOLUTIONS][4] = {
269         { // GR_640
270                 137, 106, 343, 113
271         },
272         { // GR_1024
273                 219, 169, 558, 182
274         }
275 };
276
277 // input popup info
278 // offset from the first y text line value to place the centered input box
279 int Popup_input_y_offset[GR_NUM_RESOLUTIONS] = {
280         40, 
281         40
282 };
283
284 // offset from the first y text line value to start drawing text
285 int Popup_input_text_y_offset[GR_NUM_RESOLUTIONS] = {
286         30,
287         30
288 };
289
290 typedef struct popup_background
291 {
292         char    *filename;                                                      // filename for background
293         int     coords[2];                                                      // coords to draw background at
294 } popup_background;
295
296 ////////////////////////////////////////////////////////////////
297 // Module globals
298 ////////////////////////////////////////////////////////////////
299 static int Popup_is_active=0;
300 static int Popup_should_die=0;                  // popup should quit during the next iteration of its loop
301
302 static popup_info Popup_info;
303 static int Popup_flags;
304
305 static int Title_coords[GR_NUM_RESOLUTIONS][5] =
306 {
307         { // GR_640
308                 137,            // x-left
309                 106,            // y-top
310                 343,            // width
311                 26,             //      height
312                 308             // center
313         },
314         { // GR_1024
315                 220,            // x-left
316                 169,            // y-top
317                 553,            // width
318                 26,             //      height
319                 496             // center
320         }
321 };
322
323 static int Button_regions[GR_NUM_RESOLUTIONS][3][4] = {
324         { // GR_640             
325                 {464, 232, 510, 250},           // upper right pixel of text, lower right pixel of button
326                 {464, 262, 510, 279},           
327                 {464, 292, 510, 308}            
328         },
329         { // GR_1024
330                 {752, 373, 806, 406},           // upper right pixel of text, lower right pixel of button
331                 {752, 421, 806, 461},           
332                 {752, 468, 806, 506}            
333         }
334 };
335
336 static int Button_coords[GR_NUM_RESOLUTIONS][3][2] =
337 {
338         { // GR_640
339                 {474, 224},             // upper left pixel
340                 {474, 258},
341                 {474, 286}              
342         },
343         { // GR_1024
344                 {758, 358},             // upper left pixel
345                 {758, 413},
346                 {758, 458},             
347         }
348 };
349
350 static popup_background Popup_background[GR_NUM_RESOLUTIONS][4] = 
351 {
352         { // GR_640
353                 {"Pop2",                        129, 99},
354                 {"Pop2",                        129, 99},
355                 {"Pop3",                        129, 99},               
356         },
357         { // GR_1024
358                 {"2_Pop2",              206, 158},
359                 {"2_Pop2",              206, 158},
360                 {"2_Pop3",              206, 158},              
361         }
362 };
363
364 #define BUTTON_NEGATIVE                         0
365 #define BUTTON_POSITIVE                         1
366 #define BUTTON_GENERIC_FIRST            2
367 #define BUTTON_GENERIC_SECOND           3
368 #define BUTTON_GENERIC_THIRD            4
369 static char *Popup_button_filenames[GR_NUM_RESOLUTIONS][2][5] = 
370 {
371         { // GR_640
372                 {"Pop_00",                              // negative
373                 "Pop_01",                               // positive
374                 "Pop_02",                               // first generic
375                 "Pop_03",                               // second generic
376                 "Pop_04"},                              // third generic
377
378                 {"Pop_00",                              // negative
379                 "Pop_01",                               // positive
380                 "PopD_00",                              // first generic
381                 "PopD_01",                              // second generic
382                 "PopD_02"},                             // third generic
383         },
384         { // GR_1024
385                 {"2_Pop_00",                    // negative
386                 "2_Pop_01",                             // positive
387                 "2_Pop_02",                             // first generic
388                 "2_Pop_03",                             // second generic
389                 "2_Pop_04"},                    // third generic
390
391                 {"2_Pop_00",                    // negative
392                 "2_Pop_01",                             // positive
393                 "2_PopD_00",                    // first generic
394                 "2_PopD_01",                    // second generic
395                 "2_PopD_02"},                   // third generic
396         }
397 };
398
399 int Popup_running_state;
400 int Popup_default_choice;       // which choice is highlighted (ie what gets choosen when enter is pressed)
401
402 // see if any popup buttons have been pressed
403 // exit: POPUP_NOCHANGE         => no buttons pressed
404 //                      >=0                                     =>      button index that was pressed
405 int popup_check_buttons(popup_info *pi)
406 {
407         int                     i;
408         UI_BUTTON       *b;
409
410         for ( i = 0; i < pi->nchoices; i++ ) {
411                 b = &Popup_button_regions[i];
412                 if ( b->pressed() ) {
413                         return i;
414                 }
415
416                 b = &Popup_buttons[i];
417                 if ( b->pressed() ) {
418                         return i;
419                 }
420         }
421
422         return POPUP_NOCHANGE;
423 }
424
425 // maybe play a sound when key up/down is pressed to switch default choice
426 void popup_play_default_change_sound(popup_info *pi)
427 {
428         if ( pi->nchoices > 1 ) {
429                 int i, mouse_over=0;
430                 UI_BUTTON *br, *b;
431
432                 // only play if mouse not currently highlighting a choice
433
434                 for ( i = 0; i < pi->nchoices; i++ ) {
435                         br = &Popup_button_regions[i];
436                         b = &Popup_buttons[i];
437                         if ( br->button_down() ) {
438                                 mouse_over=1;
439                                 break;
440                         }
441
442                         if ( br->button_hilighted() && !b->button_down() ) {
443                                 mouse_over=1;
444                                 break;
445                         }
446
447                         if ( b->button_hilighted() ) {
448                                 mouse_over=1;
449                         }
450                 }
451
452                 if (!mouse_over) {
453                         gamesnd_play_iface(SND_USER_SELECT);
454                 }
455         }
456 }
457
458 // do any key processing here
459 // input:       pi                                      =>      data about the popup
460 //                              k                                       => key that was pressed
461 //
462 // exit: 0 .. nchoices-1        => choice selected through keypress
463 //                      POPUP_ABORT                     =>      abort the popup
464 //                      POPUP_NOCHANGE          => nothing happenned
465 int popup_process_keys(popup_info *pi, int k, int flags)
466 {
467         int i, masked_k;
468
469         if ( k <= 0 ) {
470                 return POPUP_NOCHANGE;
471         }
472
473         for ( i = 0; i < pi->nchoices; i++ ) {
474                 if ( pi->keypress[i] == key_to_ascii(k) ) {
475                         Popup_default_choice=i;
476                         Popup_buttons[i].press_button();
477                         return i;
478                 }
479         }
480         
481         switch(k) {
482
483         case KEY_ENTER:
484                 // select the current default choice
485                 return Popup_default_choice;
486                 break;
487
488         case KEY_ESC:
489                 // only process the escape key if this flag is not set
490                 if(!(flags & PF_IGNORE_ESC)){
491                         return POPUP_ABORT;
492                 }
493                 break;
494
495         case KEY_DOWN:
496         case KEY_PAD2:
497         case KEY_TAB:
498                 popup_play_default_change_sound(pi);
499                 Popup_default_choice++;
500                 if ( Popup_default_choice >= pi->nchoices ) {
501                         Popup_default_choice=0;
502                 }
503                 break;
504
505         case KEY_UP:
506         case KEY_PAD8:
507         case KEY_SHIFTED+KEY_TAB:
508                 popup_play_default_change_sound(pi);
509                 Popup_default_choice--;
510                 if ( Popup_default_choice < 0 ) {
511                         Popup_default_choice=pi->nchoices-1;
512                 }
513                 break;
514
515         default:
516                 break;
517         } // end switch
518
519
520         masked_k = k & ~KEY_CTRLED;     // take out CTRL modifier only
521         if ( (PF_ALLOW_DEAD_KEYS) && (Game_mode & GM_IN_MISSION) ) {
522                 process_set_of_keys(masked_k, Dead_key_set_size, Dead_key_set);
523                 button_info_do(&Player->bi);    // call functions based on status of button_info bit vectors
524         }
525
526         return POPUP_NOCHANGE;
527 }
528
529 // Split off the title and break up the body lines
530 void popup_split_lines(popup_info *pi, int flags)
531 {
532         int     nlines, i, body_offset = 0;
533         int     n_chars[POPUP_MAX_LINES];
534         char    *p_str[POPUP_MAX_LINES];
535
536         gr_set_font(FONT1);
537         n_chars[0]=0;
538
539         nlines = split_str(pi->raw_text, 1000, n_chars, p_str, POPUP_MAX_LINES);
540         Assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
541
542         if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
543                 // get first line out
544                 strncpy(pi->title, p_str[0], n_chars[0]);
545                 pi->title[n_chars[0]] = 0;
546                 body_offset = 1;
547         }
548
549         if ( flags & PF_BODY_BIG ) {
550                 gr_set_font(FONT2);
551         }
552
553         nlines = split_str(pi->raw_text, Popup_text_coords[gr_screen.res][2], n_chars, p_str, POPUP_MAX_LINES);
554         Assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
555
556         pi->nlines = nlines - body_offset;
557
558         for ( i = 0; i < pi->nlines; i++ ) {
559                 Assert(n_chars[i+body_offset] < POPUP_MAX_LINE_CHARS);
560                 strncpy(pi->msg_lines[i], p_str[i+body_offset], n_chars[i+body_offset]);
561                 pi->msg_lines[i][n_chars[i+body_offset]] = 0;
562         }
563
564         gr_set_font(FONT1);
565 }
566
567 // figure out what filename to use for the button icon
568 char *popup_get_button_filename(popup_info *pi, int i, int flags)
569 {
570         char *fname = NULL;
571         int is_tiny=0;  
572
573         // check for special button texts and if found, use specialized buttons for them.
574         if ((!stricmp(pi->button_text[i], POPUP_OK + 1) || !stricmp(pi->button_text[i], POPUP_YES + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
575                 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
576         }
577
578         if ((!stricmp(pi->button_text[i], POPUP_CANCEL + 1) || !stricmp(pi->button_text[i], POPUP_NO + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
579                 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
580         }
581
582         switch (pi->nchoices) {
583         case 0:
584                 fname = "";
585                 break;
586         case 1:
587                 if ( (flags & PF_USE_AFFIRMATIVE_ICON) && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
588                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
589                 } else if ( flags & PF_USE_NEGATIVE_ICON && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
590                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
591                 } else {
592                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
593                 }
594                 break;
595
596         case 2:
597                 if ( flags & PF_USE_NEGATIVE_ICON && i==0 ) {
598                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
599                         break;
600                 } 
601
602                 if ( flags & PF_USE_AFFIRMATIVE_ICON && i==1 ) {
603                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
604                         break;
605                 } 
606
607                 if ( i == 0 ) {
608                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
609                 } else {
610                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
611                 }
612
613                 break;
614
615         case 3:
616                 switch(i) {
617                 case 0:
618                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
619                         break;
620                 case 1:
621                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
622                         break;
623                 case 2:
624                         fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_THIRD];
625                         break;
626                 }
627                 break;
628         }
629
630         return fname;
631 }
632
633 // bogus handling function for slider (we don't need it to do anything)
634 void popup_slider_bogus()
635 {
636 }
637
638 // init the Popup window
639 int popup_init(popup_info *pi, int flags)
640 {
641         int                                     i;
642         UI_BUTTON                       *b;
643         popup_background        *pbg;
644         char                                    *fname;
645
646         if(pi->nchoices == 0){
647                 pbg = &Popup_background[gr_screen.res][0];
648         } else {
649                 pbg = &Popup_background[gr_screen.res][pi->nchoices-1];
650         }
651
652         // anytime in single player, and multiplayer, not in mission, go ahead and stop time
653         if ( (Game_mode & GM_NORMAL) || ((Game_mode && GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) ){
654                 game_stop_time();
655         }
656
657         // create base window
658         Popup_window.create(pbg->coords[0], pbg->coords[1], Popup_text_coords[gr_screen.res][2]+100, Popup_text_coords[gr_screen.res][3]+50, 0);
659         Popup_window.set_foreground_bmap(pbg->filename);
660
661         // create buttons
662         for (i=0; i<pi->nchoices; i++) {
663                 b = &Popup_buttons[i];
664                 // accommodate single-choice positive icon being positioned differently
665                 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
666                         b->create(&Popup_window, "", Button_coords[gr_screen.res][i+1][0], Button_coords[gr_screen.res][i+1][1], 30, 25, 0, 1);
667                 } else {
668                         b->create(&Popup_window, "", Button_coords[gr_screen.res][i][0], Button_coords[gr_screen.res][i][1], 30, 25, 0, 1);
669                 }
670
671                 fname = popup_get_button_filename(pi, i, flags);
672                 b->set_bmaps(fname, 3, 0);
673                 b->set_highlight_action(common_play_highlight_sound);
674                 if ( pi->keypress[i] >= 0 ) {
675                         b->set_hotkey(pi->keypress[i]);
676                 }
677
678                 // create invisible buttons to detect mouse presses... can't use mask since button region is dynamically sized
679                 int lx, w, h;
680
681                 gr_get_string_size(&w, &h, pi->button_text[i]);
682                 lx = Button_regions[gr_screen.res][i][0] - w;
683                 b = &Popup_button_regions[i];   
684
685                 // accommodate single-choice positive icon being positioned differently
686                 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
687                         b->create(&Popup_window, "", lx, Button_regions[gr_screen.res][i+1][1], Button_regions[gr_screen.res][i+1][2]-lx, Button_regions[gr_screen.res][i+1][3]-Button_regions[gr_screen.res][i+1][1], 0, 1);
688                 } else {
689                         b->create(&Popup_window, "", lx, Button_regions[gr_screen.res][i][1], Button_regions[gr_screen.res][i][2]-lx, Button_regions[gr_screen.res][i][3]-Button_regions[gr_screen.res][i][1], 0, 1);
690                 }
691
692                 b->hide();
693         }
694
695         // webcursor setup
696         if (Web_cursor_bitmap >= 0) {
697                 if (flags & PF_WEB_CURSOR_1) {
698                         Popup_buttons[1].set_custom_cursor_bmap(Web_cursor_bitmap);
699                 }
700                 if (flags & PF_WEB_CURSOR_2) {
701                         Popup_buttons[2].set_custom_cursor_bmap(Web_cursor_bitmap);
702                 }
703         }
704
705         // if this is an input popup, create and center the popup
706         if(flags & PF_INPUT){
707                 Popup_input.create(&Popup_window, Popup_text_coords[gr_screen.res][0], pbg->coords[1] + Popup_input_y_offset[gr_screen.res], Popup_text_coords[gr_screen.res][2], pi->max_input_text_len, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_ESC_FOC | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_TEXT_CEN);
708                 Popup_input.set_focus();
709         }       
710         
711         Popup_default_choice=0;
712         Popup_should_die = 0;
713
714         if (flags & PF_RUN_STATE) {
715                 Popup_running_state = 1;
716         } else {
717                 Popup_running_state = 0;
718         }
719
720         popup_split_lines(pi, flags);
721
722         // create the popup slider (which we may not need to use
723         Popup_slider.create(&Popup_window, Popup_slider_coords[gr_screen.res][0], Popup_slider_coords[gr_screen.res][1], Popup_slider_coords[gr_screen.res][2], Popup_slider_coords[gr_screen.res][3], pi->nlines > Popup_max_display[gr_screen.res] ? pi->nlines - Popup_max_display[gr_screen.res] : 0,
724                                                                 Popup_slider_name[gr_screen.res], popup_slider_bogus, popup_slider_bogus, NULL);
725
726         return 0;
727 }
728
729 // called when a popup goes away
730 void popup_close(popup_info *pi,int screen)
731 {
732         int i;
733         
734         gamesnd_play_iface(SND_POPUP_DISAPPEAR);        // play sound when popup disappears
735
736         for (i=0; i<pi->nchoices; i++ ) {
737                 if ( pi->button_text[i] != NULL ) {
738                         free(pi->button_text[i]);
739                         pi->button_text[i] = NULL;
740                 }
741         }
742
743         if(screen >= 0){
744                 gr_free_screen(screen); 
745         }
746         Popup_window.destroy();
747         anim_ignore_next_frametime();                                   // to avoid skips in animation since next frametime is saturated
748         game_flush();
749
750         Popup_is_active = 0;
751         Popup_running_state = 0;
752
753         // anytime in single player, and multiplayer, not in mission, go ahead and stop time
754         if ( (Game_mode & GM_NORMAL) || ((Game_mode && GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) )
755                 game_start_time();
756 }
757
758 // set the popup text color
759 void popup_set_text_color(int flags)
760 {
761         if ( flags & PF_BODY_RED ) {
762                 gr_set_color_fast(&Color_red);
763                 return;
764         }
765
766         if ( flags & PF_BODY_GREEN ) {
767                 gr_set_color_fast(&Color_green);
768                 return;
769         }
770
771         if ( flags & PF_BODY_BLUE ) {
772                 gr_set_color_fast(&Color_blue);
773                 return;
774         }
775
776         gr_set_color_fast(&Color_bright_blue);
777 }
778
779 // set the popup text color
780 void popup_set_title_color(int flags)
781 {
782         if ( flags & PF_TITLE_RED ) {
783                 gr_set_color_fast(&Color_red);
784                 return;
785         }
786
787         if ( flags & PF_TITLE_GREEN ) {
788                 gr_set_color_fast(&Color_green);
789                 return;
790         }
791
792         if ( flags & PF_TITLE_BLUE ) {
793                 gr_set_color_fast(&Color_blue);
794                 return;
795         }
796
797         if ( flags & PF_TITLE_WHITE ) {
798                 gr_set_color_fast(&Color_bright_white);
799                 return;
800         }
801
802         gr_set_color_fast(&Color_bright_blue);
803 }
804
805 // Draw the title centered within the popup
806 void popup_draw_title(int sy, char *line, int flags)
807 {
808         int w, h, sx;
809
810         if ( flags & PF_TITLE_BIG ) {
811                 gr_set_font(FONT2);
812         } else {
813                 gr_set_font(FONT1);
814         }
815
816         gr_get_string_size(&w, &h, line);
817         sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
818
819         popup_set_title_color(flags);
820         gr_string(sx,sy,line);
821 }
822
823 // calculate the starting display index
824 int popup_calc_starting_index(popup_info *pi)
825 {
826         // we're basically ignoring any titles here. 
827         if(pi->nlines <= Popup_max_display[gr_screen.res]){
828                 return 0;
829         }
830
831         // otherwise, we want to see what item index the slider is on
832         return Popup_slider.get_currentItem();
833 }
834
835 // Figure out the y-coord to start drawing the popup text.  The text
836 // is centered vertically within the popup.
837 int popup_calc_starting_y(popup_info *pi, int flags)
838 {
839         int sy, total_h=0;
840         int num_lines = pi->nlines > Popup_max_display[gr_screen.res] ? Popup_max_display[gr_screen.res] : pi->nlines;
841
842         if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
843                 if ( flags & PF_TITLE_BIG ) {
844                         gr_set_font(FONT2);
845                 } else {
846                         gr_set_font(FONT1);
847                 }
848                 total_h += gr_get_font_height();
849         }
850
851         if ( flags & PF_BODY_BIG ) {
852                 gr_set_font(FONT2);
853         } else {
854                 gr_set_font(FONT1);
855         }
856
857         total_h += num_lines * gr_get_font_height();
858         sy = fl2i((Popup_text_coords[gr_screen.res][1] + Popup_text_coords[gr_screen.res][3]/2.0f) - total_h/2.0f + 0.5f);
859
860         // if this is an input style box, add in some y
861         if(flags & PF_INPUT){
862                 sy += Popup_input_text_y_offset[gr_screen.res];
863         }
864
865         return sy;
866 }
867
868 // Draw the message text nicely formatted in the popup
869 void popup_draw_msg_text(popup_info *pi, int flags)
870 {
871         int sx, sy, i, w, h;
872         int line_index;
873         int line_count;
874
875         // figure out the starting display 
876         line_index = popup_calc_starting_index(pi);
877
878         // figure out the starting y:
879         sy = popup_calc_starting_y(pi, flags);
880
881         // draw title if required
882         if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
883                 popup_draw_title(sy, pi->title, flags);
884                 sy += gr_get_font_height();
885         }
886
887         // draw body 
888         if ( flags & PF_BODY_BIG ) {
889                 gr_set_font(FONT2);
890         } else {
891                 gr_set_font(FONT1);
892         }
893
894         popup_set_text_color(flags);
895         line_count = 0;
896         for ( i = line_index; i < pi->nlines; i++, line_count++ ) {
897                 // if we've already displayed the max # of lines
898                 if(line_count >= Popup_max_display[gr_screen.res]){
899                         break;
900                 }
901
902                 gr_get_string_size(&w, &h, pi->msg_lines[i]);
903                 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
904                 gr_string(sx, sy + line_count * h, pi->msg_lines[i]);
905         }
906
907         // maybe draw "more"
908         h = 10;
909         if(i < pi->nlines){
910                 gr_set_color_fast(&Color_bright_red);
911                 gr_string(Title_coords[gr_screen.res][4], sy + (Popup_max_display[gr_screen.res]) * h, XSTR("More", 459));
912         }
913
914         gr_set_font(FONT1);     // reset back to regular font size
915 }
916
917 // Draw the button text nicely formatted in the popup
918 void popup_draw_button_text(popup_info *pi, int flags)
919 {
920         int w, h, i, sx, sy;
921
922         gr_set_color_fast(&Color_bright_blue);
923
924         for ( i=0; i < pi->nchoices; i++ ) {
925                 gr_get_string_size(&w, &h, pi->button_text[i]);
926
927                 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
928                         sx = Button_regions[gr_screen.res][i+1][0]-w;
929                         sy = Button_regions[gr_screen.res][i+1][1]+4;
930                 } else {
931                         sx = Button_regions[gr_screen.res][i][0]-w;
932                         sy = Button_regions[gr_screen.res][i][1]+4;
933                 }
934
935                 gr_string(sx, sy, pi->button_text[i]);
936
937                 // figure out where to draw underline char
938                 if ( pi->shortcut_index[i] > 0 ) {
939                         int     cut=pi->shortcut_index[i];
940                         char    save_char=pi->button_text[i][cut];
941                         pi->button_text[i][cut] = 0;
942                         gr_get_string_size(&w, &h, pi->button_text[i]);
943                         pi->button_text[i][cut] = save_char;
944                         sx += w;
945                 }
946                 
947                 if ( pi->shortcut_index[i] >= 0 ) {
948                         gr_printf(sx, sy, NOX("%c"), 95);
949                 }
950         }
951 }
952
953 // See if any of the button should change appearance based on mouse position
954 void popup_force_draw_buttons(popup_info *pi)
955 {
956         int i,mouse_is_highlighting=0;
957         UI_BUTTON *br, *b;
958
959         for ( i = 0; i < pi->nchoices; i++ ) {
960                 br = &Popup_button_regions[i];
961                 b = &Popup_buttons[i];
962                 if ( br->button_down() ) {
963                         b->draw_forced(2);
964                         mouse_is_highlighting=1;
965                         continue;
966                 }
967
968                 if ( (b->button_hilighted()) || (br->button_hilighted() && !b->button_down()) ) {
969                         Popup_default_choice=i;
970                         mouse_is_highlighting=1;
971                         b->draw_forced(1);
972                 }
973         }
974
975         // Only if mouse is not highlighting an option, let the default choice be drawn highlighted
976         if ( (!mouse_is_highlighting) && (pi->nchoices>1) ) {
977                 for ( i = 0; i < pi->nchoices; i++ ) {
978                         b = &Popup_buttons[i];
979                         // highlight the default choice
980                         if ( i == Popup_default_choice ) {
981                                 b->draw_forced(1);
982                         }
983                 }
984         }
985 }
986
987 // exit: -1                                             =>      error
988 //                      0..nchoices-1           => choice
989 int popup_do(popup_info *pi, int flags)
990 {
991         int screen_id, choice = -1, done = 0;
992
993         screen_id = gr_save_screen();
994
995         if ( popup_init(pi, flags) == -1 ){
996                 return -1;
997         }
998
999         while(!done) {
1000                 int k;
1001
1002                 os_poll();
1003
1004                 // if we were killed by a call to popup_kill_any_active(), kill the popup
1005                 if(Popup_should_die){
1006                         choice = -1;
1007                         break;
1008                 }
1009
1010                 // if we're flagged as should be running the state underneath, then do so
1011                 if(flags & PF_RUN_STATE){
1012                         game_do_state(gameseq_get_state());
1013                 }
1014                 // otherwise just run the common functions (for networking,etc)
1015                 else {
1016                         game_set_frametime(-1);
1017                         game_do_state_common(gameseq_get_state(),flags & PF_NO_NETWORKING);     // do stuff common to all states 
1018                 }
1019
1020                 k = Popup_window.process();                                             // poll for input, handle mouse
1021                 choice = popup_process_keys(pi, k, flags);
1022                 if ( choice != POPUP_NOCHANGE ) {
1023                         done=1;
1024                 }
1025
1026                 if ( !done ) {
1027                         choice = popup_check_buttons(pi);
1028                         if ( choice != POPUP_NOCHANGE ) {
1029                                 done=1;
1030                         }
1031                 }
1032
1033                 // don't draw anything 
1034                 if(!(flags & PF_RUN_STATE)){
1035                         gr_restore_screen(screen_id);
1036                 }
1037
1038                 // if this is an input popup, store the input text
1039                 if(flags & PF_INPUT){
1040                         Popup_input.get_text(pi->input_text);
1041                 }
1042
1043                 Popup_window.draw();
1044                 popup_force_draw_buttons(pi);
1045                 popup_draw_msg_text(pi, flags);
1046                 popup_draw_button_text(pi, flags);
1047                 gr_flip();
1048         }
1049
1050         popup_close(pi,screen_id);
1051         return choice;
1052 }
1053
1054 int popup_do_with_condition(popup_info *pi, int flags, int(*condition)())
1055 {
1056         int screen_id, choice = -1, done = 0;
1057         int test;
1058         screen_id = gr_save_screen();
1059         if ( popup_init(pi, flags) == -1 )
1060                 return -1;
1061
1062         while(!done) {
1063                 int k;
1064
1065                 os_poll();
1066                 
1067                 game_set_frametime(-1);
1068                 game_do_state_common(gameseq_get_state());      // do stuff common to all states 
1069                 gr_restore_screen(screen_id);
1070
1071                 // draw one frame first
1072                 Popup_window.draw();
1073                 popup_force_draw_buttons(pi);
1074                 popup_draw_msg_text(pi, flags);
1075                 popup_draw_button_text(pi, flags);
1076                 gr_flip();
1077
1078                 // test the condition function or process for the window
1079                 if ((test = condition()) > 0) {
1080                         done = 1;
1081                         choice = test;
1082                 } else {
1083                         k = Popup_window.process();                                             // poll for input, handle mouse
1084                         choice = popup_process_keys(pi, k, flags);
1085                         if ( choice != POPUP_NOCHANGE ) {
1086                                 done=1;
1087                         }
1088
1089                         if ( !done ) {
1090                                 choice = popup_check_buttons(pi);
1091                                 if ( choice != POPUP_NOCHANGE ) {
1092                                         done=1;
1093                                 }
1094                         }
1095                 }               
1096         }
1097
1098         popup_close(pi,screen_id);
1099         return choice;
1100 }
1101
1102
1103 // maybe assign a keyboard shortcut to this button
1104 // input:       pi              =>      popup information so far
1105 //                              i               =>      number of choice
1106 //                              str     => string for button press      
1107 void popup_maybe_assign_keypress(popup_info *pi, int n, char *str)
1108 {
1109         int i,j,len=0,next_char_is_shortcut=0;
1110
1111         pi->keypress[n]=-1;
1112         pi->shortcut_index[n]=-1;
1113
1114         len=strlen(str)+1;
1115
1116         pi->button_text[n] = (char*)malloc(len);
1117         memset(pi->button_text[n], 0, len);
1118
1119         j=0;
1120         // copy chars over, watching for underline meta-char '&'
1121         for (i=0; i<len-1; i++) {
1122                 if ( str[i] == '&' ) {
1123                         pi->shortcut_index[n]=i;
1124                         next_char_is_shortcut=1;
1125                 } else {
1126                         if ( next_char_is_shortcut ) {
1127                                 next_char_is_shortcut=0;
1128                                 char first_char_string[2];
1129                                 first_char_string[0]=str[i];
1130                                 first_char_string[1]=0;
1131                                 strlwr(first_char_string);
1132                                 pi->keypress[n] = first_char_string[0];
1133                         }
1134                         pi->button_text[n][j++]=str[i]; 
1135                 }
1136         }
1137 }
1138
1139 // input:       flags                   =>              flags                   =>              formatting specificatons (PF_...)
1140 //                              nchoices                =>              number of choices popup has
1141 //                              text_1          =>              text for first button
1142 //                              ...                     =>              
1143 //                              text_n          =>              text for last button
1144 //                              msg text                =>              text msg for popup (can be of form "%s",pl->text)
1145 //
1146 // exit: choice selected (0..nchoices-1)
1147 //                      will return -1 if there was an error or popup was aborted
1148 //
1149 // typical usage:
1150 //
1151 //      rval = popup(0, 2, POPUP_OK, POPUP_CANCEL, "Sorry %s, try again", pl->callsign);
1152 int popup(int flags, int nchoices, ... )
1153 {
1154         int                     i, choice;
1155         char                    *format, *s;
1156         va_list         args;   
1157
1158         if ( Popup_is_active ) {
1159                 Int3();         // should never happen
1160                 return -1;
1161         }
1162
1163         Popup_flags = flags;
1164
1165         Assert( nchoices > 0 && nchoices <= POPUP_MAX_CHOICES );
1166         Popup_info.nchoices = nchoices;
1167
1168         va_start(args, nchoices );
1169
1170         // get button text
1171         for (i=0; i<nchoices; i++ )     {
1172                 s = va_arg( args, char * );
1173                 Popup_info.button_text[i] = NULL;
1174                 popup_maybe_assign_keypress(&Popup_info, i, s);
1175         }
1176
1177         // get msg text
1178         format = va_arg( args, char * );
1179         Popup_info.raw_text[0] = 0;
1180         vsprintf(Popup_info.raw_text, format, args);
1181         va_end(args);
1182         Assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );
1183         
1184         gamesnd_play_iface(SND_POPUP_APPEAR);   // play sound when popup appears
1185
1186         Mouse_hidden = 0;
1187         Popup_is_active = 1;
1188
1189         choice = popup_do( &Popup_info, flags );
1190         switch(choice) {
1191         case POPUP_ABORT:
1192                 return -1;
1193         default:
1194                 return choice;
1195         } // end switch
1196 }
1197
1198 // determine if a popup is being drawn
1199 int popup_active()
1200 {
1201         return Popup_is_active;
1202 }
1203
1204 // This function displays a popup message box and every frame it checks the condition() function
1205 // which is passed in as an argument.
1206 // If the condition() returns TRUE, the message box ends itself.  This function returns whatever
1207 // the condition() did if the condition() occurred, and FALSE if the cancel button was pressed.
1208 int popup_till_condition(int (*condition)(), ...) 
1209 {
1210         int                     choice;
1211         char                    *format, *s;
1212         va_list         args;
1213         int flags = 0;          
1214
1215         if ( Popup_is_active ) {
1216                 Int3();         // should never happen
1217                 return -1;
1218         }
1219         //int nchoices = 1;
1220         Popup_info.nchoices = 1;
1221
1222         Popup_flags = 0;
1223
1224         va_start(args, condition );
1225
1226         // get button text
1227         s = va_arg( args, char * );
1228         Popup_info.button_text[0] = NULL;
1229         popup_maybe_assign_keypress(&Popup_info, 0, s);
1230
1231         // get msg text
1232         format = va_arg( args, char * );
1233         Popup_info.raw_text[0] = 0;
1234         vsprintf(Popup_info.raw_text, format, args);
1235         va_end(args);
1236         Popup_info.raw_text[POPUP_MAX_CHARS-1] = '\0';
1237                 
1238         gamesnd_play_iface(SND_POPUP_APPEAR);   // play sound when popup appears
1239
1240         Mouse_hidden = 0;
1241         Popup_is_active = 1;
1242
1243         choice = popup_do_with_condition( &Popup_info, flags, condition );
1244         switch(choice) {
1245         case POPUP_ABORT:
1246                 return 0;
1247         default:
1248                 return choice;
1249         } // end switch
1250 }
1251
1252 // popup to return the value from an input box
1253 char *popup_input(int flags, char *caption, int max_output_len)
1254 {
1255         if ( Popup_is_active ) {
1256                 Int3();         // should never happen
1257                 return NULL;
1258         }
1259
1260         // make it an inputbox type popup
1261         Popup_flags = flags;
1262         Popup_flags |= PF_INPUT;
1263
1264         // add a cancel button
1265         Popup_info.nchoices = 0;
1266         // popup_maybe_assign_keypress(&Popup_info, 0, "&Cancel");      
1267
1268         // get msg text
1269         Assert(caption != NULL);
1270         strcpy(Popup_info.raw_text, caption);   
1271         Assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );
1272
1273         // set input text length
1274         if((max_output_len > POPUP_INPUT_MAX_CHARS) || (max_output_len == -1)){
1275                 Popup_info.max_input_text_len = POPUP_INPUT_MAX_CHARS - 1;
1276         } else {
1277                 Popup_info.max_input_text_len = max_output_len;
1278         }
1279
1280         // zero the popup input text
1281         memset(Popup_info.input_text, 0, POPUP_INPUT_MAX_CHARS);
1282         
1283         gamesnd_play_iface(SND_POPUP_APPEAR);   // play sound when popup appears
1284
1285         Mouse_hidden = 0;
1286         Popup_is_active = 1;
1287
1288         // if the user cancelled
1289         if(popup_do(&Popup_info, Popup_flags) == POPUP_ABORT){
1290                 return NULL;
1291         }
1292         
1293         // otherwise return the
1294         return Popup_info.input_text;   
1295 }
1296
1297 int popup_running_state()
1298 {
1299         return Popup_running_state;
1300 }
1301
1302 // kill any active popup, forcing it to return -1 (similar to ESC)
1303 void popup_kill_any_active()
1304 {
1305         if(Popup_is_active){
1306                 Popup_should_die = 1;
1307         }
1308 }
1309
1310 // change the text inside of the popup 
1311 void popup_change_text(char *new_text)
1312 {
1313         // copy the raw text
1314         strncpy(Popup_info.raw_text,new_text,POPUP_MAX_CHARS);
1315
1316         // recalculate all display information
1317         popup_split_lines(&Popup_info,Popup_flags);
1318 }
1319