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