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
261 void (*callback)(int); // callback to call on user choice (optional)
262 int flags; // popup flags (PF_*)
263 int choice; // choice user made
264 int screen_id; // background screen id
265 int (*condition)(); // test condition (optional)
268 ////////////////////////////////////////////////////////////////
269 // UI Data and constants
270 ////////////////////////////////////////////////////////////////
271 UI_WINDOW Popup_window;
272 UI_BUTTON Popup_buttons[POPUP_MAX_CHOICES]; // actual lit buttons
273 UI_BUTTON Popup_button_regions[POPUP_MAX_CHOICES]; // fake buttons used for mouse detection over text
274 UI_INPUTBOX Popup_input; // input box for the popup
276 UI_SLIDER2 Popup_slider; // if we have more text in the popup than can be displayed at once
279 // extents for message portion of popup
280 int Popup_text_coords[GR_NUM_RESOLUTIONS][4] = {
294 // offset from the first y text line value to place the centered input box
295 int Popup_input_y_offset[GR_NUM_RESOLUTIONS] = {
300 // offset from the first y text line value to start drawing text
301 int Popup_input_text_y_offset[GR_NUM_RESOLUTIONS] = {
306 typedef struct popup_background
308 const char *filename; // filename for background
309 int coords[2]; // coords to draw background at
312 ////////////////////////////////////////////////////////////////
314 ////////////////////////////////////////////////////////////////
315 static int Popup_is_active=0;
316 static int Popup_should_die=0; // popup should quit during the next iteration of its loop
318 static popup_info Popup_info;
320 static int Title_coords[GR_NUM_RESOLUTIONS][5] =
346 static int Button_regions[GR_NUM_RESOLUTIONS][3][4] = {
349 {464, 269, 505, 290}, // upper right pixel of text, lower right pixel of button
350 {464, 297, 505, 320},
353 {464, 232, 510, 250}, // upper right pixel of text, lower right pixel of button
354 {464, 262, 510, 279},
359 {752, 373, 806, 406}, // upper right pixel of text, lower right pixel of button
360 {752, 421, 806, 461},
365 static int Button_coords[GR_NUM_RESOLUTIONS][3][2] =
369 {474, 257}, // upper left pixel
373 {474, 224}, // upper left pixel
379 {758, 358}, // upper left pixel
385 static popup_background Popup_background[GR_NUM_RESOLUTIONS][4] =
389 { "Pop2a", { 131, 122} },
390 { "Pop2a", { 131, 122 } },
391 { "Pop3", { 131, 122 } },
393 { "Pop2", { 129, 99 } },
394 { "Pop2", { 129, 99 } },
395 { "Pop3", { 129, 99 } },
399 { "2_Pop2", { 206, 158 } },
400 { "2_Pop2", { 206, 158 } },
401 { "2_Pop3", { 206, 158 } },
405 #define BUTTON_NEGATIVE 0
406 #define BUTTON_POSITIVE 1
407 #define BUTTON_GENERIC_FIRST 2
408 #define BUTTON_GENERIC_SECOND 3
409 #define BUTTON_GENERIC_THIRD 4
410 static const char *Popup_button_filenames[GR_NUM_RESOLUTIONS][2][5] =
414 {"Pop2a_00", // negative
415 "Pop2a_01", // positive
416 "Pop2a_02", // first generic
417 "Pop2a_03", // second generic
418 "Pop2a_04"}, // third generic
420 {"Pop2a_00", // negative
421 "Pop2a_01", // positive
422 "PopD_00", // first generic
423 "PopD_01", // second generic
424 "PopD_02"}, // third generic
426 {"Pop_00", // negative
427 "Pop_01", // positive
428 "Pop_02", // first generic
429 "Pop_03", // second generic
430 "Pop_04"}, // third generic
432 {"Pop_00", // negative
433 "Pop_01", // positive
434 "PopD_00", // first generic
435 "PopD_01", // second generic
436 "PopD_02"}, // third generic
440 {"2_Pop_00", // negative
441 "2_Pop_01", // positive
442 "2_Pop_02", // first generic
443 "2_Pop_03", // second generic
444 "2_Pop_04"}, // third generic
446 {"2_Pop_00", // negative
447 "2_Pop_01", // positive
448 "2_PopD_00", // first generic
449 "2_PopD_01", // second generic
450 "2_PopD_02"}, // third generic
454 int Popup_running_state;
455 int Popup_default_choice; // which choice is highlighted (ie what gets choosen when enter is pressed)
457 // see if any popup buttons have been pressed
458 // exit: POPUP_NOCHANGE => no buttons pressed
459 // >=0 => button index that was pressed
460 int popup_check_buttons(popup_info *pi)
465 for ( i = 0; i < pi->nchoices; i++ ) {
466 b = &Popup_button_regions[i];
467 if ( b->pressed() ) {
471 b = &Popup_buttons[i];
472 if ( b->pressed() ) {
477 return POPUP_NOCHANGE;
480 // maybe play a sound when key up/down is pressed to switch default choice
481 void popup_play_default_change_sound(popup_info *pi)
483 if ( pi->nchoices > 1 ) {
487 // only play if mouse not currently highlighting a choice
489 for ( i = 0; i < pi->nchoices; i++ ) {
490 br = &Popup_button_regions[i];
491 b = &Popup_buttons[i];
492 if ( br->button_down() ) {
497 if ( br->button_hilighted() && !b->button_down() ) {
502 if ( b->button_hilighted() ) {
508 gamesnd_play_iface(SND_USER_SELECT);
513 // do any key processing here
514 // input: pi => data about the popup
515 // k => key that was pressed
517 // exit: 0 .. nchoices-1 => choice selected through keypress
518 // POPUP_ABORT => abort the popup
519 // POPUP_NOCHANGE => nothing happenned
520 int popup_process_keys(popup_info *pi, int k)
525 return POPUP_NOCHANGE;
528 for ( i = 0; i < pi->nchoices; i++ ) {
529 if ( pi->keypress[i] == key_get_text_input() ) {
530 Popup_default_choice=i;
531 Popup_buttons[i].press_button();
539 // select the current default choice
540 return Popup_default_choice;
544 // only process the escape key if this flag is not set
545 if(!(pi->flags & PF_IGNORE_ESC)){
553 popup_play_default_change_sound(pi);
554 Popup_default_choice++;
555 if ( Popup_default_choice >= pi->nchoices ) {
556 Popup_default_choice=0;
562 case KEY_SHIFTED+SDLK_TAB:
563 popup_play_default_change_sound(pi);
564 Popup_default_choice--;
565 if ( Popup_default_choice < 0 ) {
566 Popup_default_choice=pi->nchoices-1;
575 masked_k = k & ~KEY_CTRLED; // take out CTRL modifier only
576 if ( (PF_ALLOW_DEAD_KEYS) && (Game_mode & GM_IN_MISSION) ) {
577 process_set_of_keys(masked_k, Dead_key_set_size, Dead_key_set);
578 button_info_do(&Player->bi); // call functions based on status of button_info bit vectors
581 return POPUP_NOCHANGE;
584 // Split off the title and break up the body lines
585 void popup_split_lines(popup_info *pi)
587 int nlines, i, body_offset = 0;
588 int n_chars[POPUP_MAX_LINES];
589 char *p_str[POPUP_MAX_LINES];
595 nlines = split_str(pi->raw_text, 1000, n_chars, p_str, POPUP_MAX_LINES);
596 SDL_assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
598 if ( pi->flags & (PF_TITLE | PF_TITLE_BIG) ) {
599 // get first line out
600 len = SDL_min(n_chars[0] + 1, POPUP_MAX_LINE_CHARS);
601 SDL_strlcpy(pi->title, p_str[0], len);
605 if ( pi->flags & PF_BODY_BIG ) {
609 nlines = split_str(pi->raw_text, Popup_text_coords[gr_screen.res][2], n_chars, p_str, POPUP_MAX_LINES);
610 SDL_assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
612 pi->nlines = nlines - body_offset;
614 for ( i = 0; i < pi->nlines; i++ ) {
615 SDL_assert(n_chars[i+body_offset] < POPUP_MAX_LINE_CHARS);
616 len = SDL_min(n_chars[i+body_offset] + 1, POPUP_MAX_LINE_CHARS);
617 SDL_strlcpy(pi->msg_lines[i], p_str[i+body_offset], len);
623 // figure out what filename to use for the button icon
624 const char *popup_get_button_filename(popup_info *pi, int i)
626 const char *fname = NULL;
629 // check for special button texts and if found, use specialized buttons for them.
630 if ((!SDL_strcasecmp(pi->button_text[i], POPUP_OK + 1) || !SDL_strcasecmp(pi->button_text[i], POPUP_YES + 1)) && !(pi->flags & PF_NO_SPECIAL_BUTTONS)){
631 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
634 if ((!SDL_strcasecmp(pi->button_text[i], POPUP_CANCEL + 1) || !SDL_strcasecmp(pi->button_text[i], POPUP_NO + 1)) && !(pi->flags & PF_NO_SPECIAL_BUTTONS)){
635 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
638 switch (pi->nchoices) {
643 if ( (pi->flags & PF_USE_AFFIRMATIVE_ICON) && !(pi->flags & PF_NO_SPECIAL_BUTTONS) ) {
644 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
645 } else if ( pi->flags & PF_USE_NEGATIVE_ICON && !(pi->flags & PF_NO_SPECIAL_BUTTONS) ) {
646 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
648 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
653 if ( pi->flags & PF_USE_NEGATIVE_ICON && i==0 ) {
654 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
658 if ( pi->flags & PF_USE_AFFIRMATIVE_ICON && i==1 ) {
659 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
664 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
666 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
674 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
677 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
680 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_THIRD];
689 // bogus handling function for slider (we don't need it to do anything)
690 void popup_slider_bogus()
694 // init the Popup window
695 int popup_init(popup_info *pi)
699 popup_background *pbg;
703 pi->choice = POPUP_NOCHANGE;
705 if(pi->nchoices == 0){
706 pbg = &Popup_background[gr_screen.res][0];
708 pbg = &Popup_background[gr_screen.res][pi->nchoices-1];
711 // anytime in single player, and multiplayer, not in mission, go ahead and stop time
712 if ( (Game_mode & GM_NORMAL) || ((Game_mode & GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) ){
716 // create base window
717 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);
718 Popup_window.set_foreground_bmap(pbg->filename);
721 for (i=0; i<pi->nchoices; i++) {
722 b = &Popup_buttons[i];
723 // accommodate single-choice positive icon being positioned differently
724 if ( (pi->nchoices == 1) && (pi->flags&PF_USE_AFFIRMATIVE_ICON) ) {
725 b->create(&Popup_window, "", Button_coords[gr_screen.res][i+1][0], Button_coords[gr_screen.res][i+1][1], 30, 25, 0, 1);
727 b->create(&Popup_window, "", Button_coords[gr_screen.res][i][0], Button_coords[gr_screen.res][i][1], 30, 25, 0, 1);
730 fname = popup_get_button_filename(pi, i);
731 b->set_bmaps(fname, 3, 0);
732 b->set_highlight_action(common_play_highlight_sound);
733 if ( pi->keypress[i] >= 0 ) {
734 b->set_hotkey(pi->keypress[i]);
737 // create invisible buttons to detect mouse presses... can't use mask since button region is dynamically sized
740 gr_get_string_size(&w, &h, pi->button_text[i]);
741 lx = Button_regions[gr_screen.res][i][0] - w;
742 b = &Popup_button_regions[i];
744 // accommodate single-choice positive icon being positioned differently
745 if ( (pi->nchoices == 1) && (pi->flags&PF_USE_AFFIRMATIVE_ICON) ) {
746 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);
748 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);
755 if (Web_cursor_bitmap >= 0) {
756 if (pi->flags & PF_WEB_CURSOR_1) {
757 Popup_buttons[1].set_custom_cursor_bmap(Web_cursor_bitmap);
759 if (pi->flags & PF_WEB_CURSOR_2) {
760 Popup_buttons[2].set_custom_cursor_bmap(Web_cursor_bitmap);
764 // if this is an input popup, create and center the popup
765 if(pi->flags & PF_INPUT){
766 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);
767 Popup_input.set_focus();
770 Popup_default_choice=0;
771 Popup_should_die = 0;
773 if (pi->flags & PF_RUN_STATE) {
774 Popup_running_state = 1;
776 Popup_running_state = 0;
779 popup_split_lines(pi);
782 // create the popup slider (which we may not need to use
783 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,
784 Popup_slider_name[gr_screen.res], popup_slider_bogus, popup_slider_bogus, NULL);
790 // called when a popup goes away
791 void popup_close(popup_info *pi)
795 gamesnd_play_iface(SND_POPUP_DISAPPEAR); // play sound when popup disappears
797 for (i=0; i<pi->nchoices; i++ ) {
798 if ( pi->button_text[i] != NULL ) {
799 free(pi->button_text[i]);
800 pi->button_text[i] = NULL;
804 if(pi->screen_id >= 0){
805 gr_free_screen(pi->screen_id);
810 pi->condition = NULL;
812 Popup_window.destroy();
813 anim_ignore_next_frametime(); // to avoid skips in animation since next frametime is saturated
817 Popup_running_state = 0;
819 // anytime in single player, and multiplayer, not in mission, go ahead and stop time
820 if ( (Game_mode & GM_NORMAL) || ((Game_mode & GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) )
826 if ( !Popup_is_active ) {
830 popup_info *pi = &Popup_info;
835 // set the popup text color
836 void popup_set_text_color(int flags)
838 if ( flags & PF_BODY_RED ) {
839 gr_set_color_fast(&Color_red);
843 if ( flags & PF_BODY_GREEN ) {
844 gr_set_color_fast(&Color_green);
848 if ( flags & PF_BODY_BLUE ) {
849 gr_set_color_fast(&Color_blue);
853 gr_set_color_fast(&Color_bright_blue);
856 // set the popup text color
857 void popup_set_title_color(int flags)
859 if ( flags & PF_TITLE_RED ) {
860 gr_set_color_fast(&Color_red);
864 if ( flags & PF_TITLE_GREEN ) {
865 gr_set_color_fast(&Color_green);
869 if ( flags & PF_TITLE_BLUE ) {
870 gr_set_color_fast(&Color_blue);
874 if ( flags & PF_TITLE_WHITE ) {
875 gr_set_color_fast(&Color_bright_white);
879 gr_set_color_fast(&Color_bright_blue);
882 // Draw the title centered within the popup
883 void popup_draw_title(int sy, const char *line, int flags)
887 if ( flags & PF_TITLE_BIG ) {
893 gr_get_string_size(&w, &h, line);
894 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
896 popup_set_title_color(flags);
897 gr_string(sx,sy,line);
900 // calculate the starting display index
901 int popup_calc_starting_index(popup_info *pi)
903 // we're basically ignoring any titles here.
904 if(pi->nlines <= Popup_max_display[gr_screen.res]){
909 // otherwise, we want to see what item index the slider is on
910 return Popup_slider.get_currentItem();
916 // Figure out the y-coord to start drawing the popup text. The text
917 // is centered vertically within the popup.
918 int popup_calc_starting_y(popup_info *pi)
921 int num_lines = pi->nlines > Popup_max_display[gr_screen.res] ? Popup_max_display[gr_screen.res] : pi->nlines;
923 if ( pi->flags & (PF_TITLE | PF_TITLE_BIG) ) {
924 if ( pi->flags & PF_TITLE_BIG ) {
929 total_h += gr_get_font_height();
932 if ( pi->flags & PF_BODY_BIG ) {
938 total_h += num_lines * gr_get_font_height();
939 sy = fl2i((Popup_text_coords[gr_screen.res][1] + Popup_text_coords[gr_screen.res][3]/2.0f) - total_h/2.0f + 0.5f);
941 // if this is an input style box, add in some y
942 if(pi->flags & PF_INPUT){
943 sy += Popup_input_text_y_offset[gr_screen.res];
949 // Draw the message text nicely formatted in the popup
950 void popup_draw_msg_text(popup_info *pi)
956 // figure out the starting display
957 line_index = popup_calc_starting_index(pi);
959 // figure out the starting y:
960 sy = popup_calc_starting_y(pi);
962 // draw title if required
963 if ( pi->flags & (PF_TITLE | PF_TITLE_BIG) ) {
964 popup_draw_title(sy, pi->title, pi->flags);
965 sy += gr_get_font_height();
969 if ( pi->flags & PF_BODY_BIG ) {
975 popup_set_text_color(pi->flags);
977 for ( i = line_index; i < pi->nlines; i++, line_count++ ) {
978 // if we've already displayed the max # of lines
979 if(line_count >= Popup_max_display[gr_screen.res]){
983 gr_get_string_size(&w, &h, pi->msg_lines[i]);
984 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
985 gr_string(sx, sy + line_count * h, pi->msg_lines[i]);
991 gr_set_color_fast(&Color_bright_red);
992 gr_string(Title_coords[gr_screen.res][4], sy + (Popup_max_display[gr_screen.res]) * h, XSTR("More", 459));
995 gr_set_font(FONT1); // reset back to regular font size
998 // Draw the button text nicely formatted in the popup
999 void popup_draw_button_text(popup_info *pi)
1001 int w, h, i, sx, sy;
1003 gr_set_color_fast(&Color_bright_blue);
1005 for ( i=0; i < pi->nchoices; i++ ) {
1006 gr_get_string_size(&w, &h, pi->button_text[i]);
1008 if ( (pi->nchoices == 1) && (pi->flags & PF_USE_AFFIRMATIVE_ICON) ) {
1009 sx = Button_regions[gr_screen.res][i+1][0]-w;
1010 sy = Button_regions[gr_screen.res][i+1][1]+4;
1012 sx = Button_regions[gr_screen.res][i][0]-w;
1013 sy = Button_regions[gr_screen.res][i][1]+4;
1016 gr_string(sx, sy, pi->button_text[i]);
1018 // figure out where to draw underline char
1019 if ( pi->shortcut_index[i] > 0 ) {
1020 int cut=pi->shortcut_index[i];
1021 char save_char=pi->button_text[i][cut];
1022 pi->button_text[i][cut] = 0;
1023 gr_get_string_size(&w, &h, pi->button_text[i]);
1024 pi->button_text[i][cut] = save_char;
1028 if ( pi->shortcut_index[i] >= 0 ) {
1029 gr_printf(sx, sy, NOX("%c"), 95);
1034 // See if any of the button should change appearance based on mouse position
1035 void popup_force_draw_buttons(popup_info *pi)
1037 int i,mouse_is_highlighting=0;
1040 for ( i = 0; i < pi->nchoices; i++ ) {
1041 br = &Popup_button_regions[i];
1042 b = &Popup_buttons[i];
1043 if ( br->button_down() ) {
1045 mouse_is_highlighting=1;
1049 if ( (b->button_hilighted()) || (br->button_hilighted() && !b->button_down()) ) {
1050 Popup_default_choice=i;
1051 mouse_is_highlighting=1;
1056 // Only if mouse is not highlighting an option, let the default choice be drawn highlighted
1057 if ( (!mouse_is_highlighting) && (pi->nchoices>1) ) {
1058 for ( i = 0; i < pi->nchoices; i++ ) {
1059 b = &Popup_buttons[i];
1060 // highlight the default choice
1061 if ( i == Popup_default_choice ) {
1068 static void popup_do(popup_info *pi)
1074 // if we were killed by a call to popup_kill_any_active(), kill the popup
1075 if (Popup_should_die) {
1079 (*(pi->callback))(-1);
1087 // if we're flagged as should be running the state underneath, then do so
1088 if (pi->flags & PF_RUN_STATE) {
1089 game_do_state(gameseq_get_state());
1091 // otherwise just run the common functions (for networking,etc)
1093 game_set_frametime(-1);
1094 game_do_state_common(gameseq_get_state(), pi->flags & PF_NO_NETWORKING); // do stuff common to all states
1097 // test the condition function or process for the window
1098 if (pi->condition && ((test = (*(pi->condition))()) > 0)) {
1102 (*(pi->callback))(pi->choice);
1109 int k = Popup_window.process(); // poll for input, handle mouse
1111 pi->choice = popup_process_keys(pi, k);
1113 if (pi->choice != POPUP_NOCHANGE) {
1115 (*(pi->callback))(pi->choice);
1123 pi->choice = popup_check_buttons(pi);
1125 if (pi->choice != POPUP_NOCHANGE) {
1127 (*(pi->callback))(pi->choice);
1136 // don't draw anything
1137 if ( !(pi->flags & PF_RUN_STATE) ) {
1138 gr_restore_screen(pi->screen_id);
1141 // if this is an input popup, store the input text
1142 if (pi->flags & PF_INPUT) {
1143 Popup_input.get_text(pi->input_text);
1146 Popup_window.draw();
1147 popup_force_draw_buttons(pi);
1148 popup_draw_msg_text(pi);
1149 popup_draw_button_text(pi);
1154 void popup_do_frame()
1156 if ( !Popup_is_active ) {
1160 popup_info *pi = &Popup_info;
1165 static int popup_do_sync()
1167 popup_info *pi = &Popup_info;
1169 SDL_assert(Popup_is_active);
1176 // maybe assign a keyboard shortcut to this button
1177 // input: pi => popup information so far
1178 // i => number of choice
1179 // str => string for button press
1180 void popup_maybe_assign_keypress(popup_info *pi, int n, char *str)
1182 int i,j,len=0,next_char_is_shortcut=0;
1185 pi->shortcut_index[n]=-1;
1189 pi->button_text[n] = (char*)malloc(len);
1190 memset(pi->button_text[n], 0, len);
1193 // copy chars over, watching for underline meta-char '&'
1194 for (i=0; i<len-1; i++) {
1195 if ( str[i] == '&' ) {
1196 pi->shortcut_index[n]=i;
1197 next_char_is_shortcut=1;
1199 if ( next_char_is_shortcut ) {
1200 next_char_is_shortcut=0;
1201 char first_char_string[2];
1202 first_char_string[0]=str[i];
1203 first_char_string[1]=0;
1204 SDL_strlwr(first_char_string);
1205 pi->keypress[n] = first_char_string[0];
1207 pi->button_text[n][j++]=str[i];
1212 static void popup_internal(void (*callback)(int), int (*condition)(), int flags, int nchoices, va_list args)
1216 popup_info *pi = &Popup_info;
1219 pi->callback = callback;
1220 pi->condition = condition;
1222 SDL_assert( nchoices > 0 && nchoices <= POPUP_MAX_CHOICES );
1223 pi->nchoices = nchoices;
1226 for (i=0; i<nchoices; i++ ) {
1227 s = va_arg( args, char * );
1228 pi->button_text[i] = NULL;
1229 popup_maybe_assign_keypress(pi, i, s);
1233 format = va_arg( args, char * );
1234 pi->raw_text[0] = 0;
1235 SDL_vsnprintf(pi->raw_text, SDL_arraysize(pi->raw_text), format, args);
1237 if ( popup_init(pi) == -1 ) {
1242 pi->choice = POPUP_ABORT;
1247 pi->screen_id = gr_save_screen();
1249 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1252 Popup_is_active = 1;
1255 // input: callback => function to call on user action (popup done, etc.)
1256 // flags => formatting specificatons (PF_... shown above)
1257 // nchoices => number of choices popup has
1258 // text_1 => text for first button
1260 // text_n => text for last button
1261 // msg text => text msg for popup (can be of form "%s",pl->text)
1264 // popup(NULL, 0, 2, POPUP_YES, POPUP_NO, "Hey %s, do you want to quit", pl->callsign);
1265 void popup_callback(void (*callback)(int), int flags, int nchoices, ... )
1269 if ( Popup_is_active ) {
1270 Int3(); // should never happen
1274 va_start(args, nchoices);
1276 popup_internal(callback, NULL, flags, nchoices, args);
1281 void popup(int flags, int nchoices, ...)
1285 if ( Popup_is_active ) {
1286 Int3(); // should never happen
1290 va_start(args, nchoices);
1292 popup_internal(NULL, NULL, flags, nchoices, args);
1297 int popup_sync(int flags, int nchoices, ...)
1299 popup_info *pi = &Popup_info;
1302 if ( Popup_is_active ) {
1303 Int3(); // should never happen
1307 va_start(args, nchoices);
1309 popup_internal(NULL, NULL, flags, nchoices, args);
1313 int rval = pi->choice; // if init failed, this will be ABORT
1315 while (rval == POPUP_NOCHANGE) {
1316 rval = popup_do_sync();
1321 if (rval == POPUP_ABORT) {
1328 // determine if a popup is being drawn
1331 return Popup_is_active;
1334 // This function displays a popup message box and every frame it checks the condition() function
1335 // which is passed in as an argument.
1336 // If the condition() returns TRUE, the message box ends itself. This function returns whatever
1337 // the condition() did if the condition() occurred, and FALSE if the cancel button was pressed.
1338 int popup_till_condition(int (*condition)(), ...)
1340 popup_info *pi = &Popup_info;
1343 if ( Popup_is_active ) {
1344 Int3(); // should never happen
1348 va_start(args, condition);
1350 popup_internal(NULL, condition, 0, 1, args);
1354 int rval = pi->choice; // if init failed, this will be ABORT
1356 while (rval == POPUP_NOCHANGE) {
1357 rval = popup_do_sync();
1362 if (rval == POPUP_ABORT) {
1369 // popup to return the value from an input box
1370 void popup_input(void (*callback)(int), int flags, const char *caption, int max_output_len)
1372 popup_info *pi = &Popup_info;
1374 if ( Popup_is_active ) {
1375 Int3(); // should never happen
1379 // make it an inputbox type popup
1380 pi->flags = flags | PF_INPUT;
1382 pi->callback = NULL;
1383 pi->condition = NULL;
1385 // add a cancel button
1387 // popup_maybe_assign_keypress(&Popup_info, 0, "&Cancel");
1390 SDL_assert(caption != NULL);
1391 SDL_strlcpy(pi->raw_text, caption, SDL_arraysize(pi->raw_text));
1392 SDL_assert(strlen(pi->raw_text) < POPUP_MAX_CHARS );
1394 // set input text length
1395 if((max_output_len > POPUP_INPUT_MAX_CHARS) || (max_output_len == -1)){
1396 pi->max_input_text_len = POPUP_INPUT_MAX_CHARS - 1;
1398 pi->max_input_text_len = max_output_len;
1401 if ( popup_init(pi) == -1 ) {
1406 pi->choice = POPUP_ABORT;
1411 pi->screen_id = gr_save_screen();
1413 // zero the popup input text
1414 SDL_zero(pi->input_text);
1416 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1419 Popup_is_active = 1;
1422 const char *popup_input_sync(int flags, const char *caption, int max_output_len)
1424 popup_info *pi = &Popup_info;
1426 if ( Popup_is_active ) {
1427 Int3(); // should never happen
1431 popup_input(NULL, flags, caption, max_output_len);
1433 int rval = pi->choice; // if init failed, this will be ABORT
1435 while (rval == POPUP_NOCHANGE) {
1436 rval = popup_do_sync();
1441 if (rval == POPUP_ABORT) {
1445 return pi->input_text;
1448 const char *popup_get_input_text()
1450 popup_info *pi = &Popup_info;
1452 return pi->input_text;
1455 int popup_running_state()
1457 return Popup_running_state;
1460 // kill any active popup, forcing it to return -1 (similar to ESC)
1461 void popup_kill_any_active()
1463 if(Popup_is_active){
1464 Popup_should_die = 1;
1468 // change the text inside of the popup
1469 void popup_change_text(const char *new_text)
1471 // copy the raw text
1472 SDL_strlcpy(Popup_info.raw_text, new_text, SDL_arraysize(Popup_info.raw_text));
1474 // recalculate all display information
1475 popup_split_lines(&Popup_info);