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