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.3 2002/06/09 04:41:25 relnev
19 * added copyright header
21 * Revision 1.2 2002/05/07 03:16:51 theoddone33
22 * The Great Newline Fix
24 * Revision 1.1.1.1 2002/05/03 03:28:10 root
28 * 12 10/14/99 2:00p Jefff
29 * added include for os_poll to fix build error
31 * 11 10/14/99 10:18a Daveb
32 * Fixed incorrect CD checking problem on standalone server.
34 * 10 8/16/99 9:50a Jefff
35 * added mouseover webcursor options to user-defined popup buttons.
37 * 9 8/11/99 5:47p Jefff
38 * fixed button bitmap loading
40 * 8 8/04/99 10:53a Dave
41 * Added title to the user tips popup.
43 * 7 8/02/99 9:13p Dave
46 * 6 6/18/99 5:16p Dave
47 * Added real beam weapon lighting. Fixed beam weapon sounds. Added MOTD
48 * dialog to PXO screen.
50 * 5 6/01/99 3:52p Dave
51 * View footage screen. Fixed xstrings to not display the & symbol. Popup,
52 * dead popup, pxo find player popup, pxo private room popup.
54 * 4 2/11/99 3:08p Dave
55 * PXO refresh button. Very preliminary squad war support.
57 * 3 10/13/98 9:29a Dave
58 * Started neatening up freespace.h. Many variables renamed and
59 * reorganized. Added AlphaColors.[h,cpp]
61 * 2 10/07/98 10:53a Dave
64 * 1 10/07/98 10:51a Dave
66 * 36 5/11/98 11:39p Dave
69 * 35 3/22/98 10:59p Allender
70 * don't stop time in multiplayer when "in mission"
72 * 34 3/17/98 12:41a Lawrance
73 * Support \n's between title and body
75 * 33 3/12/98 4:11p Dave
76 * AL: Check that max popup lines isn't exceeded
78 * 32 2/22/98 12:19p John
79 * Externalized some strings
81 * 31 2/10/98 3:26p Hoffoss
82 * Don't check for 'dead key set' unless in the mission.
84 * 30 2/05/98 11:21p Lawrance
85 * move death popup to below letterbox view
87 * 29 2/05/98 11:09a Dave
88 * Fixed an ingame join bug. Fixed a read-only file problem with
89 * multiplauer file xfer.
91 * 28 2/03/98 11:52p Lawrance
92 * Don't highlight default choice if there is only one.
94 * 27 2/03/98 8:18p Dave
95 * More MT stats transfer stuff
97 * 26 1/30/98 3:05p Lawrance
98 * reposition text on death popup
100 * 25 1/29/98 6:55p Lawrance
101 * Integrate new art for the death popup
103 * 24 1/28/98 6:24p Dave
104 * Made standalone use ~8 megs less memory. Fixed multiplayer submenu
105 * sequencing problem.
107 * 23 1/27/98 5:52p Lawrance
108 * support new "tiny" popups
110 * 22 1/27/98 5:01p Dave
111 * Put in support for leaving multiplayer games in the pause state. Fixed
112 * a popup bug which freed saved screens incorrectly. Reworked scoring
113 * kill and assist evaluation.
115 * 21 1/27/98 3:25p Hoffoss
116 * Made popups use the correct button icons for default positive and
119 * 20 1/26/98 6:28p Lawrance
120 * Use '&' meta char for underlining, change how keyboard usage works so
121 * fits better with mouse usage.
123 * 19 1/23/98 12:01a Lawrance
124 * Fix bug when in single-choice popups.
126 * 18 1/22/98 10:45p Lawrance
127 * Implment default selections and arrow key navigation.
129 * 17 1/20/98 5:52p Lawrance
130 * center popup text in X and Y directions. Support single affirmative
131 * icon special placement.
133 * 16 1/19/98 11:37p Lawrance
134 * Fixing Optimization build warnings
136 * 15 1/17/98 10:04p Lawrance
137 * fix errors in popup comments
139 * 14 1/15/98 2:52p Lawrance
140 * Fix problem with reading bogus buttons.
142 * 13 1/14/98 6:55p Dave
143 * Fixed a slew of multiplayer bugs. Made certain important popups ignore
144 * the escape character.
146 * 12 1/14/98 6:42p Hoffoss
147 * Massive changes to UI code. A lot cleaner and better now. Did all
148 * this to get the new UI_DOT_SLIDER to work properly, which the old code
149 * wasn't flexible enough to handle.
151 * 11 1/14/98 12:23p Lawrance
152 * Support for three choice popups.
154 * 10 1/13/98 5:37p Dave
155 * Reworked a lot of standalone interface code. Put in single and
156 * multiplayer popups for death sequence. Solidified multiplayer kick
159 * 9 1/13/98 4:06p Lawrance
160 * Add underline char for shortcuts.
162 * 8 1/11/98 11:14p Lawrance
163 * Don't grey out background when popup is active.
165 * 7 1/08/98 10:32a Lawrance
166 * Grey out screen when a popup appears.
168 * 6 1/02/98 9:08p Lawrance
169 * Integrated art for popups, expanded options.
171 * 5 12/30/97 4:30p Sandeep
172 * Added conditional popups
174 * 4 12/26/97 10:01p Lawrance
175 * Allow keyboard shortcuts for popup buttons
177 * 3 12/24/97 9:49p Lawrance
178 * ensure mouse gets drawn when popup menu is up
180 * 2 12/24/97 8:54p Lawrance
181 * Integrating new popup code
183 * 1 12/24/97 3:51p Lawrance
190 #include "freespace.h"
191 #include "gamesequence.h"
198 #include "animplay.h"
199 #include "contexthelp.h"
200 #include "keycontrol.h"
203 #include "alphacolors.h"
206 #define POPUP_MAX_CHOICES 3 // max number of buttons allowed on popup
208 #define POPUP_MAX_LINE_CHARS 256 // max chars of msg text allowed per line
209 #define POPUP_MAX_LINES 30 // max lines of text allowed
210 #define POPUP_MAX_CHARS 2048 // total max chars
211 #define POPUP_INPUT_MAX_CHARS 255 // max length of input string
213 #define POPUP_NOCHANGE 100
214 #define POPUP_ABORT 101
216 int Popup_max_display[GR_NUM_RESOLUTIONS] = {
221 char *Popup_slider_name[GR_NUM_RESOLUTIONS] = {
226 int Popup_slider_coords[GR_NUM_RESOLUTIONS][4] = {
235 ////////////////////////////////////////////////////////////////
236 // Internal popup flags
237 ////////////////////////////////////////////////////////////////
238 #define PF_INPUT (1<<0) // contents of the box is an inputbox and a caption
240 ////////////////////////////////////////////////////////////////
242 ////////////////////////////////////////////////////////////////
243 typedef struct popup_info
245 int nchoices; // number of choices user can pick
246 char *button_text[POPUP_MAX_CHOICES]; // button text
247 int keypress[POPUP_MAX_CHOICES]; // button keypress shortcut
248 int shortcut_index[POPUP_MAX_CHOICES]; // what char should be underlines for shortcut
249 char raw_text[POPUP_MAX_CHARS]; // the unbroken text for the popup
250 char title[POPUP_MAX_LINE_CHARS]; // title text for popup (optional)
252 char msg_lines[POPUP_MAX_LINES][POPUP_MAX_LINE_CHARS]; // lines of text in popup
253 char input_text[POPUP_INPUT_MAX_CHARS]; // input box text (if this is an inputbox popup)
254 int max_input_text_len;
255 int web_cursor_flag[POPUP_MAX_CHOICES]; // flag for using web cursor over button
258 ////////////////////////////////////////////////////////////////
259 // UI Data and constants
260 ////////////////////////////////////////////////////////////////
261 UI_WINDOW Popup_window;
262 UI_BUTTON Popup_buttons[POPUP_MAX_CHOICES]; // actual lit buttons
263 UI_BUTTON Popup_button_regions[POPUP_MAX_CHOICES]; // fake buttons used for mouse detection over text
264 UI_INPUTBOX Popup_input; // input box for the popup
265 UI_SLIDER2 Popup_slider; // if we have more text in the popup than can be displayed at once
267 // extents for message portion of popup
268 int Popup_text_coords[GR_NUM_RESOLUTIONS][4] = {
278 // offset from the first y text line value to place the centered input box
279 int Popup_input_y_offset[GR_NUM_RESOLUTIONS] = {
284 // offset from the first y text line value to start drawing text
285 int Popup_input_text_y_offset[GR_NUM_RESOLUTIONS] = {
290 typedef struct popup_background
292 char *filename; // filename for background
293 int coords[2]; // coords to draw background at
296 ////////////////////////////////////////////////////////////////
298 ////////////////////////////////////////////////////////////////
299 static int Popup_is_active=0;
300 static int Popup_should_die=0; // popup should quit during the next iteration of its loop
302 static popup_info Popup_info;
303 static int Popup_flags;
305 static int Title_coords[GR_NUM_RESOLUTIONS][5] =
323 static int Button_regions[GR_NUM_RESOLUTIONS][3][4] = {
325 {464, 232, 510, 250}, // upper right pixel of text, lower right pixel of button
326 {464, 262, 510, 279},
330 {752, 373, 806, 406}, // upper right pixel of text, lower right pixel of button
331 {752, 421, 806, 461},
336 static int Button_coords[GR_NUM_RESOLUTIONS][3][2] =
339 {474, 224}, // upper left pixel
344 {758, 358}, // upper left pixel
350 static popup_background Popup_background[GR_NUM_RESOLUTIONS][4] =
358 {"2_Pop2", 206, 158},
359 {"2_Pop2", 206, 158},
360 {"2_Pop3", 206, 158},
364 #define BUTTON_NEGATIVE 0
365 #define BUTTON_POSITIVE 1
366 #define BUTTON_GENERIC_FIRST 2
367 #define BUTTON_GENERIC_SECOND 3
368 #define BUTTON_GENERIC_THIRD 4
369 static char *Popup_button_filenames[GR_NUM_RESOLUTIONS][2][5] =
372 {"Pop_00", // negative
373 "Pop_01", // positive
374 "Pop_02", // first generic
375 "Pop_03", // second generic
376 "Pop_04"}, // third generic
378 {"Pop_00", // negative
379 "Pop_01", // positive
380 "PopD_00", // first generic
381 "PopD_01", // second generic
382 "PopD_02"}, // third generic
385 {"2_Pop_00", // negative
386 "2_Pop_01", // positive
387 "2_Pop_02", // first generic
388 "2_Pop_03", // second generic
389 "2_Pop_04"}, // third generic
391 {"2_Pop_00", // negative
392 "2_Pop_01", // positive
393 "2_PopD_00", // first generic
394 "2_PopD_01", // second generic
395 "2_PopD_02"}, // third generic
399 int Popup_running_state;
400 int Popup_default_choice; // which choice is highlighted (ie what gets choosen when enter is pressed)
402 // see if any popup buttons have been pressed
403 // exit: POPUP_NOCHANGE => no buttons pressed
404 // >=0 => button index that was pressed
405 int popup_check_buttons(popup_info *pi)
410 for ( i = 0; i < pi->nchoices; i++ ) {
411 b = &Popup_button_regions[i];
412 if ( b->pressed() ) {
416 b = &Popup_buttons[i];
417 if ( b->pressed() ) {
422 return POPUP_NOCHANGE;
425 // maybe play a sound when key up/down is pressed to switch default choice
426 void popup_play_default_change_sound(popup_info *pi)
428 if ( pi->nchoices > 1 ) {
432 // only play if mouse not currently highlighting a choice
434 for ( i = 0; i < pi->nchoices; i++ ) {
435 br = &Popup_button_regions[i];
436 b = &Popup_buttons[i];
437 if ( br->button_down() ) {
442 if ( br->button_hilighted() && !b->button_down() ) {
447 if ( b->button_hilighted() ) {
453 gamesnd_play_iface(SND_USER_SELECT);
458 // do any key processing here
459 // input: pi => data about the popup
460 // k => key that was pressed
462 // exit: 0 .. nchoices-1 => choice selected through keypress
463 // POPUP_ABORT => abort the popup
464 // POPUP_NOCHANGE => nothing happenned
465 int popup_process_keys(popup_info *pi, int k, int flags)
470 return POPUP_NOCHANGE;
473 for ( i = 0; i < pi->nchoices; i++ ) {
474 if ( pi->keypress[i] == key_to_ascii(k) ) {
475 Popup_default_choice=i;
476 Popup_buttons[i].press_button();
484 // select the current default choice
485 return Popup_default_choice;
489 // only process the escape key if this flag is not set
490 if(!(flags & PF_IGNORE_ESC)){
498 popup_play_default_change_sound(pi);
499 Popup_default_choice++;
500 if ( Popup_default_choice >= pi->nchoices ) {
501 Popup_default_choice=0;
507 case KEY_SHIFTED+KEY_TAB:
508 popup_play_default_change_sound(pi);
509 Popup_default_choice--;
510 if ( Popup_default_choice < 0 ) {
511 Popup_default_choice=pi->nchoices-1;
520 masked_k = k & ~KEY_CTRLED; // take out CTRL modifier only
521 if ( (PF_ALLOW_DEAD_KEYS) && (Game_mode & GM_IN_MISSION) ) {
522 process_set_of_keys(masked_k, Dead_key_set_size, Dead_key_set);
523 button_info_do(&Player->bi); // call functions based on status of button_info bit vectors
526 return POPUP_NOCHANGE;
529 // Split off the title and break up the body lines
530 void popup_split_lines(popup_info *pi, int flags)
532 int nlines, i, body_offset = 0;
533 int n_chars[POPUP_MAX_LINES];
534 char *p_str[POPUP_MAX_LINES];
539 nlines = split_str(pi->raw_text, 1000, n_chars, p_str, POPUP_MAX_LINES);
540 Assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
542 if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
543 // get first line out
544 strncpy(pi->title, p_str[0], n_chars[0]);
545 pi->title[n_chars[0]] = 0;
549 if ( flags & PF_BODY_BIG ) {
553 nlines = split_str(pi->raw_text, Popup_text_coords[gr_screen.res][2], n_chars, p_str, POPUP_MAX_LINES);
554 Assert(nlines >= 0 && nlines <= POPUP_MAX_LINES );
556 pi->nlines = nlines - body_offset;
558 for ( i = 0; i < pi->nlines; i++ ) {
559 Assert(n_chars[i+body_offset] < POPUP_MAX_LINE_CHARS);
560 strncpy(pi->msg_lines[i], p_str[i+body_offset], n_chars[i+body_offset]);
561 pi->msg_lines[i][n_chars[i+body_offset]] = 0;
567 // figure out what filename to use for the button icon
568 char *popup_get_button_filename(popup_info *pi, int i, int flags)
573 // check for special button texts and if found, use specialized buttons for them.
574 if ((!stricmp(pi->button_text[i], POPUP_OK + 1) || !stricmp(pi->button_text[i], POPUP_YES + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
575 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
578 if ((!stricmp(pi->button_text[i], POPUP_CANCEL + 1) || !stricmp(pi->button_text[i], POPUP_NO + 1)) && !(flags & PF_NO_SPECIAL_BUTTONS)){
579 return Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
582 switch (pi->nchoices) {
587 if ( (flags & PF_USE_AFFIRMATIVE_ICON) && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
588 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
589 } else if ( flags & PF_USE_NEGATIVE_ICON && !(flags & PF_NO_SPECIAL_BUTTONS) ) {
590 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
592 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
597 if ( flags & PF_USE_NEGATIVE_ICON && i==0 ) {
598 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_NEGATIVE];
602 if ( flags & PF_USE_AFFIRMATIVE_ICON && i==1 ) {
603 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_POSITIVE];
608 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
610 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
618 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_FIRST];
621 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_SECOND];
624 fname = Popup_button_filenames[gr_screen.res][is_tiny][BUTTON_GENERIC_THIRD];
633 // bogus handling function for slider (we don't need it to do anything)
634 void popup_slider_bogus()
638 // init the Popup window
639 int popup_init(popup_info *pi, int flags)
643 popup_background *pbg;
646 if(pi->nchoices == 0){
647 pbg = &Popup_background[gr_screen.res][0];
649 pbg = &Popup_background[gr_screen.res][pi->nchoices-1];
652 // anytime in single player, and multiplayer, not in mission, go ahead and stop time
653 if ( (Game_mode & GM_NORMAL) || ((Game_mode && GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) ){
657 // create base window
658 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);
659 Popup_window.set_foreground_bmap(pbg->filename);
662 for (i=0; i<pi->nchoices; i++) {
663 b = &Popup_buttons[i];
664 // accommodate single-choice positive icon being positioned differently
665 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
666 b->create(&Popup_window, "", Button_coords[gr_screen.res][i+1][0], Button_coords[gr_screen.res][i+1][1], 30, 25, 0, 1);
668 b->create(&Popup_window, "", Button_coords[gr_screen.res][i][0], Button_coords[gr_screen.res][i][1], 30, 25, 0, 1);
671 fname = popup_get_button_filename(pi, i, flags);
672 b->set_bmaps(fname, 3, 0);
673 b->set_highlight_action(common_play_highlight_sound);
674 if ( pi->keypress[i] >= 0 ) {
675 b->set_hotkey(pi->keypress[i]);
678 // create invisible buttons to detect mouse presses... can't use mask since button region is dynamically sized
681 gr_get_string_size(&w, &h, pi->button_text[i]);
682 lx = Button_regions[gr_screen.res][i][0] - w;
683 b = &Popup_button_regions[i];
685 // accommodate single-choice positive icon being positioned differently
686 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
687 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);
689 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);
696 if (Web_cursor_bitmap >= 0) {
697 if (flags & PF_WEB_CURSOR_1) {
698 Popup_buttons[1].set_custom_cursor_bmap(Web_cursor_bitmap);
700 if (flags & PF_WEB_CURSOR_2) {
701 Popup_buttons[2].set_custom_cursor_bmap(Web_cursor_bitmap);
705 // if this is an input popup, create and center the popup
706 if(flags & PF_INPUT){
707 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);
708 Popup_input.set_focus();
711 Popup_default_choice=0;
712 Popup_should_die = 0;
714 if (flags & PF_RUN_STATE) {
715 Popup_running_state = 1;
717 Popup_running_state = 0;
720 popup_split_lines(pi, flags);
722 // create the popup slider (which we may not need to use
723 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,
724 Popup_slider_name[gr_screen.res], popup_slider_bogus, popup_slider_bogus, NULL);
729 // called when a popup goes away
730 void popup_close(popup_info *pi,int screen)
734 gamesnd_play_iface(SND_POPUP_DISAPPEAR); // play sound when popup disappears
736 for (i=0; i<pi->nchoices; i++ ) {
737 if ( pi->button_text[i] != NULL ) {
738 free(pi->button_text[i]);
739 pi->button_text[i] = NULL;
744 gr_free_screen(screen);
746 Popup_window.destroy();
747 anim_ignore_next_frametime(); // to avoid skips in animation since next frametime is saturated
751 Popup_running_state = 0;
753 // anytime in single player, and multiplayer, not in mission, go ahead and stop time
754 if ( (Game_mode & GM_NORMAL) || ((Game_mode && GM_MULTIPLAYER) && !(Game_mode & GM_IN_MISSION)) )
758 // set the popup text color
759 void popup_set_text_color(int flags)
761 if ( flags & PF_BODY_RED ) {
762 gr_set_color_fast(&Color_red);
766 if ( flags & PF_BODY_GREEN ) {
767 gr_set_color_fast(&Color_green);
771 if ( flags & PF_BODY_BLUE ) {
772 gr_set_color_fast(&Color_blue);
776 gr_set_color_fast(&Color_bright_blue);
779 // set the popup text color
780 void popup_set_title_color(int flags)
782 if ( flags & PF_TITLE_RED ) {
783 gr_set_color_fast(&Color_red);
787 if ( flags & PF_TITLE_GREEN ) {
788 gr_set_color_fast(&Color_green);
792 if ( flags & PF_TITLE_BLUE ) {
793 gr_set_color_fast(&Color_blue);
797 if ( flags & PF_TITLE_WHITE ) {
798 gr_set_color_fast(&Color_bright_white);
802 gr_set_color_fast(&Color_bright_blue);
805 // Draw the title centered within the popup
806 void popup_draw_title(int sy, char *line, int flags)
810 if ( flags & PF_TITLE_BIG ) {
816 gr_get_string_size(&w, &h, line);
817 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
819 popup_set_title_color(flags);
820 gr_string(sx,sy,line);
823 // calculate the starting display index
824 int popup_calc_starting_index(popup_info *pi)
826 // we're basically ignoring any titles here.
827 if(pi->nlines <= Popup_max_display[gr_screen.res]){
831 // otherwise, we want to see what item index the slider is on
832 return Popup_slider.get_currentItem();
835 // Figure out the y-coord to start drawing the popup text. The text
836 // is centered vertically within the popup.
837 int popup_calc_starting_y(popup_info *pi, int flags)
840 int num_lines = pi->nlines > Popup_max_display[gr_screen.res] ? Popup_max_display[gr_screen.res] : pi->nlines;
842 if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
843 if ( flags & PF_TITLE_BIG ) {
848 total_h += gr_get_font_height();
851 if ( flags & PF_BODY_BIG ) {
857 total_h += num_lines * gr_get_font_height();
858 sy = fl2i((Popup_text_coords[gr_screen.res][1] + Popup_text_coords[gr_screen.res][3]/2.0f) - total_h/2.0f + 0.5f);
860 // if this is an input style box, add in some y
861 if(flags & PF_INPUT){
862 sy += Popup_input_text_y_offset[gr_screen.res];
868 // Draw the message text nicely formatted in the popup
869 void popup_draw_msg_text(popup_info *pi, int flags)
875 // figure out the starting display
876 line_index = popup_calc_starting_index(pi);
878 // figure out the starting y:
879 sy = popup_calc_starting_y(pi, flags);
881 // draw title if required
882 if ( flags & (PF_TITLE | PF_TITLE_BIG) ) {
883 popup_draw_title(sy, pi->title, flags);
884 sy += gr_get_font_height();
888 if ( flags & PF_BODY_BIG ) {
894 popup_set_text_color(flags);
896 for ( i = line_index; i < pi->nlines; i++, line_count++ ) {
897 // if we've already displayed the max # of lines
898 if(line_count >= Popup_max_display[gr_screen.res]){
902 gr_get_string_size(&w, &h, pi->msg_lines[i]);
903 sx = fl2i(Title_coords[gr_screen.res][4] - w/2.0f + 0.5f);
904 gr_string(sx, sy + line_count * h, pi->msg_lines[i]);
910 gr_set_color_fast(&Color_bright_red);
911 gr_string(Title_coords[gr_screen.res][4], sy + (Popup_max_display[gr_screen.res]) * h, XSTR("More", 459));
914 gr_set_font(FONT1); // reset back to regular font size
917 // Draw the button text nicely formatted in the popup
918 void popup_draw_button_text(popup_info *pi, int flags)
922 gr_set_color_fast(&Color_bright_blue);
924 for ( i=0; i < pi->nchoices; i++ ) {
925 gr_get_string_size(&w, &h, pi->button_text[i]);
927 if ( (pi->nchoices == 1) && (flags&PF_USE_AFFIRMATIVE_ICON) ) {
928 sx = Button_regions[gr_screen.res][i+1][0]-w;
929 sy = Button_regions[gr_screen.res][i+1][1]+4;
931 sx = Button_regions[gr_screen.res][i][0]-w;
932 sy = Button_regions[gr_screen.res][i][1]+4;
935 gr_string(sx, sy, pi->button_text[i]);
937 // figure out where to draw underline char
938 if ( pi->shortcut_index[i] > 0 ) {
939 int cut=pi->shortcut_index[i];
940 char save_char=pi->button_text[i][cut];
941 pi->button_text[i][cut] = 0;
942 gr_get_string_size(&w, &h, pi->button_text[i]);
943 pi->button_text[i][cut] = save_char;
947 if ( pi->shortcut_index[i] >= 0 ) {
948 gr_printf(sx, sy, NOX("%c"), 95);
953 // See if any of the button should change appearance based on mouse position
954 void popup_force_draw_buttons(popup_info *pi)
956 int i,mouse_is_highlighting=0;
959 for ( i = 0; i < pi->nchoices; i++ ) {
960 br = &Popup_button_regions[i];
961 b = &Popup_buttons[i];
962 if ( br->button_down() ) {
964 mouse_is_highlighting=1;
968 if ( (b->button_hilighted()) || (br->button_hilighted() && !b->button_down()) ) {
969 Popup_default_choice=i;
970 mouse_is_highlighting=1;
975 // Only if mouse is not highlighting an option, let the default choice be drawn highlighted
976 if ( (!mouse_is_highlighting) && (pi->nchoices>1) ) {
977 for ( i = 0; i < pi->nchoices; i++ ) {
978 b = &Popup_buttons[i];
979 // highlight the default choice
980 if ( i == Popup_default_choice ) {
988 // 0..nchoices-1 => choice
989 int popup_do(popup_info *pi, int flags)
991 int screen_id, choice = -1, done = 0;
993 screen_id = gr_save_screen();
995 if ( popup_init(pi, flags) == -1 ){
1004 // if we were killed by a call to popup_kill_any_active(), kill the popup
1005 if(Popup_should_die){
1010 // if we're flagged as should be running the state underneath, then do so
1011 if(flags & PF_RUN_STATE){
1012 game_do_state(gameseq_get_state());
1014 // otherwise just run the common functions (for networking,etc)
1016 game_set_frametime(-1);
1017 game_do_state_common(gameseq_get_state(),flags & PF_NO_NETWORKING); // do stuff common to all states
1020 k = Popup_window.process(); // poll for input, handle mouse
1021 choice = popup_process_keys(pi, k, flags);
1022 if ( choice != POPUP_NOCHANGE ) {
1027 choice = popup_check_buttons(pi);
1028 if ( choice != POPUP_NOCHANGE ) {
1033 // don't draw anything
1034 if(!(flags & PF_RUN_STATE)){
1035 gr_restore_screen(screen_id);
1038 // if this is an input popup, store the input text
1039 if(flags & PF_INPUT){
1040 Popup_input.get_text(pi->input_text);
1043 Popup_window.draw();
1044 popup_force_draw_buttons(pi);
1045 popup_draw_msg_text(pi, flags);
1046 popup_draw_button_text(pi, flags);
1050 popup_close(pi,screen_id);
1054 int popup_do_with_condition(popup_info *pi, int flags, int(*condition)())
1056 int screen_id, choice = -1, done = 0;
1058 screen_id = gr_save_screen();
1059 if ( popup_init(pi, flags) == -1 )
1067 game_set_frametime(-1);
1068 game_do_state_common(gameseq_get_state()); // do stuff common to all states
1069 gr_restore_screen(screen_id);
1071 // draw one frame first
1072 Popup_window.draw();
1073 popup_force_draw_buttons(pi);
1074 popup_draw_msg_text(pi, flags);
1075 popup_draw_button_text(pi, flags);
1078 // test the condition function or process for the window
1079 if ((test = condition()) > 0) {
1083 k = Popup_window.process(); // poll for input, handle mouse
1084 choice = popup_process_keys(pi, k, flags);
1085 if ( choice != POPUP_NOCHANGE ) {
1090 choice = popup_check_buttons(pi);
1091 if ( choice != POPUP_NOCHANGE ) {
1098 popup_close(pi,screen_id);
1103 // maybe assign a keyboard shortcut to this button
1104 // input: pi => popup information so far
1105 // i => number of choice
1106 // str => string for button press
1107 void popup_maybe_assign_keypress(popup_info *pi, int n, char *str)
1109 int i,j,len=0,next_char_is_shortcut=0;
1112 pi->shortcut_index[n]=-1;
1116 pi->button_text[n] = (char*)malloc(len);
1117 memset(pi->button_text[n], 0, len);
1120 // copy chars over, watching for underline meta-char '&'
1121 for (i=0; i<len-1; i++) {
1122 if ( str[i] == '&' ) {
1123 pi->shortcut_index[n]=i;
1124 next_char_is_shortcut=1;
1126 if ( next_char_is_shortcut ) {
1127 next_char_is_shortcut=0;
1128 char first_char_string[2];
1129 first_char_string[0]=str[i];
1130 first_char_string[1]=0;
1131 strlwr(first_char_string);
1132 pi->keypress[n] = first_char_string[0];
1134 pi->button_text[n][j++]=str[i];
1139 // input: flags => flags => formatting specificatons (PF_...)
1140 // nchoices => number of choices popup has
1141 // text_1 => text for first button
1143 // text_n => text for last button
1144 // msg text => text msg for popup (can be of form "%s",pl->text)
1146 // exit: choice selected (0..nchoices-1)
1147 // will return -1 if there was an error or popup was aborted
1151 // rval = popup(0, 2, POPUP_OK, POPUP_CANCEL, "Sorry %s, try again", pl->callsign);
1152 int popup(int flags, int nchoices, ... )
1158 if ( Popup_is_active ) {
1159 Int3(); // should never happen
1163 Popup_flags = flags;
1165 Assert( nchoices > 0 && nchoices <= POPUP_MAX_CHOICES );
1166 Popup_info.nchoices = nchoices;
1168 va_start(args, nchoices );
1171 for (i=0; i<nchoices; i++ ) {
1172 s = va_arg( args, char * );
1173 Popup_info.button_text[i] = NULL;
1174 popup_maybe_assign_keypress(&Popup_info, i, s);
1178 format = va_arg( args, char * );
1179 Popup_info.raw_text[0] = 0;
1180 vsprintf(Popup_info.raw_text, format, args);
1182 Assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );
1184 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1187 Popup_is_active = 1;
1189 choice = popup_do( &Popup_info, flags );
1198 // determine if a popup is being drawn
1201 return Popup_is_active;
1204 // This function displays a popup message box and every frame it checks the condition() function
1205 // which is passed in as an argument.
1206 // If the condition() returns TRUE, the message box ends itself. This function returns whatever
1207 // the condition() did if the condition() occurred, and FALSE if the cancel button was pressed.
1208 int popup_till_condition(int (*condition)(), ...)
1215 if ( Popup_is_active ) {
1216 Int3(); // should never happen
1220 Popup_info.nchoices = 1;
1224 va_start(args, condition );
1227 s = va_arg( args, char * );
1228 Popup_info.button_text[0] = NULL;
1229 popup_maybe_assign_keypress(&Popup_info, 0, s);
1232 format = va_arg( args, char * );
1233 Popup_info.raw_text[0] = 0;
1234 vsprintf(Popup_info.raw_text, format, args);
1236 Popup_info.raw_text[POPUP_MAX_CHARS-1] = '\0';
1238 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1241 Popup_is_active = 1;
1243 choice = popup_do_with_condition( &Popup_info, flags, condition );
1252 // popup to return the value from an input box
1253 char *popup_input(int flags, char *caption, int max_output_len)
1255 if ( Popup_is_active ) {
1256 Int3(); // should never happen
1260 // make it an inputbox type popup
1261 Popup_flags = flags;
1262 Popup_flags |= PF_INPUT;
1264 // add a cancel button
1265 Popup_info.nchoices = 0;
1266 // popup_maybe_assign_keypress(&Popup_info, 0, "&Cancel");
1269 Assert(caption != NULL);
1270 strcpy(Popup_info.raw_text, caption);
1271 Assert(strlen(Popup_info.raw_text) < POPUP_MAX_CHARS );
1273 // set input text length
1274 if((max_output_len > POPUP_INPUT_MAX_CHARS) || (max_output_len == -1)){
1275 Popup_info.max_input_text_len = POPUP_INPUT_MAX_CHARS - 1;
1277 Popup_info.max_input_text_len = max_output_len;
1280 // zero the popup input text
1281 memset(Popup_info.input_text, 0, POPUP_INPUT_MAX_CHARS);
1283 gamesnd_play_iface(SND_POPUP_APPEAR); // play sound when popup appears
1286 Popup_is_active = 1;
1288 // if the user cancelled
1289 if(popup_do(&Popup_info, Popup_flags) == POPUP_ABORT){
1293 // otherwise return the
1294 return Popup_info.input_text;
1297 int popup_running_state()
1299 return Popup_running_state;
1302 // kill any active popup, forcing it to return -1 (similar to ESC)
1303 void popup_kill_any_active()
1305 if(Popup_is_active){
1306 Popup_should_die = 1;
1310 // change the text inside of the popup
1311 void popup_change_text(char *new_text)
1313 // copy the raw text
1314 strncpy(Popup_info.raw_text,new_text,POPUP_MAX_CHARS);
1316 // recalculate all display information
1317 popup_split_lines(&Popup_info,Popup_flags);