2 * $Logfile: /Freespace2/code/Popup/Popup.cpp $
7 * Code for displaying pop-up dialog boxes
10 * Revision 1.1 2002/05/03 03:28:10 root
14 * 12 10/14/99 2:00p Jefff
15 * added include for os_poll to fix build error
17 * 11 10/14/99 10:18a Daveb
18 * Fixed incorrect CD checking problem on standalone server.
20 * 10 8/16/99 9:50a Jefff
21 * added mouseover webcursor options to user-defined popup buttons.
23 * 9 8/11/99 5:47p Jefff
24 * fixed button bitmap loading
26 * 8 8/04/99 10:53a Dave
27 * Added title to the user tips popup.
29 * 7 8/02/99 9:13p Dave
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.
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.
40 * 4 2/11/99 3:08p Dave
41 * PXO refresh button. Very preliminary squad war support.
43 * 3 10/13/98 9:29a Dave
44 * Started neatening up freespace.h. Many variables renamed and
45 * reorganized. Added AlphaColors.[h,cpp]
47 * 2 10/07/98 10:53a Dave
50 * 1 10/07/98 10:51a Dave
52 * 36 5/11/98 11:39p Dave
55 * 35 3/22/98 10:59p Allender
56 * don't stop time in multiplayer when "in mission"
58 * 34 3/17/98 12:41a Lawrance
59 * Support \n's between title and body
61 * 33 3/12/98 4:11p Dave
62 * AL: Check that max popup lines isn't exceeded
64 * 32 2/22/98 12:19p John
65 * Externalized some strings
67 * 31 2/10/98 3:26p Hoffoss
68 * Don't check for 'dead key set' unless in the mission.
70 * 30 2/05/98 11:21p Lawrance
71 * move death popup to below letterbox view
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.
77 * 28 2/03/98 11:52p Lawrance
78 * Don't highlight default choice if there is only one.
80 * 27 2/03/98 8:18p Dave
81 * More MT stats transfer stuff
83 * 26 1/30/98 3:05p Lawrance
84 * reposition text on death popup
86 * 25 1/29/98 6:55p Lawrance
87 * Integrate new art for the death popup
89 * 24 1/28/98 6:24p Dave
90 * Made standalone use ~8 megs less memory. Fixed multiplayer submenu
93 * 23 1/27/98 5:52p Lawrance
94 * support new "tiny" popups
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.
101 * 21 1/27/98 3:25p Hoffoss
102 * Made popups use the correct button icons for default positive and
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.
109 * 19 1/23/98 12:01a Lawrance
110 * Fix bug when in single-choice popups.
112 * 18 1/22/98 10:45p Lawrance
113 * Implment default selections and arrow key navigation.
115 * 17 1/20/98 5:52p Lawrance
116 * center popup text in X and Y directions. Support single affirmative
117 * icon special placement.
119 * 16 1/19/98 11:37p Lawrance
120 * Fixing Optimization build warnings
122 * 15 1/17/98 10:04p Lawrance
123 * fix errors in popup comments
125 * 14 1/15/98 2:52p Lawrance
126 * Fix problem with reading bogus buttons.
128 * 13 1/14/98 6:55p Dave
129 * Fixed a slew of multiplayer bugs. Made certain important popups ignore
130 * the escape character.
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.
137 * 11 1/14/98 12:23p Lawrance
138 * Support for three choice popups.
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
145 * 9 1/13/98 4:06p Lawrance
146 * Add underline char for shortcuts.
148 * 8 1/11/98 11:14p Lawrance
149 * Don't grey out background when popup is active.
151 * 7 1/08/98 10:32a Lawrance
152 * Grey out screen when a popup appears.
154 * 6 1/02/98 9:08p Lawrance
155 * Integrated art for popups, expanded options.
157 * 5 12/30/97 4:30p Sandeep
158 * Added conditional popups
160 * 4 12/26/97 10:01p Lawrance
161 * Allow keyboard shortcuts for popup buttons
163 * 3 12/24/97 9:49p Lawrance
164 * ensure mouse gets drawn when popup menu is up
166 * 2 12/24/97 8:54p Lawrance
167 * Integrating new popup code
169 * 1 12/24/97 3:51p Lawrance
176 #include "freespace.h"
177 #include "gamesequence.h"
184 #include "animplay.h"
185 #include "contexthelp.h"
186 #include "keycontrol.h"
189 #include "alphacolors.h"
192 #define POPUP_MAX_CHOICES 3 // max number of buttons allowed on popup
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
199 #define POPUP_NOCHANGE 100
200 #define POPUP_ABORT 101
202 int Popup_max_display[GR_NUM_RESOLUTIONS] = {
207 char *Popup_slider_name[GR_NUM_RESOLUTIONS] = {
212 int Popup_slider_coords[GR_NUM_RESOLUTIONS][4] = {
221 ////////////////////////////////////////////////////////////////
222 // Internal popup flags
223 ////////////////////////////////////////////////////////////////
224 #define PF_INPUT (1<<0) // contents of the box is an inputbox and a caption
226 ////////////////////////////////////////////////////////////////
228 ////////////////////////////////////////////////////////////////
229 typedef struct popup_info
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)
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
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
253 // extents for message portion of popup
254 int Popup_text_coords[GR_NUM_RESOLUTIONS][4] = {
264 // offset from the first y text line value to place the centered input box
265 int Popup_input_y_offset[GR_NUM_RESOLUTIONS] = {
270 // offset from the first y text line value to start drawing text
271 int Popup_input_text_y_offset[GR_NUM_RESOLUTIONS] = {
276 typedef struct popup_background
278 char *filename; // filename for background
279 int coords[2]; // coords to draw background at
282 ////////////////////////////////////////////////////////////////
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
288 static popup_info Popup_info;
289 static int Popup_flags;
291 static int Title_coords[GR_NUM_RESOLUTIONS][5] =
309 static int Button_regions[GR_NUM_RESOLUTIONS][3][4] = {
311 {464, 232, 510, 250}, // upper right pixel of text, lower right pixel of button
312 {464, 262, 510, 279},
316 {752, 373, 806, 406}, // upper right pixel of text, lower right pixel of button
317 {752, 421, 806, 461},
322 static int Button_coords[GR_NUM_RESOLUTIONS][3][2] =
325 {474, 224}, // upper left pixel
330 {758, 358}, // upper left pixel
336 static popup_background Popup_background[GR_NUM_RESOLUTIONS][4] =
344 {"2_Pop2", 206, 158},
345 {"2_Pop2", 206, 158},
346 {"2_Pop3", 206, 158},
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] =
358 {"Pop_00", // negative
359 "Pop_01", // positive
360 "Pop_02", // first generic
361 "Pop_03", // second generic
362 "Pop_04"}, // third generic
364 {"Pop_00", // negative
365 "Pop_01", // positive
366 "PopD_00", // first generic
367 "PopD_01", // second generic
368 "PopD_02"}, // third generic
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
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
385 int Popup_running_state;
386 int Popup_default_choice; // which choice is highlighted (ie what gets choosen when enter is pressed)
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)
396 for ( i = 0; i < pi->nchoices; i++ ) {
397 b = &Popup_button_regions[i];
398 if ( b->pressed() ) {
402 b = &Popup_buttons[i];
403 if ( b->pressed() ) {
408 return POPUP_NOCHANGE;
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)
414 if ( pi->nchoices > 1 ) {
418 // only play if mouse not currently highlighting a choice
420 for ( i = 0; i < pi->nchoices; i++ ) {
421 br = &Popup_button_regions[i];
422 b = &Popup_buttons[i];
423 if ( br->button_down() ) {
428 if ( br->button_hilighted() && !b->button_down() ) {
433 if ( b->button_hilighted() ) {
439 gamesnd_play_iface(SND_USER_SELECT);
444 // do any key processing here
445 // input: pi => data about the popup
446 // k => key that was pressed
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)
456 return POPUP_NOCHANGE;
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();
470 // select the current default choice
471 return Popup_default_choice;
475 // only process the escape key if this flag is not set
476 if(!(flags & PF_IGNORE_ESC)){
484 popup_play_default_change_sound(pi);
485 Popup_default_choice++;
486 if ( Popup_default_choice >= pi->nchoices ) {
487 Popup_default_choice=0;
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;
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
512 return POPUP_NOCHANGE;
515 // Split off the title and break up the body lines
516 void popup_split_lines(popup_info *pi, int flags)
518 int nlines, i, body_offset = 0;
519 int n_chars[POPUP_MAX_LINES];
520 char *p_str[POPUP_MAX_LINES];
525 nlines = split_str(pi->raw_text, 1000, n_chars, p_str, POPUP_MAX_LINES);
526 Assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
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;
535 if ( flags & PF_BODY_BIG ) {
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 );
542 pi->nlines = nlines - body_offset;
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;
553 // figure out what filename to use for the button icon
554 char *popup_get_button_filename(popup_info *pi, int i, int flags)
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];
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];
568 switch (pi->nchoices) {
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];
578 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
583 if ( flags & PF_USE_NEGATIVE_ICON && i==0 ) {
584 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
588 if ( flags & PF_USE_AFFIRMATIVE_ICON && i==1 ) {
589 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
594 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
596 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
604 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
607 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
610 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_THIRD];
619 // bogus handling function for slider (we don't need it to do anything)
620 void popup_slider_bogus()
624 // init the Popup window
625 int popup_init(popup_info *pi, int flags)
629 popup_background *pbg;
632 if(pi->nchoices == 0){
633 pbg = &Popup_background[gr_screen.res][0];
635 pbg = &Popup_background[gr_screen.res][pi->nchoices-1];
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)) ){
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);
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);
654 b->create(&Popup_window, "", Button_coords[gr_screen.res][i][0], Button_coords[gr_screen.res][i][1], 30, 25, 0, 1);
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]);
664 // create invisible buttons to detect mouse presses... can't use mask since button region is dynamically sized
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];
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);
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);
682 if (Web_cursor_bitmap >= 0) {
683 if (flags & PF_WEB_CURSOR_1) {
684 Popup_buttons[1].set_custom_cursor_bmap(Web_cursor_bitmap);
686 if (flags & PF_WEB_CURSOR_2) {
687 Popup_buttons[2].set_custom_cursor_bmap(Web_cursor_bitmap);
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();
697 Popup_default_choice=0;
698 Popup_should_die = 0;
700 if (flags & PF_RUN_STATE) {
701 Popup_running_state = 1;
703 Popup_running_state = 0;
706 popup_split_lines(pi, flags);
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);
715 // called when a popup goes away
716 void popup_close(popup_info *pi,int screen)
720 gamesnd_play_iface(SND_POPUP_DISAPPEAR); // play sound when popup disappears
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;
730 gr_free_screen(screen);
732 Popup_window.destroy();
733 anim_ignore_next_frametime(); // to avoid skips in animation since next frametime is saturated
737 Popup_running_state = 0;
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)) )
744 // set the popup text color
745 void popup_set_text_color(int flags)
747 if ( flags & PF_BODY_RED ) {
748 gr_set_color_fast(&Color_red);
752 if ( flags & PF_BODY_GREEN ) {
753 gr_set_color_fast(&Color_green);
757 if ( flags & PF_BODY_BLUE ) {
758 gr_set_color_fast(&Color_blue);
762 gr_set_color_fast(&Color_bright_blue);
765 // set the popup text color
766 void popup_set_title_color(int flags)
768 if ( flags & PF_TITLE_RED ) {
769 gr_set_color_fast(&Color_red);
773 if ( flags & PF_TITLE_GREEN ) {
774 gr_set_color_fast(&Color_green);
778 if ( flags & PF_TITLE_BLUE ) {
779 gr_set_color_fast(&Color_blue);
783 if ( flags & PF_TITLE_WHITE ) {
784 gr_set_color_fast(&Color_bright_white);
788 gr_set_color_fast(&Color_bright_blue);
791 // Draw the title centered within the popup
792 void popup_draw_title(int sy, char *line, int flags)
796 if ( flags & PF_TITLE_BIG ) {
802 gr_get_string_size(&w, &h, line);
803 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
805 popup_set_title_color(flags);
806 gr_string(sx,sy,line);
809 // calculate the starting display index
810 int popup_calc_starting_index(popup_info *pi)
812 // we're basically ignoring any titles here.
813 if(pi->nlines <= Popup_max_display[gr_screen.res]){
817 // otherwise, we want to see what item index the slider is on
818 return Popup_slider.get_currentItem();
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)
826 int num_lines = pi->nlines > Popup_max_display[gr_screen.res] ? Popup_max_display[gr_screen.res] : pi->nlines;
828 if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
829 if ( flags & PF_TITLE_BIG ) {
834 total_h += gr_get_font_height();
837 if ( flags & PF_BODY_BIG ) {
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);
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];
854 // Draw the message text nicely formatted in the popup
855 void popup_draw_msg_text(popup_info *pi, int flags)
861 // figure out the starting display
862 line_index = popup_calc_starting_index(pi);
864 // figure out the starting y:
865 sy = popup_calc_starting_y(pi, flags);
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();
874 if ( flags & PF_BODY_BIG ) {
880 popup_set_text_color(flags);
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]){
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]);
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));
900 gr_set_font(FONT1); // reset back to regular font size
903 // Draw the button text nicely formatted in the popup
904 void popup_draw_button_text(popup_info *pi, int flags)
908 gr_set_color_fast(&Color_bright_blue);
910 for ( i=0; i < pi->nchoices; i++ ) {
911 gr_get_string_size(&w, &h, pi->button_text[i]);
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;
917 sx = Button_regions[gr_screen.res][i][0]-w;
918 sy = Button_regions[gr_screen.res][i][1]+4;
921 gr_string(sx, sy, pi->button_text[i]);
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;
933 if ( pi->shortcut_index[i] >= 0 ) {
934 gr_printf(sx, sy, NOX("%c"), 95);
939 // See if any of the button should change appearance based on mouse position
940 void popup_force_draw_buttons(popup_info *pi)
942 int i,mouse_is_highlighting=0;
945 for ( i = 0; i < pi->nchoices; i++ ) {
946 br = &Popup_button_regions[i];
947 b = &Popup_buttons[i];
948 if ( br->button_down() ) {
950 mouse_is_highlighting=1;
954 if ( (b->button_hilighted()) || (br->button_hilighted() && !b->button_down()) ) {
955 Popup_default_choice=i;
956 mouse_is_highlighting=1;
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 ) {
974 // 0..nchoices-1 => choice
975 int popup_do(popup_info *pi, int flags)
977 int screen_id, choice = -1, done = 0;
979 screen_id = gr_save_screen();
981 if ( popup_init(pi, flags) == -1 ){
990 // if we were killed by a call to popup_kill_any_active(), kill the popup
991 if(Popup_should_die){
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());
1000 // otherwise just run the common functions (for networking,etc)
1002 game_set_frametime(-1);
1003 game_do_state_common(gameseq_get_state(),flags & PF_NO_NETWORKING); // do stuff common to all states
1006 k = Popup_window.process(); // poll for input, handle mouse
1007 choice = popup_process_keys(pi, k, flags);
1008 if ( choice != POPUP_NOCHANGE ) {
1013 choice = popup_check_buttons(pi);
1014 if ( choice != POPUP_NOCHANGE ) {
1019 // don't draw anything
1020 if(!(flags & PF_RUN_STATE)){
1021 gr_restore_screen(screen_id);
1024 // if this is an input popup, store the input text
1025 if(flags & PF_INPUT){
1026 Popup_input.get_text(pi->input_text);
1029 Popup_window.draw();
1030 popup_force_draw_buttons(pi);
1031 popup_draw_msg_text(pi, flags);
1032 popup_draw_button_text(pi, flags);
1036 popup_close(pi,screen_id);
1040 int popup_do_with_condition(popup_info *pi, int flags, int(*condition)())
1042 int screen_id, choice = -1, done = 0;
1044 screen_id = gr_save_screen();
1045 if ( popup_init(pi, flags) == -1 )
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);
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);
1064 // test the condition function or process for the window
1065 if ((test = condition()) > 0) {
1069 k = Popup_window.process(); // poll for input, handle mouse
1070 choice = popup_process_keys(pi, k, flags);
1071 if ( choice != POPUP_NOCHANGE ) {
1076 choice = popup_check_buttons(pi);
1077 if ( choice != POPUP_NOCHANGE ) {
1084 popup_close(pi,screen_id);
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)
1095 int i,j,len=0,next_char_is_shortcut=0;
1098 pi->shortcut_index[n]=-1;
1102 pi->button_text[n] = (char*)malloc(len);
1103 memset(pi->button_text[n], 0, len);
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;
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];
1120 pi->button_text[n][j++]=str[i];
1125 // input: flags => flags => formatting specificatons (PF_...)
1126 // nchoices => number of choices popup has
1127 // text_1 => text for first button
1129 // text_n => text for last button
1130 // msg text => text msg for popup (can be of form "%s",pl->text)
1132 // exit: choice selected (0..nchoices-1)
1133 // will return -1 if there was an error or popup was aborted
1137 // rval = popup(0, 2, POPUP_OK, POPUP_CANCEL, "Sorry %s, try again", pl->callsign);
1138 int popup(int flags, int nchoices, ... )
1144 if ( Popup_is_active ) {
1145 Int3(); // should never happen
1149 Popup_flags = flags;
1151 Assert( nchoices > 0 && nchoices <= POPUP_MAX_CHOICES );
1152 Popup_info.nchoices = nchoices;
1154 va_start(args, nchoices );
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);
1164 format = va_arg( args, char * );
1165 Popup_info.raw_text[0] = 0;
1166 vsprintf(Popup_info.raw_text, format, args);
1168 Assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );
1170 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1173 Popup_is_active = 1;
1175 choice = popup_do( &Popup_info, flags );
1184 // determine if a popup is being drawn
1187 return Popup_is_active;
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)(), ...)
1201 if ( Popup_is_active ) {
1202 Int3(); // should never happen
1206 Popup_info.nchoices = 1;
1210 va_start(args, condition );
1213 s = va_arg( args, char * );
1214 Popup_info.button_text[0] = NULL;
1215 popup_maybe_assign_keypress(&Popup_info, 0, s);
1218 format = va_arg( args, char * );
1219 Popup_info.raw_text[0] = 0;
1220 vsprintf(Popup_info.raw_text, format, args);
1222 Popup_info.raw_text[POPUP_MAX_CHARS-1] = '\0';
1224 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1227 Popup_is_active = 1;
1229 choice = popup_do_with_condition( &Popup_info, flags, condition );
1238 // popup to return the value from an input box
1239 char *popup_input(int flags, char *caption, int max_output_len)
1241 if ( Popup_is_active ) {
1242 Int3(); // should never happen
1246 // make it an inputbox type popup
1247 Popup_flags = flags;
1248 Popup_flags |= PF_INPUT;
1250 // add a cancel button
1251 Popup_info.nchoices = 0;
1252 // popup_maybe_assign_keypress(&Popup_info, 0, "&Cancel");
1255 Assert(caption != NULL);
1256 strcpy(Popup_info.raw_text, caption);
1257 Assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );
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;
1263 Popup_info.max_input_text_len = max_output_len;
1266 // zero the popup input text
1267 memset(Popup_info.input_text, 0, POPUP_INPUT_MAX_CHARS);
1269 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1272 Popup_is_active = 1;
1274 // if the user cancelled
1275 if(popup_do(&Popup_info, Popup_flags) == POPUP_ABORT){
1279 // otherwise return the
1280 return Popup_info.input_text;
1283 int popup_running_state()
1285 return Popup_running_state;
1288 // kill any active popup, forcing it to return -1 (similar to ESC)
1289 void popup_kill_any_active()
1291 if(Popup_is_active){
1292 Popup_should_die = 1;
1296 // change the text inside of the popup
1297 void popup_change_text(char *new_text)
1299 // copy the raw text
1300 strncpy(Popup_info.raw_text,new_text,POPUP_MAX_CHARS);
1302 // recalculate all display information
1303 popup_split_lines(&Popup_info,Popup_flags);