2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Popup/Popup.cpp $
15 * Code for displaying pop-up dialog boxes
18 * Revision 1.4 2003/05/25 02:30:43 taylor
21 * Revision 1.3 2002/06/09 04:41:25 relnev
22 * added copyright header
24 * Revision 1.2 2002/05/07 03:16:51 theoddone33
25 * The Great Newline Fix
27 * Revision 1.1.1.1 2002/05/03 03:28:10 root
31 * 12 10/14/99 2:00p Jefff
32 * added include for os_poll to fix build error
34 * 11 10/14/99 10:18a Daveb
35 * Fixed incorrect CD checking problem on standalone server.
37 * 10 8/16/99 9:50a Jefff
38 * added mouseover webcursor options to user-defined popup buttons.
40 * 9 8/11/99 5:47p Jefff
41 * fixed button bitmap loading
43 * 8 8/04/99 10:53a Dave
44 * Added title to the user tips popup.
46 * 7 8/02/99 9:13p Dave
49 * 6 6/18/99 5:16p Dave
50 * Added real beam weapon lighting. Fixed beam weapon sounds. Added MOTD
51 * dialog to PXO screen.
53 * 5 6/01/99 3:52p Dave
54 * View footage screen. Fixed xstrings to not display the & symbol. Popup,
55 * dead popup, pxo find player popup, pxo private room popup.
57 * 4 2/11/99 3:08p Dave
58 * PXO refresh button. Very preliminary squad war support.
60 * 3 10/13/98 9:29a Dave
61 * Started neatening up freespace.h. Many variables renamed and
62 * reorganized. Added AlphaColors.[h,cpp]
64 * 2 10/07/98 10:53a Dave
67 * 1 10/07/98 10:51a Dave
69 * 36 5/11/98 11:39p Dave
72 * 35 3/22/98 10:59p Allender
73 * don't stop time in multiplayer when "in mission"
75 * 34 3/17/98 12:41a Lawrance
76 * Support \n's between title and body
78 * 33 3/12/98 4:11p Dave
79 * AL: Check that max popup lines isn't exceeded
81 * 32 2/22/98 12:19p John
82 * Externalized some strings
84 * 31 2/10/98 3:26p Hoffoss
85 * Don't check for 'dead key set' unless in the mission.
87 * 30 2/05/98 11:21p Lawrance
88 * move death popup to below letterbox view
90 * 29 2/05/98 11:09a Dave
91 * Fixed an ingame join bug. Fixed a read-only file problem with
92 * multiplauer file xfer.
94 * 28 2/03/98 11:52p Lawrance
95 * Don't highlight default choice if there is only one.
97 * 27 2/03/98 8:18p Dave
98 * More MT stats transfer stuff
100 * 26 1/30/98 3:05p Lawrance
101 * reposition text on death popup
103 * 25 1/29/98 6:55p Lawrance
104 * Integrate new art for the death popup
106 * 24 1/28/98 6:24p Dave
107 * Made standalone use ~8 megs less memory. Fixed multiplayer submenu
108 * sequencing problem.
110 * 23 1/27/98 5:52p Lawrance
111 * support new "tiny" popups
113 * 22 1/27/98 5:01p Dave
114 * Put in support for leaving multiplayer games in the pause state. Fixed
115 * a popup bug which freed saved screens incorrectly. Reworked scoring
116 * kill and assist evaluation.
118 * 21 1/27/98 3:25p Hoffoss
119 * Made popups use the correct button icons for default positive and
122 * 20 1/26/98 6:28p Lawrance
123 * Use '&' meta char for underlining, change how keyboard usage works so
124 * fits better with mouse usage.
126 * 19 1/23/98 12:01a Lawrance
127 * Fix bug when in single-choice popups.
129 * 18 1/22/98 10:45p Lawrance
130 * Implment default selections and arrow key navigation.
132 * 17 1/20/98 5:52p Lawrance
133 * center popup text in X and Y directions. Support single affirmative
134 * icon special placement.
136 * 16 1/19/98 11:37p Lawrance
137 * Fixing Optimization build warnings
139 * 15 1/17/98 10:04p Lawrance
140 * fix errors in popup comments
142 * 14 1/15/98 2:52p Lawrance
143 * Fix problem with reading bogus buttons.
145 * 13 1/14/98 6:55p Dave
146 * Fixed a slew of multiplayer bugs. Made certain important popups ignore
147 * the escape character.
149 * 12 1/14/98 6:42p Hoffoss
150 * Massive changes to UI code. A lot cleaner and better now. Did all
151 * this to get the new UI_DOT_SLIDER to work properly, which the old code
152 * wasn't flexible enough to handle.
154 * 11 1/14/98 12:23p Lawrance
155 * Support for three choice popups.
157 * 10 1/13/98 5:37p Dave
158 * Reworked a lot of standalone interface code. Put in single and
159 * multiplayer popups for death sequence. Solidified multiplayer kick
162 * 9 1/13/98 4:06p Lawrance
163 * Add underline char for shortcuts.
165 * 8 1/11/98 11:14p Lawrance
166 * Don't grey out background when popup is active.
168 * 7 1/08/98 10:32a Lawrance
169 * Grey out screen when a popup appears.
171 * 6 1/02/98 9:08p Lawrance
172 * Integrated art for popups, expanded options.
174 * 5 12/30/97 4:30p Sandeep
175 * Added conditional popups
177 * 4 12/26/97 10:01p Lawrance
178 * Allow keyboard shortcuts for popup buttons
180 * 3 12/24/97 9:49p Lawrance
181 * ensure mouse gets drawn when popup menu is up
183 * 2 12/24/97 8:54p Lawrance
184 * Integrating new popup code
186 * 1 12/24/97 3:51p Lawrance
193 #include "freespace.h"
194 #include "gamesequence.h"
201 #include "animplay.h"
202 #include "contexthelp.h"
203 #include "keycontrol.h"
206 #include "alphacolors.h"
209 #define POPUP_MAX_CHOICES 3 // max number of buttons allowed on popup
211 #define POPUP_MAX_LINE_CHARS 256 // max chars of msg text allowed per line
212 #define POPUP_MAX_LINES 30 // max lines of text allowed
213 #define POPUP_MAX_CHARS 2048 // total max chars
214 #define POPUP_INPUT_MAX_CHARS 255 // max length of input string
216 #define POPUP_NOCHANGE 100
217 #define POPUP_ABORT 101
219 int Popup_max_display[GR_NUM_RESOLUTIONS] = {
224 #ifndef MAKE_FS1 // to avoid trying to find the interface tool
225 const char *Popup_slider_name[GR_NUM_RESOLUTIONS] = {
231 int Popup_slider_coords[GR_NUM_RESOLUTIONS][4] = {
240 ////////////////////////////////////////////////////////////////
241 // Internal popup flags
242 ////////////////////////////////////////////////////////////////
243 #define PF_INPUT (1<<0) // contents of the box is an inputbox and a caption
245 ////////////////////////////////////////////////////////////////
247 ////////////////////////////////////////////////////////////////
248 typedef struct popup_info
250 int nchoices; // number of choices user can pick
251 char *button_text[POPUP_MAX_CHOICES]; // button text
252 int keypress[POPUP_MAX_CHOICES]; // button keypress shortcut
253 int shortcut_index[POPUP_MAX_CHOICES]; // what char should be underlines for shortcut
254 char raw_text[POPUP_MAX_CHARS]; // the unbroken text for the popup
255 char title[POPUP_MAX_LINE_CHARS]; // title text for popup (optional)
257 char msg_lines[POPUP_MAX_LINES][POPUP_MAX_LINE_CHARS]; // lines of text in popup
258 char input_text[POPUP_INPUT_MAX_CHARS]; // input box text (if this is an inputbox popup)
259 int max_input_text_len;
260 int web_cursor_flag[POPUP_MAX_CHOICES]; // flag for using web cursor over button
263 ////////////////////////////////////////////////////////////////
264 // UI Data and constants
265 ////////////////////////////////////////////////////////////////
266 UI_WINDOW Popup_window;
267 UI_BUTTON Popup_buttons[POPUP_MAX_CHOICES]; // actual lit buttons
268 UI_BUTTON Popup_button_regions[POPUP_MAX_CHOICES]; // fake buttons used for mouse detection over text
269 UI_INPUTBOX Popup_input; // input box for the popup
271 UI_SLIDER2 Popup_slider; // if we have more text in the popup than can be displayed at once
274 // extents for message portion of popup
275 int Popup_text_coords[GR_NUM_RESOLUTIONS][4] = {
289 // offset from the first y text line value to place the centered input box
290 int Popup_input_y_offset[GR_NUM_RESOLUTIONS] = {
295 // offset from the first y text line value to start drawing text
296 int Popup_input_text_y_offset[GR_NUM_RESOLUTIONS] = {
301 typedef struct popup_background
303 const char *filename; // filename for background
304 int coords[2]; // coords to draw background at
307 ////////////////////////////////////////////////////////////////
309 ////////////////////////////////////////////////////////////////
310 static int Popup_is_active=0;
311 static int Popup_should_die=0; // popup should quit during the next iteration of its loop
313 static popup_info Popup_info;
314 static int Popup_flags;
316 static int Title_coords[GR_NUM_RESOLUTIONS][5] =
342 static int Button_regions[GR_NUM_RESOLUTIONS][3][4] = {
345 {464, 269, 505, 290}, // upper right pixel of text, lower right pixel of button
346 {464, 297, 505, 320},
349 {464, 232, 510, 250}, // upper right pixel of text, lower right pixel of button
350 {464, 262, 510, 279},
355 {752, 373, 806, 406}, // upper right pixel of text, lower right pixel of button
356 {752, 421, 806, 461},
361 static int Button_coords[GR_NUM_RESOLUTIONS][3][2] =
365 {474, 257}, // upper left pixel
369 {474, 224}, // upper left pixel
375 {758, 358}, // upper left pixel
381 static popup_background Popup_background[GR_NUM_RESOLUTIONS][4] =
385 { "Pop2a", { 131, 122} },
386 { "Pop2a", { 131, 122 } },
387 { "Pop3", { 131, 122 } },
389 { "Pop2", { 129, 99 } },
390 { "Pop2", { 129, 99 } },
391 { "Pop3", { 129, 99 } },
395 { "2_Pop2", { 206, 158 } },
396 { "2_Pop2", { 206, 158 } },
397 { "2_Pop3", { 206, 158 } },
401 #define BUTTON_NEGATIVE 0
402 #define BUTTON_POSITIVE 1
403 #define BUTTON_GENERIC_FIRST 2
404 #define BUTTON_GENERIC_SECOND 3
405 #define BUTTON_GENERIC_THIRD 4
406 static const char *Popup_button_filenames[GR_NUM_RESOLUTIONS][2][5] =
410 {"Pop2a_00", // negative
411 "Pop2a_01", // positive
412 "Pop2a_02", // first generic
413 "Pop2a_03", // second generic
414 "Pop2a_04"}, // third generic
416 {"Pop2a_00", // negative
417 "Pop2a_01", // positive
418 "PopD_00", // first generic
419 "PopD_01", // second generic
420 "PopD_02"}, // third generic
422 {"Pop_00", // negative
423 "Pop_01", // positive
424 "Pop_02", // first generic
425 "Pop_03", // second generic
426 "Pop_04"}, // third generic
428 {"Pop_00", // negative
429 "Pop_01", // positive
430 "PopD_00", // first generic
431 "PopD_01", // second generic
432 "PopD_02"}, // third generic
436 {"2_Pop_00", // negative
437 "2_Pop_01", // positive
438 "2_Pop_02", // first generic
439 "2_Pop_03", // second generic
440 "2_Pop_04"}, // third generic
442 {"2_Pop_00", // negative
443 "2_Pop_01", // positive
444 "2_PopD_00", // first generic
445 "2_PopD_01", // second generic
446 "2_PopD_02"}, // third generic
450 int Popup_running_state;
451 int Popup_default_choice; // which choice is highlighted (ie what gets choosen when enter is pressed)
453 // see if any popup buttons have been pressed
454 // exit: POPUP_NOCHANGE => no buttons pressed
455 // >=0 => button index that was pressed
456 int popup_check_buttons(popup_info *pi)
461 for ( i = 0; i < pi->nchoices; i++ ) {
462 b = &Popup_button_regions[i];
463 if ( b->pressed() ) {
467 b = &Popup_buttons[i];
468 if ( b->pressed() ) {
473 return POPUP_NOCHANGE;
476 // maybe play a sound when key up/down is pressed to switch default choice
477 void popup_play_default_change_sound(popup_info *pi)
479 if ( pi->nchoices > 1 ) {
483 // only play if mouse not currently highlighting a choice
485 for ( i = 0; i < pi->nchoices; i++ ) {
486 br = &Popup_button_regions[i];
487 b = &Popup_buttons[i];
488 if ( br->button_down() ) {
493 if ( br->button_hilighted() && !b->button_down() ) {
498 if ( b->button_hilighted() ) {
504 gamesnd_play_iface(SND_USER_SELECT);
509 // do any key processing here
510 // input: pi => data about the popup
511 // k => key that was pressed
513 // exit: 0 .. nchoices-1 => choice selected through keypress
514 // POPUP_ABORT => abort the popup
515 // POPUP_NOCHANGE => nothing happenned
516 int popup_process_keys(popup_info *pi, int k, int flags)
521 return POPUP_NOCHANGE;
524 for ( i = 0; i < pi->nchoices; i++ ) {
525 if ( pi->keypress[i] == key_get_text_input() ) {
526 Popup_default_choice=i;
527 Popup_buttons[i].press_button();
535 // select the current default choice
536 return Popup_default_choice;
540 // only process the escape key if this flag is not set
541 if(!(flags & PF_IGNORE_ESC)){
549 popup_play_default_change_sound(pi);
550 Popup_default_choice++;
551 if ( Popup_default_choice >= pi->nchoices ) {
552 Popup_default_choice=0;
558 case KEY_SHIFTED+SDLK_TAB:
559 popup_play_default_change_sound(pi);
560 Popup_default_choice--;
561 if ( Popup_default_choice < 0 ) {
562 Popup_default_choice=pi->nchoices-1;
571 masked_k = k & ~KEY_CTRLED; // take out CTRL modifier only
572 if ( (PF_ALLOW_DEAD_KEYS) && (Game_mode & GM_IN_MISSION) ) {
573 process_set_of_keys(masked_k, Dead_key_set_size, Dead_key_set);
574 button_info_do(&Player->bi); // call functions based on status of button_info bit vectors
577 return POPUP_NOCHANGE;
580 // Split off the title and break up the body lines
581 void popup_split_lines(popup_info *pi, int flags)
583 int nlines, i, body_offset = 0;
584 int n_chars[POPUP_MAX_LINES];
585 char *p_str[POPUP_MAX_LINES];
591 nlines = split_str(pi->raw_text, 1000, n_chars, p_str, POPUP_MAX_LINES);
592 SDL_assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
594 if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
595 // get first line out
596 len = SDL_min(n_chars[0] + 1, POPUP_MAX_LINE_CHARS);
597 SDL_strlcpy(pi->title, p_str[0], len);
601 if ( flags & PF_BODY_BIG ) {
605 nlines = split_str(pi->raw_text, Popup_text_coords[gr_screen.res][2], n_chars, p_str, POPUP_MAX_LINES);
606 SDL_assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
608 pi->nlines = nlines - body_offset;
610 for ( i = 0; i < pi->nlines; i++ ) {
611 SDL_assert(n_chars[i+body_offset] < POPUP_MAX_LINE_CHARS);
612 len = SDL_min(n_chars[i+body_offset] + 1, POPUP_MAX_LINE_CHARS);
613 SDL_strlcpy(pi->msg_lines[i], p_str[i+body_offset], len);
619 // figure out what filename to use for the button icon
620 const char *popup_get_button_filename(popup_info *pi, int i, int flags)
622 const char *fname = NULL;
625 // check for special button texts and if found, use specialized buttons for them.
626 if ((!SDL_strcasecmp(pi->button_text[i], POPUP_OK + 1) || !SDL_strcasecmp(pi->button_text[i], POPUP_YES + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
627 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
630 if ((!SDL_strcasecmp(pi->button_text[i], POPUP_CANCEL + 1) || !SDL_strcasecmp(pi->button_text[i], POPUP_NO + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
631 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
634 switch (pi->nchoices) {
639 if ( (flags & PF_USE_AFFIRMATIVE_ICON) && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
640 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
641 } else if ( flags & PF_USE_NEGATIVE_ICON && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
642 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
644 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
649 if ( flags & PF_USE_NEGATIVE_ICON && i==0 ) {
650 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
654 if ( flags & PF_USE_AFFIRMATIVE_ICON && i==1 ) {
655 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
660 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
662 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
670 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
673 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
676 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_THIRD];
685 // bogus handling function for slider (we don't need it to do anything)
686 void popup_slider_bogus()
690 // init the Popup window
691 int popup_init(popup_info *pi, int flags)
695 popup_background *pbg;
698 if(pi->nchoices == 0){
699 pbg = &Popup_background[gr_screen.res][0];
701 pbg = &Popup_background[gr_screen.res][pi->nchoices-1];
704 // anytime in single player, and multiplayer, not in mission, go ahead and stop time
705 if ( (Game_mode & GM_NORMAL) || ((Game_mode & GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) ){
709 // create base window
710 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);
711 Popup_window.set_foreground_bmap(pbg->filename);
714 for (i=0; i<pi->nchoices; i++) {
715 b = &Popup_buttons[i];
716 // accommodate single-choice positive icon being positioned differently
717 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
718 b->create(&Popup_window, "", Button_coords[gr_screen.res][i+1][0], Button_coords[gr_screen.res][i+1][1], 30, 25, 0, 1);
720 b->create(&Popup_window, "", Button_coords[gr_screen.res][i][0], Button_coords[gr_screen.res][i][1], 30, 25, 0, 1);
723 fname = popup_get_button_filename(pi, i, flags);
724 b->set_bmaps(fname, 3, 0);
725 b->set_highlight_action(common_play_highlight_sound);
726 if ( pi->keypress[i] >= 0 ) {
727 b->set_hotkey(pi->keypress[i]);
730 // create invisible buttons to detect mouse presses... can't use mask since button region is dynamically sized
733 gr_get_string_size(&w, &h, pi->button_text[i]);
734 lx = Button_regions[gr_screen.res][i][0] - w;
735 b = &Popup_button_regions[i];
737 // accommodate single-choice positive icon being positioned differently
738 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
739 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);
741 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);
748 if (Web_cursor_bitmap >= 0) {
749 if (flags & PF_WEB_CURSOR_1) {
750 Popup_buttons[1].set_custom_cursor_bmap(Web_cursor_bitmap);
752 if (flags & PF_WEB_CURSOR_2) {
753 Popup_buttons[2].set_custom_cursor_bmap(Web_cursor_bitmap);
757 // if this is an input popup, create and center the popup
758 if(flags & PF_INPUT){
759 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);
760 Popup_input.set_focus();
763 Popup_default_choice=0;
764 Popup_should_die = 0;
766 if (flags & PF_RUN_STATE) {
767 Popup_running_state = 1;
769 Popup_running_state = 0;
772 popup_split_lines(pi, flags);
775 // create the popup slider (which we may not need to use
776 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,
777 Popup_slider_name[gr_screen.res], popup_slider_bogus, popup_slider_bogus, NULL);
783 // called when a popup goes away
784 void popup_close(popup_info *pi,int screen)
788 gamesnd_play_iface(SND_POPUP_DISAPPEAR); // play sound when popup disappears
790 for (i=0; i<pi->nchoices; i++ ) {
791 if ( pi->button_text[i] != NULL ) {
792 free(pi->button_text[i]);
793 pi->button_text[i] = NULL;
798 gr_free_screen(screen);
800 Popup_window.destroy();
801 anim_ignore_next_frametime(); // to avoid skips in animation since next frametime is saturated
805 Popup_running_state = 0;
807 // anytime in single player, and multiplayer, not in mission, go ahead and stop time
808 if ( (Game_mode & GM_NORMAL) || ((Game_mode & GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) )
812 // set the popup text color
813 void popup_set_text_color(int flags)
815 if ( flags & PF_BODY_RED ) {
816 gr_set_color_fast(&Color_red);
820 if ( flags & PF_BODY_GREEN ) {
821 gr_set_color_fast(&Color_green);
825 if ( flags & PF_BODY_BLUE ) {
826 gr_set_color_fast(&Color_blue);
830 gr_set_color_fast(&Color_bright_blue);
833 // set the popup text color
834 void popup_set_title_color(int flags)
836 if ( flags & PF_TITLE_RED ) {
837 gr_set_color_fast(&Color_red);
841 if ( flags & PF_TITLE_GREEN ) {
842 gr_set_color_fast(&Color_green);
846 if ( flags & PF_TITLE_BLUE ) {
847 gr_set_color_fast(&Color_blue);
851 if ( flags & PF_TITLE_WHITE ) {
852 gr_set_color_fast(&Color_bright_white);
856 gr_set_color_fast(&Color_bright_blue);
859 // Draw the title centered within the popup
860 void popup_draw_title(int sy, const char *line, int flags)
864 if ( flags & PF_TITLE_BIG ) {
870 gr_get_string_size(&w, &h, line);
871 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
873 popup_set_title_color(flags);
874 gr_string(sx,sy,line);
877 // calculate the starting display index
878 int popup_calc_starting_index(popup_info *pi)
880 // we're basically ignoring any titles here.
881 if(pi->nlines <= Popup_max_display[gr_screen.res]){
886 // otherwise, we want to see what item index the slider is on
887 return Popup_slider.get_currentItem();
893 // Figure out the y-coord to start drawing the popup text. The text
894 // is centered vertically within the popup.
895 int popup_calc_starting_y(popup_info *pi, int flags)
898 int num_lines = pi->nlines > Popup_max_display[gr_screen.res] ? Popup_max_display[gr_screen.res] : pi->nlines;
900 if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
901 if ( flags & PF_TITLE_BIG ) {
906 total_h += gr_get_font_height();
909 if ( flags & PF_BODY_BIG ) {
915 total_h += num_lines * gr_get_font_height();
916 sy = fl2i((Popup_text_coords[gr_screen.res][1] + Popup_text_coords[gr_screen.res][3]/2.0f) - total_h/2.0f + 0.5f);
918 // if this is an input style box, add in some y
919 if(flags & PF_INPUT){
920 sy += Popup_input_text_y_offset[gr_screen.res];
926 // Draw the message text nicely formatted in the popup
927 void popup_draw_msg_text(popup_info *pi, int flags)
933 // figure out the starting display
934 line_index = popup_calc_starting_index(pi);
936 // figure out the starting y:
937 sy = popup_calc_starting_y(pi, flags);
939 // draw title if required
940 if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
941 popup_draw_title(sy, pi->title, flags);
942 sy += gr_get_font_height();
946 if ( flags & PF_BODY_BIG ) {
952 popup_set_text_color(flags);
954 for ( i = line_index; i < pi->nlines; i++, line_count++ ) {
955 // if we've already displayed the max # of lines
956 if(line_count >= Popup_max_display[gr_screen.res]){
960 gr_get_string_size(&w, &h, pi->msg_lines[i]);
961 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
962 gr_string(sx, sy + line_count * h, pi->msg_lines[i]);
968 gr_set_color_fast(&Color_bright_red);
969 gr_string(Title_coords[gr_screen.res][4], sy + (Popup_max_display[gr_screen.res]) * h, XSTR("More", 459));
972 gr_set_font(FONT1); // reset back to regular font size
975 // Draw the button text nicely formatted in the popup
976 void popup_draw_button_text(popup_info *pi, int flags)
980 gr_set_color_fast(&Color_bright_blue);
982 for ( i=0; i < pi->nchoices; i++ ) {
983 gr_get_string_size(&w, &h, pi->button_text[i]);
985 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
986 sx = Button_regions[gr_screen.res][i+1][0]-w;
987 sy = Button_regions[gr_screen.res][i+1][1]+4;
989 sx = Button_regions[gr_screen.res][i][0]-w;
990 sy = Button_regions[gr_screen.res][i][1]+4;
993 gr_string(sx, sy, pi->button_text[i]);
995 // figure out where to draw underline char
996 if ( pi->shortcut_index[i] > 0 ) {
997 int cut=pi->shortcut_index[i];
998 char save_char=pi->button_text[i][cut];
999 pi->button_text[i][cut] = 0;
1000 gr_get_string_size(&w, &h, pi->button_text[i]);
1001 pi->button_text[i][cut] = save_char;
1005 if ( pi->shortcut_index[i] >= 0 ) {
1006 gr_printf(sx, sy, NOX("%c"), 95);
1011 // See if any of the button should change appearance based on mouse position
1012 void popup_force_draw_buttons(popup_info *pi)
1014 int i,mouse_is_highlighting=0;
1017 for ( i = 0; i < pi->nchoices; i++ ) {
1018 br = &Popup_button_regions[i];
1019 b = &Popup_buttons[i];
1020 if ( br->button_down() ) {
1022 mouse_is_highlighting=1;
1026 if ( (b->button_hilighted()) || (br->button_hilighted() && !b->button_down()) ) {
1027 Popup_default_choice=i;
1028 mouse_is_highlighting=1;
1033 // Only if mouse is not highlighting an option, let the default choice be drawn highlighted
1034 if ( (!mouse_is_highlighting) && (pi->nchoices>1) ) {
1035 for ( i = 0; i < pi->nchoices; i++ ) {
1036 b = &Popup_buttons[i];
1037 // highlight the default choice
1038 if ( i == Popup_default_choice ) {
1045 // exit: -1 => error
1046 // 0..nchoices-1 => choice
1047 int popup_do(popup_info *pi, int flags)
1049 int screen_id, choice = -1, done = 0;
1051 screen_id = gr_save_screen();
1053 if ( popup_init(pi, flags) == -1 ){
1062 // if we were killed by a call to popup_kill_any_active(), kill the popup
1063 if(Popup_should_die){
1068 // if we're flagged as should be running the state underneath, then do so
1069 if(flags & PF_RUN_STATE){
1070 game_do_state(gameseq_get_state());
1072 // otherwise just run the common functions (for networking,etc)
1074 game_set_frametime(-1);
1075 game_do_state_common(gameseq_get_state(),flags & PF_NO_NETWORKING); // do stuff common to all states
1078 k = Popup_window.process(); // poll for input, handle mouse
1079 choice = popup_process_keys(pi, k, flags);
1080 if ( choice != POPUP_NOCHANGE ) {
1085 choice = popup_check_buttons(pi);
1086 if ( choice != POPUP_NOCHANGE ) {
1091 // don't draw anything
1092 if(!(flags & PF_RUN_STATE)){
1093 gr_restore_screen(screen_id);
1096 // if this is an input popup, store the input text
1097 if(flags & PF_INPUT){
1098 Popup_input.get_text(pi->input_text);
1101 Popup_window.draw();
1102 popup_force_draw_buttons(pi);
1103 popup_draw_msg_text(pi, flags);
1104 popup_draw_button_text(pi, flags);
1108 popup_close(pi,screen_id);
1112 int popup_do_with_condition(popup_info *pi, int flags, int(*condition)())
1114 int screen_id, choice = -1, done = 0;
1116 screen_id = gr_save_screen();
1117 if ( popup_init(pi, flags) == -1 )
1125 game_set_frametime(-1);
1126 game_do_state_common(gameseq_get_state()); // do stuff common to all states
1127 gr_restore_screen(screen_id);
1129 // draw one frame first
1130 Popup_window.draw();
1131 popup_force_draw_buttons(pi);
1132 popup_draw_msg_text(pi, flags);
1133 popup_draw_button_text(pi, flags);
1136 // test the condition function or process for the window
1137 if ((test = condition()) > 0) {
1141 k = Popup_window.process(); // poll for input, handle mouse
1142 choice = popup_process_keys(pi, k, flags);
1143 if ( choice != POPUP_NOCHANGE ) {
1148 choice = popup_check_buttons(pi);
1149 if ( choice != POPUP_NOCHANGE ) {
1156 popup_close(pi,screen_id);
1161 // maybe assign a keyboard shortcut to this button
1162 // input: pi => popup information so far
1163 // i => number of choice
1164 // str => string for button press
1165 void popup_maybe_assign_keypress(popup_info *pi, int n, char *str)
1167 int i,j,len=0,next_char_is_shortcut=0;
1170 pi->shortcut_index[n]=-1;
1174 pi->button_text[n] = (char*)malloc(len);
1175 memset(pi->button_text[n], 0, len);
1178 // copy chars over, watching for underline meta-char '&'
1179 for (i=0; i<len-1; i++) {
1180 if ( str[i] == '&' ) {
1181 pi->shortcut_index[n]=i;
1182 next_char_is_shortcut=1;
1184 if ( next_char_is_shortcut ) {
1185 next_char_is_shortcut=0;
1186 char first_char_string[2];
1187 first_char_string[0]=str[i];
1188 first_char_string[1]=0;
1189 SDL_strlwr(first_char_string);
1190 pi->keypress[n] = first_char_string[0];
1192 pi->button_text[n][j++]=str[i];
1197 // input: flags => flags => formatting specificatons (PF_...)
1198 // nchoices => number of choices popup has
1199 // text_1 => text for first button
1201 // text_n => text for last button
1202 // msg text => text msg for popup (can be of form "%s",pl->text)
1204 // exit: choice selected (0..nchoices-1)
1205 // will return -1 if there was an error or popup was aborted
1209 // rval = popup(0, 2, POPUP_OK, POPUP_CANCEL, "Sorry %s, try again", pl->callsign);
1210 int popup(int flags, int nchoices, ... )
1216 if ( Popup_is_active ) {
1217 Int3(); // should never happen
1221 Popup_flags = flags;
1223 SDL_assert( nchoices > 0 && nchoices <= POPUP_MAX_CHOICES );
1224 Popup_info.nchoices = nchoices;
1226 va_start(args, nchoices );
1229 for (i=0; i<nchoices; i++ ) {
1230 s = va_arg( args, char * );
1231 Popup_info.button_text[i] = NULL;
1232 popup_maybe_assign_keypress(&Popup_info, i, s);
1236 format = va_arg( args, char * );
1237 Popup_info.raw_text[0] = 0;
1238 SDL_vsnprintf(Popup_info.raw_text, SDL_arraysize(Popup_info.raw_text), format, args);
1241 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1244 Popup_is_active = 1;
1246 choice = popup_do( &Popup_info, flags );
1255 // determine if a popup is being drawn
1258 return Popup_is_active;
1261 // This function displays a popup message box and every frame it checks the condition() function
1262 // which is passed in as an argument.
1263 // If the condition() returns TRUE, the message box ends itself. This function returns whatever
1264 // the condition() did if the condition() occurred, and FALSE if the cancel button was pressed.
1265 int popup_till_condition(int (*condition)(), ...)
1272 if ( Popup_is_active ) {
1273 Int3(); // should never happen
1277 Popup_info.nchoices = 1;
1281 va_start(args, condition );
1284 s = va_arg( args, char * );
1285 Popup_info.button_text[0] = NULL;
1286 popup_maybe_assign_keypress(&Popup_info, 0, s);
1289 format = va_arg( args, char * );
1290 Popup_info.raw_text[0] = 0;
1291 SDL_vsnprintf(Popup_info.raw_text, SDL_arraysize(Popup_info.raw_text), format, args);
1294 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1297 Popup_is_active = 1;
1299 choice = popup_do_with_condition( &Popup_info, flags, condition );
1308 // popup to return the value from an input box
1309 char *popup_input(int flags, const char *caption, int max_output_len)
1311 if ( Popup_is_active ) {
1312 Int3(); // should never happen
1316 // make it an inputbox type popup
1317 Popup_flags = flags;
1318 Popup_flags |= PF_INPUT;
1320 // add a cancel button
1321 Popup_info.nchoices = 0;
1322 // popup_maybe_assign_keypress(&Popup_info, 0, "&Cancel");
1325 SDL_assert(caption != NULL);
1326 SDL_strlcpy(Popup_info.raw_text, caption, SDL_arraysize(Popup_info.raw_text));
1327 SDL_assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );
1329 // set input text length
1330 if((max_output_len > POPUP_INPUT_MAX_CHARS) || (max_output_len == -1)){
1331 Popup_info.max_input_text_len = POPUP_INPUT_MAX_CHARS - 1;
1333 Popup_info.max_input_text_len = max_output_len;
1336 // zero the popup input text
1337 memset(Popup_info.input_text, 0, POPUP_INPUT_MAX_CHARS);
1339 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1342 Popup_is_active = 1;
1344 // if the user cancelled
1345 if(popup_do(&Popup_info, Popup_flags) == POPUP_ABORT){
1349 // otherwise return the
1350 return Popup_info.input_text;
1353 int popup_running_state()
1355 return Popup_running_state;
1358 // kill any active popup, forcing it to return -1 (similar to ESC)
1359 void popup_kill_any_active()
1361 if(Popup_is_active){
1362 Popup_should_die = 1;
1366 // change the text inside of the popup
1367 void popup_change_text(const char *new_text)
1369 // copy the raw text
1370 SDL_strlcpy(Popup_info.raw_text, new_text, SDL_arraysize(Popup_info.raw_text));
1372 // recalculate all display information
1373 popup_split_lines(&Popup_info,Popup_flags);