1 // Copyright Michael Martin, 2004.
2 
3 /*
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #include "setupmenu.h"
20 
21 #include "controls.h"
22 #include "options.h"
23 #include "setup.h"
24 #include "sounds.h"
25 #include "colors.h"
26 #include "libs/gfxlib.h"
27 #include "libs/graphics/gfx_common.h"
28 #include "libs/graphics/widgets.h"
29 #include "libs/graphics/tfb_draw.h"
30 #include "libs/strlib.h"
31 #include "libs/reslib.h"
32 #include "libs/inplib.h"
33 #include "libs/vidlib.h"
34 #include "libs/sound/sound.h"
35 #include "libs/resource/stringbank.h"
36 #include "libs/log.h"
37 #include "libs/memlib.h"
38 #include "resinst.h"
39 #include "nameref.h"
40 #include <math.h>
41 
42 
43 static STRING SetupTab;
44 
45 typedef struct setup_menu_state {
46 	BOOLEAN (*InputFunc) (struct setup_menu_state *pInputState);
47 
48 	BOOLEAN initialized;
49 	int anim_frame_count;
50 	DWORD NextTime;
51 } SETUP_MENU_STATE;
52 
53 static BOOLEAN DoSetupMenu (SETUP_MENU_STATE *pInputState);
54 static BOOLEAN done;
55 static WIDGET *current, *next;
56 
57 static int quit_main_menu (WIDGET *self, int event);
58 static int quit_sub_menu (WIDGET *self, int event);
59 static int do_graphics (WIDGET *self, int event);
60 static int do_audio (WIDGET *self, int event);
61 static int do_engine (WIDGET *self, int event);
62 static int do_resources (WIDGET *self, int event);
63 static int do_keyconfig (WIDGET *self, int event);
64 static int do_advanced (WIDGET *self, int event);
65 static int do_editkeys (WIDGET *self, int event);
66 static void change_template (WIDGET_CHOICE *self, int oldval);
67 static void rename_template (WIDGET_TEXTENTRY *self);
68 static void rebind_control (WIDGET_CONTROLENTRY *widget);
69 static void clear_control (WIDGET_CONTROLENTRY *widget);
70 
71 #define MENU_COUNT          8
72 #define CHOICE_COUNT       24
73 #define SLIDER_COUNT        4
74 #define BUTTON_COUNT       10
75 #define LABEL_COUNT         4
76 #define TEXTENTRY_COUNT     1
77 #define CONTROLENTRY_COUNT  7
78 
79 /* The space for our widgets */
80 static WIDGET_MENU_SCREEN menus[MENU_COUNT];
81 static WIDGET_CHOICE choices[CHOICE_COUNT];
82 static WIDGET_SLIDER sliders[SLIDER_COUNT];
83 static WIDGET_BUTTON buttons[BUTTON_COUNT];
84 static WIDGET_LABEL labels[LABEL_COUNT];
85 static WIDGET_TEXTENTRY textentries[TEXTENTRY_COUNT];
86 static WIDGET_CONTROLENTRY controlentries[CONTROLENTRY_COUNT];
87 
88 /* The hardcoded data that isn't strings */
89 
90 typedef int (*HANDLER)(WIDGET *, int);
91 
92 static int choice_widths[CHOICE_COUNT] = {
93 	3, 2, 3, 3, 2, 2, 2, 2, 2, 2,
94 	2, 2, 3, 2, 2, 3, 3, 2,	3, 3,
95 	3, 2, 2, 2 };
96 
97 static HANDLER button_handlers[BUTTON_COUNT] = {
98 	quit_main_menu, quit_sub_menu, do_graphics, do_engine,
99 	do_audio, do_resources, do_keyconfig, do_advanced, do_editkeys,
100 	do_keyconfig };
101 
102 /* These refer to uninitialized widgets, but that's OK; we'll fill
103  * them in before we touch them */
104 static WIDGET *main_widgets[] = {
105 	(WIDGET *)(&buttons[2]),
106 	(WIDGET *)(&buttons[3]),
107 	(WIDGET *)(&buttons[4]),
108 	(WIDGET *)(&buttons[5]),
109 	(WIDGET *)(&buttons[6]),
110 	(WIDGET *)(&buttons[7]),
111 	(WIDGET *)(&buttons[0]),
112 	NULL };
113 
114 static WIDGET *graphics_widgets[] = {
115 	(WIDGET *)(&choices[0]),
116 	(WIDGET *)(&choices[23]),
117 	(WIDGET *)(&choices[10]),
118 	(WIDGET *)(&sliders[3]),
119 	(WIDGET *)(&choices[2]),
120 	(WIDGET *)(&choices[3]),
121 	(WIDGET *)(&buttons[1]),
122 	NULL };
123 
124 static WIDGET *audio_widgets[] = {
125 	(WIDGET *)(&sliders[0]),
126 	(WIDGET *)(&sliders[1]),
127 	(WIDGET *)(&sliders[2]),
128 	(WIDGET *)(&choices[14]),
129 	(WIDGET *)(&choices[9]),
130 	(WIDGET *)(&choices[21]),
131 	(WIDGET *)(&choices[22]),
132 	(WIDGET *)(&buttons[1]),
133 	NULL };
134 
135 static WIDGET *engine_widgets[] = {
136 	(WIDGET *)(&choices[4]),
137 	(WIDGET *)(&choices[5]),
138 	(WIDGET *)(&choices[6]),
139 	(WIDGET *)(&choices[7]),
140 	(WIDGET *)(&choices[8]),
141 	(WIDGET *)(&choices[13]),
142 	(WIDGET *)(&choices[11]),
143 	(WIDGET *)(&choices[17]),
144 	(WIDGET *)(&buttons[1]),
145 	NULL };
146 
147 static WIDGET *advanced_widgets[] = {
148 #ifdef HAVE_OPENGL
149 	(WIDGET *)(&choices[1]),
150 #endif
151 	(WIDGET *)(&choices[12]),
152 	(WIDGET *)(&choices[15]),
153 	(WIDGET *)(&choices[16]),
154 	(WIDGET *)(&buttons[1]),
155 	NULL };
156 
157 static WIDGET *keyconfig_widgets[] = {
158 	(WIDGET *)(&choices[18]),
159 	(WIDGET *)(&choices[19]),
160 	(WIDGET *)(&labels[1]),
161 	(WIDGET *)(&buttons[8]),
162 	(WIDGET *)(&buttons[1]),
163 	NULL };
164 
165 static WIDGET *editkeys_widgets[] = {
166 	(WIDGET *)(&choices[20]),
167 	(WIDGET *)(&labels[2]),
168 	(WIDGET *)(&textentries[0]),
169 	(WIDGET *)(&controlentries[0]),
170 	(WIDGET *)(&controlentries[1]),
171 	(WIDGET *)(&controlentries[2]),
172 	(WIDGET *)(&controlentries[3]),
173 	(WIDGET *)(&controlentries[4]),
174 	(WIDGET *)(&controlentries[5]),
175 	(WIDGET *)(&controlentries[6]),
176 	(WIDGET *)(&buttons[9]),
177 	NULL };
178 
179 static WIDGET *incomplete_widgets[] = {
180 	(WIDGET *)(&labels[0]),
181 	(WIDGET *)(&buttons[1]),
182 	NULL };
183 
184 static const struct
185 {
186 	WIDGET **widgets;
187 	int bgIndex;
188 }
189 menu_defs[] =
190 {
191 	{main_widgets, 0},
192 	{graphics_widgets, 1},
193 	{audio_widgets, 1},
194 	{engine_widgets, 2},
195 	{incomplete_widgets, 3},
196 	{keyconfig_widgets, 1},
197 	{advanced_widgets, 2},
198 	{editkeys_widgets, 1},
199 	{NULL, 0}
200 };
201 
202 // Start with reasonable gamma bounds. These will get updated
203 // as we find out the actual bounds.
204 static float minGamma = 0.4f;
205 static float maxGamma = 2.5f;
206 // The gamma slider uses an exponential curve
207 // We use y = e^(2.1972*(x-1)) curve to give us a nice spread of
208 // gamma values 0.11 < g < 9.0 centered at g=1.0
209 #define GAMMA_CURVE_B  2.1972f
210 static float minGammaX;
211 static float maxGammaX;
212 
213 
214 static int
number_res_options(void)215 number_res_options (void)
216 {
217 	if (TFB_SupportsHardwareScaling ())
218 	{
219 		return 5;
220 	}
221 	else
222 	{
223 		return 2;
224 	}
225 }
226 
227 static int
quit_main_menu(WIDGET * self,int event)228 quit_main_menu (WIDGET *self, int event)
229 {
230 	if (event == WIDGET_EVENT_SELECT)
231 	{
232 		next = NULL;
233 		return TRUE;
234 	}
235 	(void)self;
236 	return FALSE;
237 }
238 
239 static int
quit_sub_menu(WIDGET * self,int event)240 quit_sub_menu (WIDGET *self, int event)
241 {
242 	if (event == WIDGET_EVENT_SELECT)
243 	{
244 		next = (WIDGET *)(&menus[0]);
245 		(*next->receiveFocus) (next, WIDGET_EVENT_SELECT);
246 		return TRUE;
247 	}
248 	(void)self;
249 	return FALSE;
250 }
251 
252 static int
do_graphics(WIDGET * self,int event)253 do_graphics (WIDGET *self, int event)
254 {
255 	if (event == WIDGET_EVENT_SELECT)
256 	{
257 		next = (WIDGET *)(&menus[1]);
258 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
259 		return TRUE;
260 	}
261 	(void)self;
262 	return FALSE;
263 }
264 
265 static int
do_audio(WIDGET * self,int event)266 do_audio (WIDGET *self, int event)
267 {
268 	if (event == WIDGET_EVENT_SELECT)
269 	{
270 		next = (WIDGET *)(&menus[2]);
271 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
272 		return TRUE;
273 	}
274 	(void)self;
275 	return FALSE;
276 }
277 
278 static int
do_engine(WIDGET * self,int event)279 do_engine (WIDGET *self, int event)
280 {
281 	if (event == WIDGET_EVENT_SELECT)
282 	{
283 		next = (WIDGET *)(&menus[3]);
284 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
285 		return TRUE;
286 	}
287 	(void)self;
288 	return FALSE;
289 }
290 
291 static int
do_resources(WIDGET * self,int event)292 do_resources (WIDGET *self, int event)
293 {
294 	if (event == WIDGET_EVENT_SELECT)
295 	{
296 		next = (WIDGET *)(&menus[4]);
297 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
298 		return TRUE;
299 	}
300 	(void)self;
301 	return FALSE;
302 }
303 
304 static int
do_keyconfig(WIDGET * self,int event)305 do_keyconfig (WIDGET *self, int event)
306 {
307 	if (event == WIDGET_EVENT_SELECT)
308 	{
309 		next = (WIDGET *)(&menus[5]);
310 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
311 		return TRUE;
312 	}
313 	(void)self;
314 	return FALSE;
315 }
316 
317 static int
do_advanced(WIDGET * self,int event)318 do_advanced (WIDGET *self, int event)
319 {
320 	if (event == WIDGET_EVENT_SELECT)
321 	{
322 		next = (WIDGET *)(&menus[6]);
323 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
324 		return TRUE;
325 	}
326 	(void)self;
327 	return FALSE;
328 }
329 
330 static void
populate_editkeys(int templat)331 populate_editkeys (int templat)
332 {
333 	int i, j;
334 
335 	strncpy (textentries[0].value, input_templates[templat].name, textentries[0].maxlen);
336 	textentries[0].value[textentries[0].maxlen-1] = 0;
337 
338 	for (i = 0; i < NUM_KEYS; i++)
339 	{
340 		for (j = 0; j < 2; j++)
341 		{
342 			InterrogateInputState (templat, i, j, controlentries[i].controlname[j], WIDGET_CONTROLENTRY_WIDTH);
343 		}
344 	}
345 }
346 
347 static int
do_editkeys(WIDGET * self,int event)348 do_editkeys (WIDGET *self, int event)
349 {
350 	if (event == WIDGET_EVENT_SELECT)
351 	{
352 		next = (WIDGET *)(&menus[7]);
353 		/* Prepare the components */
354 		choices[20].selected = 0;
355 
356 		populate_editkeys (0);
357 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
358 		return TRUE;
359 	}
360 	(void)self;
361 	return FALSE;
362 }
363 
364 static void
change_template(WIDGET_CHOICE * self,int oldval)365 change_template (WIDGET_CHOICE *self, int oldval)
366 {
367 	(void) oldval;
368 	populate_editkeys (self->selected);
369 }
370 
371 static void
rename_template(WIDGET_TEXTENTRY * self)372 rename_template (WIDGET_TEXTENTRY *self)
373 {
374 	/* TODO: This will have to change if the size of the
375 	   input_templates name is changed.  It would probably be nice
376 	   to track this symbolically or ensure that self->value's
377 	   buffer is always at least this big; this will require some
378 	   reworking of widgets */
379 	strncpy (input_templates[choices[20].selected].name, self->value, 30);
380 	input_templates[choices[20].selected].name[29] = 0;
381 }
382 
383 #define NUM_STEPS 20
384 #define X_STEP (SCREEN_WIDTH / NUM_STEPS)
385 #define Y_STEP (SCREEN_HEIGHT / NUM_STEPS)
386 #define MENU_FRAME_RATE (ONE_SECOND / 20)
387 
388 static void
SetDefaults(void)389 SetDefaults (void)
390 {
391 	GLOBALOPTS opts;
392 
393 	GetGlobalOptions (&opts);
394 	if (opts.res == OPTVAL_CUSTOM)
395 	{
396 		choices[0].numopts = number_res_options () + 1;
397 	}
398 	else
399 	{
400 		choices[0].numopts = number_res_options ();
401 	}
402 	choices[0].selected = opts.res;
403 	choices[1].selected = opts.driver;
404 	choices[2].selected = opts.scaler;
405 	choices[3].selected = opts.scanlines;
406 	choices[4].selected = opts.menu;
407 	choices[5].selected = opts.text;
408 	choices[6].selected = opts.cscan;
409 	choices[7].selected = opts.scroll;
410 	choices[8].selected = opts.subtitles;
411 	choices[9].selected = opts.music3do;
412 	choices[10].selected = opts.fullscreen;
413 	choices[11].selected = opts.intro;
414 	choices[12].selected = opts.fps;
415 	choices[13].selected = opts.meleezoom;
416 	choices[14].selected = opts.stereo;
417 	choices[15].selected = opts.adriver;
418 	choices[16].selected = opts.aquality;
419 	choices[17].selected = opts.shield;
420 	choices[18].selected = opts.player1;
421 	choices[19].selected = opts.player2;
422 	choices[20].selected = 0;
423 	choices[21].selected = opts.musicremix;
424 	choices[22].selected = opts.speech;
425 	choices[23].selected = opts.keepaspect;
426 
427 	sliders[0].value = opts.musicvol;
428 	sliders[1].value = opts.sfxvol;
429 	sliders[2].value = opts.speechvol;
430 	sliders[3].value = opts.gamma;
431 }
432 
433 static void
PropagateResults(void)434 PropagateResults (void)
435 {
436 	GLOBALOPTS opts;
437 	opts.res = choices[0].selected;
438 	opts.driver = choices[1].selected;
439 	opts.scaler = choices[2].selected;
440 	opts.scanlines = choices[3].selected;
441 	opts.menu = choices[4].selected;
442 	opts.text = choices[5].selected;
443 	opts.cscan = choices[6].selected;
444 	opts.scroll = choices[7].selected;
445 	opts.subtitles = choices[8].selected;
446 	opts.music3do = choices[9].selected;
447 	opts.fullscreen = choices[10].selected;
448 	opts.intro = choices[11].selected;
449 	opts.fps = choices[12].selected;
450 	opts.meleezoom = choices[13].selected;
451 	opts.stereo = choices[14].selected;
452 	opts.adriver = choices[15].selected;
453 	opts.aquality = choices[16].selected;
454 	opts.shield = choices[17].selected;
455 	opts.player1 = choices[18].selected;
456 	opts.player2 = choices[19].selected;
457 	opts.musicremix = choices[21].selected;
458 	opts.speech = choices[22].selected;
459 	opts.keepaspect = choices[23].selected;
460 
461 	opts.musicvol = sliders[0].value;
462 	opts.sfxvol = sliders[1].value;
463 	opts.speechvol = sliders[2].value;
464 	opts.gamma = sliders[3].value;
465 	SetGlobalOptions (&opts);
466 }
467 
468 static BOOLEAN
DoSetupMenu(SETUP_MENU_STATE * pInputState)469 DoSetupMenu (SETUP_MENU_STATE *pInputState)
470 {
471 	/* Cancel any presses of the Pause key. */
472 	GamePaused = FALSE;
473 
474 	if (!pInputState->initialized)
475 	{
476 		SetDefaultMenuRepeatDelay ();
477 		pInputState->NextTime = GetTimeCounter ();
478 		SetDefaults ();
479 		Widget_SetFont (StarConFont);
480 		Widget_SetWindowColors (SHADOWBOX_BACKGROUND_COLOR,
481 				SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
482 
483 		current = NULL;
484 		next = (WIDGET *)(&menus[0]);
485 		(*next->receiveFocus) (next, WIDGET_EVENT_DOWN);
486 
487 		pInputState->initialized = TRUE;
488 	}
489 	if (current != next)
490 	{
491 		SetTransitionSource (NULL);
492 	}
493 
494 	BatchGraphics ();
495 	(*next->draw)(next, 0, 0);
496 
497 	if (current != next)
498 	{
499 		ScreenTransition (3, NULL);
500 		current = next;
501 	}
502 
503 	UnbatchGraphics ();
504 
505 	if (PulsedInputState.menu[KEY_MENU_UP])
506 	{
507 		Widget_Event (WIDGET_EVENT_UP);
508 	}
509 	else if (PulsedInputState.menu[KEY_MENU_DOWN])
510 	{
511 		Widget_Event (WIDGET_EVENT_DOWN);
512 	}
513 	else if (PulsedInputState.menu[KEY_MENU_LEFT])
514 	{
515 		Widget_Event (WIDGET_EVENT_LEFT);
516 	}
517 	else if (PulsedInputState.menu[KEY_MENU_RIGHT])
518 	{
519 		Widget_Event (WIDGET_EVENT_RIGHT);
520 	}
521 	if (PulsedInputState.menu[KEY_MENU_SELECT])
522 	{
523 		Widget_Event (WIDGET_EVENT_SELECT);
524 	}
525 	if (PulsedInputState.menu[KEY_MENU_CANCEL])
526 	{
527 		Widget_Event (WIDGET_EVENT_CANCEL);
528 	}
529 	if (PulsedInputState.menu[KEY_MENU_DELETE])
530 	{
531 		Widget_Event (WIDGET_EVENT_DELETE);
532 	}
533 
534 	SleepThreadUntil (pInputState->NextTime + MENU_FRAME_RATE);
535 	pInputState->NextTime = GetTimeCounter ();
536 	return !((GLOBAL (CurrentActivity) & CHECK_ABORT) ||
537 		 (next == NULL));
538 }
539 
540 static void
redraw_menu(void)541 redraw_menu (void)
542 {
543 	BatchGraphics ();
544 	(*next->draw)(next, 0, 0);
545 	UnbatchGraphics ();
546 }
547 
548 static BOOLEAN
OnTextEntryChange(TEXTENTRY_STATE * pTES)549 OnTextEntryChange (TEXTENTRY_STATE *pTES)
550 {
551 	WIDGET_TEXTENTRY *widget = (WIDGET_TEXTENTRY *) pTES->CbParam;
552 
553 	widget->cursor_pos = pTES->CursorPos;
554 	if (pTES->JoystickMode)
555 		widget->state |= WTE_BLOCKCUR;
556 	else
557 		widget->state &= ~WTE_BLOCKCUR;
558 
559 	// XXX TODO: Here, we can examine the text entered so far
560 	// to make sure it fits on the screen, for example,
561 	// and return FALSE to disallow the last change
562 
563 	return TRUE; // allow change
564 }
565 
566 static BOOLEAN
OnTextEntryFrame(TEXTENTRY_STATE * pTES)567 OnTextEntryFrame (TEXTENTRY_STATE *pTES)
568 {
569 	redraw_menu ();
570 
571 	SleepThreadUntil (pTES->NextTime);
572 	pTES->NextTime = GetTimeCounter () + MENU_FRAME_RATE;
573 
574 	return TRUE; // continue
575 }
576 
577 static int
OnTextEntryEvent(WIDGET_TEXTENTRY * widget)578 OnTextEntryEvent (WIDGET_TEXTENTRY *widget)
579 {	// Going to edit the text
580 	TEXTENTRY_STATE tes;
581 	UNICODE revert_buf[256];
582 
583 	// position cursor at the end of text
584 	widget->cursor_pos = utf8StringCount (widget->value);
585 	widget->state = WTE_EDITING;
586 	redraw_menu ();
587 
588 	// make a backup copy for revert on cancel
589 	utf8StringCopy (revert_buf, sizeof (revert_buf), widget->value);
590 
591 	// text entry setup
592 	tes.Initialized = FALSE;
593 	tes.NextTime = GetTimeCounter () + MENU_FRAME_RATE;
594 	tes.BaseStr = widget->value;
595 	tes.MaxSize = widget->maxlen;
596 	tes.CursorPos = widget->cursor_pos;
597 	tes.CbParam = widget;
598 	tes.ChangeCallback = OnTextEntryChange;
599 	tes.FrameCallback = OnTextEntryFrame;
600 
601 	SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_SELECT);
602 	if (!DoTextEntry (&tes))
603 	{	// editing failed (canceled) -- revert the changes
604 		utf8StringCopy (widget->value, widget->maxlen, revert_buf);
605 	}
606 	else
607 	{
608 		if (widget->onChange)
609 		{
610 			(*(widget->onChange))(widget);
611 		}
612 	}
613 	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
614 
615 	widget->state = WTE_NORMAL;
616 	redraw_menu ();
617 
618 	return TRUE; // event handled
619 }
620 
621 static inline float
gammaCurve(float x)622 gammaCurve (float x)
623 {
624 	// The slider uses an exponential curve
625 	return exp ((x - 1) * GAMMA_CURVE_B);
626 }
627 
628 static inline float
solveGammaCurve(float y)629 solveGammaCurve (float y)
630 {
631 	return log (y) / GAMMA_CURVE_B + 1;
632 }
633 
634 static int
gammaToSlider(float gamma)635 gammaToSlider (float gamma)
636 {
637 	const float x = solveGammaCurve (gamma);
638 	const float step = (maxGammaX - minGammaX) / 100;
639 	return (int) ((x - minGammaX) / step + 0.5);
640 }
641 
642 static float
sliderToGamma(int value)643 sliderToGamma (int value)
644 {
645 	const float step = (maxGammaX - minGammaX) / 100;
646 	const float x = minGammaX + step * value;
647 	const float g = gammaCurve (x);
648 	// report any value that is close enough as 1.0
649 	return (fabs (g - 1.0f) < 0.001f) ? 1.0f : g;
650 }
651 
652 static void
updateGammaBounds(bool useUpper)653 updateGammaBounds (bool useUpper)
654 {
655 	float g, x;
656 	int slider;
657 
658 	// The slider uses an exponential curve.
659 	// Calculate where on the curve the min and max gamma values are
660 	minGammaX = solveGammaCurve (minGamma);
661 	maxGammaX = solveGammaCurve (maxGamma);
662 
663 	// We have 100 discrete steps through the range, so the slider may
664 	// skip over a 1.0 gamma. We need to ensure that there always is
665 	// a 1.0 on the slider by tweaking the range (expanding/contracting).
666 	slider = gammaToSlider (1.0f);
667 	g = sliderToGamma (slider);
668 	if (g == 1.0f)
669 		return; // no adjustment needed
670 
671 	x = solveGammaCurve (g);
672 	if (useUpper)
673 	{	// Move the upper bound up or down to land on 1.0
674 		const float d = (x - 1.0f) * 100 / slider;
675 		maxGammaX -= d;
676 		maxGamma = gammaCurve (maxGammaX);
677 	}
678 	else
679 	{	// Move the lower bound up or down to land on 1.0
680 		const float d = (x - 1.0f) * 100 / (100 - slider);
681 		minGammaX -= d;
682 		minGamma = gammaCurve (minGammaX);
683 	}
684 }
685 
686 static int
gamma_HandleEventSlider(WIDGET * _self,int event)687 gamma_HandleEventSlider (WIDGET *_self, int event)
688 {
689 	WIDGET_SLIDER *self = (WIDGET_SLIDER *)_self;
690 	int prevValue = self->value;
691 	float gamma;
692 	bool set;
693 
694 	switch (event)
695 	{
696 	case WIDGET_EVENT_LEFT:
697 		self->value -= self->step;
698 		break;
699 	case WIDGET_EVENT_RIGHT:
700 		self->value += self->step;
701 		break;
702 	default:
703 		return FALSE;
704 	}
705 
706 	// Limit the slider to values accepted by gfx subsys
707 	gamma = sliderToGamma (self->value);
708 	set = TFB_SetGamma (gamma);
709 	if (!set)
710 	{	// revert
711 		self->value = prevValue;
712 		gamma = sliderToGamma (self->value);
713 	}
714 
715 	// Grow or shrink the range based on accepted values
716 	if (gamma < minGamma || (!set && event == WIDGET_EVENT_LEFT))
717 	{
718 		minGamma = gamma;
719 		updateGammaBounds (true);
720 		// at the lowest end
721 		self->value = 0;
722 	}
723 	else if (gamma > maxGamma || (!set && event == WIDGET_EVENT_RIGHT))
724 	{
725 		maxGamma = gamma;
726 		updateGammaBounds (false);
727 		// at the highest end
728 		self->value = 100;
729 	}
730 	return TRUE;
731 }
732 
733 static void
gamma_DrawValue(WIDGET_SLIDER * self,int x,int y)734 gamma_DrawValue (WIDGET_SLIDER *self, int x, int y)
735 {
736 	TEXT t;
737 	char buf[16];
738 	float gamma = sliderToGamma (self->value);
739 	snprintf (buf, sizeof buf, "%.4f", gamma);
740 
741 	t.baseline.x = x;
742 	t.baseline.y = y;
743 	t.align = ALIGN_CENTER;
744 	t.CharCount = ~0;
745 	t.pStr = buf;
746 
747 	font_DrawText (&t);
748 }
749 
750 static void
rebind_control(WIDGET_CONTROLENTRY * widget)751 rebind_control (WIDGET_CONTROLENTRY *widget)
752 {
753 	int templat = choices[20].selected;
754 	int control = widget->controlindex;
755 	int index = widget->highlighted;
756 
757 	FlushInput ();
758 	DrawLabelAsWindow (&labels[3], NULL);
759 	RebindInputState (templat, control, index);
760 	populate_editkeys (templat);
761 	FlushInput ();
762 }
763 
764 static void
clear_control(WIDGET_CONTROLENTRY * widget)765 clear_control (WIDGET_CONTROLENTRY *widget)
766 {
767 	int templat = choices[20].selected;
768 	int control = widget->controlindex;
769 	int index = widget->highlighted;
770 
771 	RemoveInputState (templat, control, index);
772 	populate_editkeys (templat);
773 }
774 
775 static int
count_widgets(WIDGET ** widgets)776 count_widgets (WIDGET **widgets)
777 {
778 	int count;
779 
780 	for (count = 0; *widgets != NULL; ++widgets, ++count)
781 		;
782 	return count;
783 }
784 
785 static stringbank *bank = NULL;
786 static FRAME setup_frame = NULL;
787 
788 static void
init_widgets(void)789 init_widgets (void)
790 {
791 	const char *buffer[100], *str, *title;
792 	int count, i, index;
793 
794 	if (bank == NULL)
795 	{
796 		bank = StringBank_Create ();
797 	}
798 
799 	if (setup_frame == NULL)
800 	{
801 		setup_frame = CaptureDrawable (LoadGraphic (MENUBKG_PMAP_ANIM));
802 	}
803 
804 	count = GetStringTableCount (SetupTab);
805 
806 	if (count < 3)
807 	{
808 		log_add (log_Fatal, "PANIC: Setup string table too short to even hold all indices!");
809 		exit (EXIT_FAILURE);
810 	}
811 
812 	/* Menus */
813 	title = StringBank_AddOrFindString (bank, GetStringAddress (SetAbsStringTableIndex (SetupTab, 0)));
814 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, 1)), '\n', 100, buffer, bank) != MENU_COUNT)
815 	{
816 		/* TODO: Ignore extras instead of dying. */
817 		log_add (log_Fatal, "PANIC: Incorrect number of Menu Subtitles");
818 		exit (EXIT_FAILURE);
819 	}
820 
821 	for (i = 0; i < MENU_COUNT; i++)
822 	{
823 		menus[i].tag = WIDGET_TYPE_MENU_SCREEN;
824 		menus[i].parent = NULL;
825 		menus[i].handleEvent = Widget_HandleEventMenuScreen;
826 		menus[i].receiveFocus = Widget_ReceiveFocusMenuScreen;
827 		menus[i].draw = Widget_DrawMenuScreen;
828 		menus[i].height = Widget_HeightFullScreen;
829 		menus[i].width = Widget_WidthFullScreen;
830 		menus[i].title = title;
831 		menus[i].subtitle = buffer[i];
832 		menus[i].bgStamp.origin.x = 0;
833 		menus[i].bgStamp.origin.y = 0;
834 		menus[i].bgStamp.frame = SetAbsFrameIndex (setup_frame, menu_defs[i].bgIndex);
835 		menus[i].num_children = count_widgets (menu_defs[i].widgets);
836 		menus[i].child = menu_defs[i].widgets;
837 		menus[i].highlighted = 0;
838 	}
839 	if (menu_defs[i].widgets != NULL)
840 	{
841 		log_add (log_Error, "Menu definition array has more items!");
842 	}
843 
844 	/* Options */
845 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, 2)), '\n', 100, buffer, bank) != CHOICE_COUNT)
846 	{
847 		log_add (log_Fatal, "PANIC: Incorrect number of Choice Options");
848 		exit (EXIT_FAILURE);
849 	}
850 
851 	for (i = 0; i < CHOICE_COUNT; i++)
852 	{
853 		choices[i].tag = WIDGET_TYPE_CHOICE;
854 		choices[i].parent = NULL;
855 		choices[i].handleEvent = Widget_HandleEventChoice;
856 		choices[i].receiveFocus = Widget_ReceiveFocusChoice;
857 		choices[i].draw = Widget_DrawChoice;
858 		choices[i].height = Widget_HeightChoice;
859 		choices[i].width = Widget_WidthFullScreen;
860 		choices[i].category = buffer[i];
861 		choices[i].numopts = 0;
862 		choices[i].options = NULL;
863 		choices[i].selected = 0;
864 		choices[i].highlighted = 0;
865 		choices[i].maxcolumns = choice_widths[i];
866 		choices[i].onChange = NULL;
867 	}
868 
869 	/* Fill in the options now */
870 	index = 3;  /* Index into string table */
871 	for (i = 0; i < CHOICE_COUNT; i++)
872 	{
873 		int j, optcount;
874 
875 		if (index >= count)
876 		{
877 			log_add (log_Fatal, "PANIC: String table cut short while reading choices");
878 			exit (EXIT_FAILURE);
879 		}
880 		str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
881 		optcount = SplitString (str, '\n', 100, buffer, bank);
882 		choices[i].numopts = optcount;
883 		choices[i].options = HMalloc (optcount * sizeof (CHOICE_OPTION));
884 		for (j = 0; j < optcount; j++)
885 		{
886 			choices[i].options[j].optname = buffer[j];
887 			choices[i].options[j].tooltip[0] = "";
888 			choices[i].options[j].tooltip[1] = "";
889 			choices[i].options[j].tooltip[2] = "";
890 		}
891 		for (j = 0; j < optcount; j++)
892 		{
893 			int k, tipcount;
894 
895 			if (index >= count)
896 			{
897 				log_add (log_Fatal, "PANIC: String table cut short while reading choices");
898 				exit (EXIT_FAILURE);
899 			}
900 			str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
901 			tipcount = SplitString (str, '\n', 100, buffer, bank);
902 			if (tipcount > 3)
903 			{
904 				tipcount = 3;
905 			}
906 			for (k = 0; k < tipcount; k++)
907 			{
908 				choices[i].options[j].tooltip[k] = buffer[k];
909 			}
910 		}
911 	}
912 
913 	/* The first choice is resolution, and is handled specially */
914 	choices[0].numopts = number_res_options ();
915 
916 	/* Choices 18-20 are also special, being the names of the key configurations */
917 	for (i = 0; i < 6; i++)
918 	{
919 		choices[18].options[i].optname = input_templates[i].name;
920 		choices[19].options[i].optname = input_templates[i].name;
921 		choices[20].options[i].optname = input_templates[i].name;
922 	}
923 
924 	/* Choice 20 has a special onChange handler, too. */
925 	choices[20].onChange = change_template;
926 
927 	/* Sliders */
928 	if (index >= count)
929 	{
930 		log_add (log_Fatal, "PANIC: String table cut short while reading sliders");
931 		exit (EXIT_FAILURE);
932 	}
933 
934 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != SLIDER_COUNT)
935 	{
936 		/* TODO: Ignore extras instead of dying. */
937 		log_add (log_Fatal, "PANIC: Incorrect number of Slider Options");
938 		exit (EXIT_FAILURE);
939 	}
940 
941 	for (i = 0; i < SLIDER_COUNT; i++)
942 	{
943 		sliders[i].tag = WIDGET_TYPE_SLIDER;
944 		sliders[i].parent = NULL;
945 		sliders[i].handleEvent = Widget_HandleEventSlider;
946 		sliders[i].receiveFocus = Widget_ReceiveFocusSimple;
947 		sliders[i].draw = Widget_DrawSlider;
948 		sliders[i].height = Widget_HeightOneLine;
949 		sliders[i].width = Widget_WidthFullScreen;
950 		sliders[i].draw_value = Widget_Slider_DrawValue;
951 		sliders[i].min = 0;
952 		sliders[i].max = 100;
953 		sliders[i].step = 5;
954 		sliders[i].value = 75;
955 		sliders[i].category = buffer[i];
956 		sliders[i].tooltip[0] = "";
957 		sliders[i].tooltip[1] = "";
958 		sliders[i].tooltip[2] = "";
959 	}
960 	// gamma is a special case
961 	sliders[3].step = 1;
962 	sliders[3].handleEvent = gamma_HandleEventSlider;
963 	sliders[3].draw_value = gamma_DrawValue;
964 
965 	for (i = 0; i < SLIDER_COUNT; i++)
966 	{
967 		int j, tipcount;
968 
969 		if (index >= count)
970 		{
971 			log_add (log_Fatal, "PANIC: String table cut short while reading sliders");
972 			exit (EXIT_FAILURE);
973 		}
974 		str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
975 		tipcount = SplitString (str, '\n', 100, buffer, bank);
976 		if (tipcount > 3)
977 		{
978 			tipcount = 3;
979 		}
980 		for (j = 0; j < tipcount; j++)
981 		{
982 			sliders[i].tooltip[j] = buffer[j];
983 		}
984 	}
985 
986 	/* Buttons */
987 	if (index >= count)
988 	{
989 		log_add (log_Fatal, "PANIC: String table cut short while reading buttons");
990 		exit (EXIT_FAILURE);
991 	}
992 
993 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != BUTTON_COUNT)
994 	{
995 		/* TODO: Ignore extras instead of dying. */
996 		log_add (log_Fatal, "PANIC: Incorrect number of Button Options");
997 		exit (EXIT_FAILURE);
998 	}
999 
1000 	for (i = 0; i < BUTTON_COUNT; i++)
1001 	{
1002 		buttons[i].tag = WIDGET_TYPE_BUTTON;
1003 		buttons[i].parent = NULL;
1004 		buttons[i].handleEvent = button_handlers[i];
1005 		buttons[i].receiveFocus = Widget_ReceiveFocusSimple;
1006 		buttons[i].draw = Widget_DrawButton;
1007 		buttons[i].height = Widget_HeightOneLine;
1008 		buttons[i].width = Widget_WidthFullScreen;
1009 		buttons[i].name = buffer[i];
1010 		buttons[i].tooltip[0] = "";
1011 		buttons[i].tooltip[1] = "";
1012 		buttons[i].tooltip[2] = "";
1013 	}
1014 
1015 	for (i = 0; i < BUTTON_COUNT; i++)
1016 	{
1017 		int j, tipcount;
1018 
1019 		if (index >= count)
1020 		{
1021 			log_add (log_Fatal, "PANIC: String table cut short while reading buttons");
1022 			exit (EXIT_FAILURE);
1023 		}
1024 		str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
1025 		tipcount = SplitString (str, '\n', 100, buffer, bank);
1026 		if (tipcount > 3)
1027 		{
1028 			tipcount = 3;
1029 		}
1030 		for (j = 0; j < tipcount; j++)
1031 		{
1032 			buttons[i].tooltip[j] = buffer[j];
1033 		}
1034 	}
1035 
1036 	/* Labels */
1037 	if (index >= count)
1038 	{
1039 		log_add (log_Fatal, "PANIC: String table cut short while reading labels");
1040 		exit (EXIT_FAILURE);
1041 	}
1042 
1043 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != LABEL_COUNT)
1044 	{
1045 		/* TODO: Ignore extras instead of dying. */
1046 		log_add (log_Fatal, "PANIC: Incorrect number of Label Options");
1047 		exit (EXIT_FAILURE);
1048 	}
1049 
1050 	for (i = 0; i < LABEL_COUNT; i++)
1051 	{
1052 		labels[i].tag = WIDGET_TYPE_LABEL;
1053 		labels[i].parent = NULL;
1054 		labels[i].handleEvent = Widget_HandleEventIgnoreAll;
1055 		labels[i].receiveFocus = Widget_ReceiveFocusRefuseFocus;
1056 		labels[i].draw = Widget_DrawLabel;
1057 		labels[i].height = Widget_HeightLabel;
1058 		labels[i].width = Widget_WidthFullScreen;
1059 		labels[i].line_count = 0;
1060 		labels[i].lines = NULL;
1061 	}
1062 
1063 	for (i = 0; i < LABEL_COUNT; i++)
1064 	{
1065 		int j, linecount;
1066 
1067 		if (index >= count)
1068 		{
1069 			log_add (log_Fatal, "PANIC: String table cut short while reading labels");
1070 			exit (EXIT_FAILURE);
1071 		}
1072 		str = GetStringAddress (SetAbsStringTableIndex (SetupTab, index++));
1073 		linecount = SplitString (str, '\n', 100, buffer, bank);
1074 		labels[i].line_count = linecount;
1075 		labels[i].lines = (const char **)HMalloc(linecount * sizeof(const char *));
1076 		for (j = 0; j < linecount; j++)
1077 		{
1078 			labels[i].lines[j] = buffer[j];
1079 		}
1080 	}
1081 
1082 	/* Text Entry boxes */
1083 	if (index >= count)
1084 	{
1085 		log_add (log_Fatal, "PANIC: String table cut short while reading text entries");
1086 		exit (EXIT_FAILURE);
1087 	}
1088 
1089 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != TEXTENTRY_COUNT)
1090 	{
1091 		log_add (log_Fatal, "PANIC: Incorrect number of Text Entries");
1092 		exit (EXIT_FAILURE);
1093 	}
1094 	for (i = 0; i < TEXTENTRY_COUNT; i++)
1095 	{
1096 		textentries[i].tag = WIDGET_TYPE_TEXTENTRY;
1097 		textentries[i].parent = NULL;
1098 		textentries[i].handleEvent = Widget_HandleEventTextEntry;
1099 		textentries[i].receiveFocus = Widget_ReceiveFocusSimple;
1100 		textentries[i].draw = Widget_DrawTextEntry;
1101 		textentries[i].height = Widget_HeightOneLine;
1102 		textentries[i].width = Widget_WidthFullScreen;
1103 		textentries[i].handleEventSelect = OnTextEntryEvent;
1104 		textentries[i].category = buffer[i];
1105 		textentries[i].value[0] = 0;
1106 		textentries[i].maxlen = WIDGET_TEXTENTRY_WIDTH-1;
1107 		textentries[i].state = WTE_NORMAL;
1108 		textentries[i].cursor_pos = 0;
1109 	}
1110 
1111 	if (index >= count)
1112 	{
1113 		log_add (log_Fatal, "PANIC: String table cut short while reading text entries");
1114 		exit (EXIT_FAILURE);
1115 	}
1116 
1117 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != TEXTENTRY_COUNT)
1118 	{
1119 		/* TODO: Ignore extras instead of dying. */
1120 		log_add (log_Fatal, "PANIC: Incorrect number of Text Entries");
1121 		exit (EXIT_FAILURE);
1122 	}
1123 	for (i = 0; i < TEXTENTRY_COUNT; i++)
1124 	{
1125 		strncpy (textentries[i].value, buffer[i], textentries[i].maxlen);
1126 		textentries[i].value[textentries[i].maxlen] = 0;
1127 	}
1128 	textentries[0].onChange = rename_template;
1129 
1130 	/* Control Entry boxes */
1131 	if (index >= count)
1132 	{
1133 		log_add (log_Fatal, "PANIC: String table cut short while reading control entries");
1134 		exit (EXIT_FAILURE);
1135 	}
1136 
1137 	if (SplitString (GetStringAddress (SetAbsStringTableIndex (SetupTab, index++)), '\n', 100, buffer, bank) != CONTROLENTRY_COUNT)
1138 	{
1139 		log_add (log_Fatal, "PANIC: Incorrect number of Control Entries");
1140 		exit (EXIT_FAILURE);
1141 	}
1142 	for (i = 0; i < CONTROLENTRY_COUNT; i++)
1143 	{
1144 		controlentries[i].tag = WIDGET_TYPE_CONTROLENTRY;
1145 		controlentries[i].parent = NULL;
1146 		controlentries[i].handleEvent = Widget_HandleEventControlEntry;
1147 		controlentries[i].receiveFocus = Widget_ReceiveFocusControlEntry;
1148 		controlentries[i].draw = Widget_DrawControlEntry;
1149 		controlentries[i].height = Widget_HeightOneLine;
1150 		controlentries[i].width = Widget_WidthFullScreen;
1151 		controlentries[i].category = buffer[i];
1152 		controlentries[i].highlighted = 0;
1153 		controlentries[i].controlname[0][0] = 0;
1154 		controlentries[i].controlname[1][0] = 0;
1155 		controlentries[i].controlindex = i;
1156 		controlentries[i].onChange = rebind_control;
1157 		controlentries[i].onDelete = clear_control;
1158 	}
1159 
1160 	/* Check for garbage at the end */
1161 	if (index < count)
1162 	{
1163 		log_add (log_Warning, "WARNING: Setup strings had %d garbage entries at the end.",
1164 				count - index);
1165 	}
1166 }
1167 
1168 static void
clean_up_widgets(void)1169 clean_up_widgets (void)
1170 {
1171 	int i;
1172 
1173 	for (i = 0; i < CHOICE_COUNT; i++)
1174 	{
1175 		if (choices[i].options)
1176 		{
1177 			HFree (choices[i].options);
1178 		}
1179 	}
1180 
1181 	for (i = 0; i < LABEL_COUNT; i++)
1182 	{
1183 		if (labels[i].lines)
1184 		{
1185 			HFree ((void *)labels[i].lines);
1186 		}
1187 	}
1188 
1189 	/* Clear out the master tables */
1190 
1191 	if (SetupTab)
1192 	{
1193 		DestroyStringTable (ReleaseStringTable (SetupTab));
1194 		SetupTab = 0;
1195 	}
1196 	if (bank)
1197 	{
1198 		StringBank_Free (bank);
1199 		bank = NULL;
1200 	}
1201 	if (setup_frame)
1202 	{
1203 		DestroyDrawable (ReleaseDrawable (setup_frame));
1204 		setup_frame = NULL;
1205 	}
1206 }
1207 
1208 void
SetupMenu(void)1209 SetupMenu (void)
1210 {
1211 	SETUP_MENU_STATE s;
1212 
1213 	s.InputFunc = DoSetupMenu;
1214 	s.initialized = FALSE;
1215 	SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
1216 	SetupTab = CaptureStringTable (LoadStringTable (SETUP_MENU_STRTAB));
1217 	if (SetupTab)
1218 	{
1219 		init_widgets ();
1220 	}
1221 	else
1222 	{
1223 		log_add (log_Fatal, "PANIC: Could not find strings for the setup menu!");
1224 		exit (EXIT_FAILURE);
1225 	}
1226 	done = FALSE;
1227 
1228 	DoInput (&s, TRUE);
1229 	GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
1230 	PropagateResults ();
1231 	if (SetupTab)
1232 	{
1233 		clean_up_widgets ();
1234 	}
1235 }
1236 
1237 void
GetGlobalOptions(GLOBALOPTS * opts)1238 GetGlobalOptions (GLOBALOPTS *opts)
1239 {
1240 	bool whichBound;
1241 
1242 	if (GfxFlags & TFB_GFXFLAGS_SCALE_BILINEAR)
1243 	{
1244 		opts->scaler = OPTVAL_BILINEAR_SCALE;
1245 	}
1246 	else if (GfxFlags & TFB_GFXFLAGS_SCALE_BIADAPT)
1247 	{
1248 		opts->scaler = OPTVAL_BIADAPT_SCALE;
1249 	}
1250 	else if (GfxFlags & TFB_GFXFLAGS_SCALE_BIADAPTADV)
1251 	{
1252 		opts->scaler = OPTVAL_BIADV_SCALE;
1253 	}
1254 	else if (GfxFlags & TFB_GFXFLAGS_SCALE_TRISCAN)
1255 	{
1256 		opts->scaler = OPTVAL_TRISCAN_SCALE;
1257 	}
1258 	else if (GfxFlags & TFB_GFXFLAGS_SCALE_HQXX)
1259 	{
1260 		opts->scaler = OPTVAL_HQXX_SCALE;
1261 	}
1262 	else
1263 	{
1264 		opts->scaler = OPTVAL_NO_SCALE;
1265 	}
1266 	opts->fullscreen = (GfxFlags & TFB_GFXFLAGS_FULLSCREEN) ?
1267 			OPTVAL_ENABLED : OPTVAL_DISABLED;
1268 	opts->subtitles = optSubtitles ? OPTVAL_ENABLED : OPTVAL_DISABLED;
1269 	opts->scanlines = (GfxFlags & TFB_GFXFLAGS_SCANLINES) ?
1270 		OPTVAL_ENABLED : OPTVAL_DISABLED;
1271 	opts->menu = (optWhichMenu == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
1272 	opts->text = (optWhichFonts == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
1273 	opts->cscan = (optWhichCoarseScan == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
1274 	opts->scroll = (optSmoothScroll == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
1275 	opts->intro = (optWhichIntro == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
1276 	opts->shield = (optWhichShield == OPT_3DO) ? OPTVAL_3DO : OPTVAL_PC;
1277 	opts->fps = (GfxFlags & TFB_GFXFLAGS_SHOWFPS) ?
1278 			OPTVAL_ENABLED : OPTVAL_DISABLED;
1279 	opts->meleezoom = (optMeleeScale == TFB_SCALE_STEP) ?
1280 			OPTVAL_PC : OPTVAL_3DO;
1281 	opts->stereo = optStereoSFX ? OPTVAL_ENABLED : OPTVAL_DISABLED;
1282 	/* These values are read in, but won't change during a run. */
1283 	opts->music3do = opt3doMusic ? OPTVAL_ENABLED : OPTVAL_DISABLED;
1284 	opts->musicremix = optRemixMusic ? OPTVAL_ENABLED : OPTVAL_DISABLED;
1285 	opts->speech = optSpeech ? OPTVAL_ENABLED : OPTVAL_DISABLED;
1286 	opts->keepaspect = optKeepAspectRatio ? OPTVAL_ENABLED : OPTVAL_DISABLED;
1287 	switch (snddriver) {
1288 	case audio_DRIVER_OPENAL:
1289 		opts->adriver = OPTVAL_OPENAL;
1290 		break;
1291 	case audio_DRIVER_MIXSDL:
1292 		opts->adriver = OPTVAL_MIXSDL;
1293 		break;
1294 	default:
1295 		opts->adriver = OPTVAL_SILENCE;
1296 		break;
1297 	}
1298 	if (soundflags & audio_QUALITY_HIGH)
1299 	{
1300 		opts->aquality = OPTVAL_HIGH;
1301 	}
1302 	else if (soundflags & audio_QUALITY_LOW)
1303 	{
1304 		opts->aquality = OPTVAL_LOW;
1305 	}
1306 	else
1307 	{
1308 		opts->aquality = OPTVAL_MEDIUM;
1309 	}
1310 
1311 	/* Work out resolution.  On the way, try to guess a good default
1312 	 * for config.alwaysgl, then overwrite it if it was set previously. */
1313 	opts->driver = OPTVAL_PURE_IF_POSSIBLE;
1314 	switch (ScreenWidthActual)
1315 	{
1316 	case 320:
1317 		if (GraphicsDriver == TFB_GFXDRIVER_SDL_PURE)
1318 		{
1319 			opts->res = OPTVAL_320_240;
1320 		}
1321 		else
1322 		{
1323 			if (ScreenHeightActual != 240)
1324 			{
1325 				opts->res = OPTVAL_CUSTOM;
1326 			}
1327 			else
1328 			{
1329 				opts->res = OPTVAL_320_240;
1330 				opts->driver = OPTVAL_ALWAYS_GL;
1331 			}
1332 		}
1333 		break;
1334 	case 640:
1335 		if (GraphicsDriver == TFB_GFXDRIVER_SDL_PURE)
1336 		{
1337 			opts->res = OPTVAL_640_480;
1338 		}
1339 		else
1340 		{
1341 			if (ScreenHeightActual != 480)
1342 			{
1343 				opts->res = OPTVAL_CUSTOM;
1344 			}
1345 			else
1346 			{
1347 				opts->res = OPTVAL_640_480;
1348 				opts->driver = OPTVAL_ALWAYS_GL;
1349 			}
1350 		}
1351 		break;
1352 	case 800:
1353 		if (ScreenHeightActual != 600)
1354 		{
1355 			opts->res = OPTVAL_CUSTOM;
1356 		}
1357 		else
1358 		{
1359 			opts->res = OPTVAL_800_600;
1360 		}
1361 		break;
1362 	case 1024:
1363 		if (ScreenHeightActual != 768)
1364 		{
1365 			opts->res = OPTVAL_CUSTOM;
1366 		}
1367 		else
1368 		{
1369 			opts->res = OPTVAL_1024_768;
1370 		}
1371 		break;
1372 	case 1280:
1373 		if (ScreenHeightActual != 960)
1374 		{
1375 			opts->res = OPTVAL_CUSTOM;
1376 		}
1377 		else
1378 		{
1379 			opts->res = OPTVAL_1280_960;
1380 		}
1381 		break;
1382 	default:
1383 		opts->res = OPTVAL_CUSTOM;
1384 		break;
1385 	}
1386 
1387 	if (res_IsBoolean ("config.alwaysgl"))
1388 	{
1389 		if (res_GetBoolean ("config.alwaysgl"))
1390 		{
1391 			opts->driver = OPTVAL_ALWAYS_GL;
1392 		}
1393 		else
1394 		{
1395 			opts->driver = OPTVAL_PURE_IF_POSSIBLE;
1396 		}
1397 	}
1398 
1399 	whichBound = (optGamma < maxGamma);
1400 	// The option supplied by the user may be beyond our starting range
1401 	// but valid nonetheless. We need to account for that.
1402 	if (optGamma <= minGamma)
1403 		minGamma = optGamma - 0.03f;
1404 	else if (optGamma >= maxGamma)
1405 		maxGamma = optGamma + 0.3f;
1406 	updateGammaBounds (whichBound);
1407 	opts->gamma = gammaToSlider (optGamma);
1408 
1409 	opts->player1 = PlayerControls[0];
1410 	opts->player2 = PlayerControls[1];
1411 
1412 	opts->musicvol = (((int)(musicVolumeScale * 100.0f) + 2) / 5) * 5;
1413 	opts->sfxvol = (((int)(sfxVolumeScale * 100.0f) + 2) / 5) * 5;
1414 	opts->speechvol = (((int)(speechVolumeScale * 100.0f) + 2) / 5) * 5;
1415 }
1416 
1417 void
SetGlobalOptions(GLOBALOPTS * opts)1418 SetGlobalOptions (GLOBALOPTS *opts)
1419 {
1420 	int NewGfxFlags = GfxFlags;
1421 	int NewWidth = ScreenWidthActual;
1422 	int NewHeight = ScreenHeightActual;
1423 	int NewDriver = GraphicsDriver;
1424 
1425 	NewGfxFlags &= ~TFB_GFXFLAGS_SCALE_ANY;
1426 
1427 	switch (opts->res) {
1428 	case OPTVAL_320_240:
1429 		NewWidth = 320;
1430 		NewHeight = 240;
1431 #ifdef HAVE_OPENGL
1432 		NewDriver = (opts->driver == OPTVAL_ALWAYS_GL ? TFB_GFXDRIVER_SDL_OPENGL : TFB_GFXDRIVER_SDL_PURE);
1433 #else
1434 		NewDriver = TFB_GFXDRIVER_SDL_PURE;
1435 #endif
1436 		break;
1437 	case OPTVAL_640_480:
1438 		NewWidth = 640;
1439 		NewHeight = 480;
1440 #ifdef HAVE_OPENGL
1441 		NewDriver = (opts->driver == OPTVAL_ALWAYS_GL ? TFB_GFXDRIVER_SDL_OPENGL : TFB_GFXDRIVER_SDL_PURE);
1442 #else
1443 		NewDriver = TFB_GFXDRIVER_SDL_PURE;
1444 #endif
1445 		break;
1446 	case OPTVAL_800_600:
1447 		NewWidth = 800;
1448 		NewHeight = 600;
1449 		NewDriver = TFB_GFXDRIVER_SDL_OPENGL;
1450 		break;
1451 	case OPTVAL_1024_768:
1452 		NewWidth = 1024;
1453 		NewHeight = 768;
1454 		NewDriver = TFB_GFXDRIVER_SDL_OPENGL;
1455 		break;
1456 	case OPTVAL_1280_960:
1457 		NewWidth = 1280;
1458 		NewHeight = 960;
1459 		NewDriver = TFB_GFXDRIVER_SDL_OPENGL;
1460 		break;
1461 	default:
1462 		/* Don't mess with the custom value */
1463 		break;
1464 	}
1465 
1466 	res_PutInteger ("config.reswidth", NewWidth);
1467 	res_PutInteger ("config.resheight", NewHeight);
1468 	res_PutBoolean ("config.alwaysgl", opts->driver == OPTVAL_ALWAYS_GL);
1469 	res_PutBoolean ("config.usegl", NewDriver == TFB_GFXDRIVER_SDL_OPENGL);
1470 
1471 	switch (opts->scaler) {
1472 	case OPTVAL_BILINEAR_SCALE:
1473 		NewGfxFlags |= TFB_GFXFLAGS_SCALE_BILINEAR;
1474 		res_PutString ("config.scaler", "bilinear");
1475 		break;
1476 	case OPTVAL_BIADAPT_SCALE:
1477 		NewGfxFlags |= TFB_GFXFLAGS_SCALE_BIADAPT;
1478 		res_PutString ("config.scaler", "biadapt");
1479 		break;
1480 	case OPTVAL_BIADV_SCALE:
1481 		NewGfxFlags |= TFB_GFXFLAGS_SCALE_BIADAPTADV;
1482 		res_PutString ("config.scaler", "biadv");
1483 		break;
1484 	case OPTVAL_TRISCAN_SCALE:
1485 		NewGfxFlags |= TFB_GFXFLAGS_SCALE_TRISCAN;
1486 		res_PutString ("config.scaler", "triscan");
1487 		break;
1488 	case OPTVAL_HQXX_SCALE:
1489 		NewGfxFlags |= TFB_GFXFLAGS_SCALE_HQXX;
1490 		res_PutString ("config.scaler", "hq");
1491 		break;
1492 	default:
1493 		/* OPTVAL_NO_SCALE has no equivalent in gfxflags. */
1494 		res_PutString ("config.scaler", "no");
1495 		break;
1496 	}
1497 	if (opts->scanlines) {
1498 		NewGfxFlags |= TFB_GFXFLAGS_SCANLINES;
1499 	} else {
1500 		NewGfxFlags &= ~TFB_GFXFLAGS_SCANLINES;
1501 	}
1502 	if (opts->fullscreen)
1503 		NewGfxFlags |= TFB_GFXFLAGS_FULLSCREEN;
1504 	else
1505 		NewGfxFlags &= ~TFB_GFXFLAGS_FULLSCREEN;
1506 
1507 	res_PutBoolean ("config.scanlines", (BOOLEAN)opts->scanlines);
1508 	res_PutBoolean ("config.fullscreen", (BOOLEAN)opts->fullscreen);
1509 
1510 
1511 	if ((NewWidth != ScreenWidthActual) ||
1512 	    (NewHeight != ScreenHeightActual) ||
1513 	    (NewDriver != GraphicsDriver) ||
1514 	    (NewGfxFlags != GfxFlags))
1515 	{
1516 		FlushGraphics ();
1517 		UninitVideoPlayer ();
1518 		TFB_DrawScreen_ReinitVideo (NewDriver, NewGfxFlags, NewWidth, NewHeight);
1519 		FlushGraphics ();
1520 		InitVideoPlayer (TRUE);
1521 	}
1522 
1523 	// Avoid setting gamma when it is not necessary
1524 	if (optGamma != 1.0f || sliderToGamma (opts->gamma) != 1.0f)
1525 	{
1526 		optGamma = sliderToGamma (opts->gamma);
1527 		setGammaCorrection (optGamma);
1528 	}
1529 
1530 	optSubtitles = (opts->subtitles == OPTVAL_ENABLED) ? TRUE : FALSE;
1531 	optWhichMenu = (opts->menu == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
1532 	optWhichFonts = (opts->text == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
1533 	optWhichCoarseScan = (opts->cscan == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
1534 	optSmoothScroll = (opts->scroll == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
1535 	optWhichShield = (opts->shield == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
1536 	optMeleeScale = (opts->meleezoom == OPTVAL_3DO) ? TFB_SCALE_TRILINEAR : TFB_SCALE_STEP;
1537 	opt3doMusic = (opts->music3do == OPTVAL_ENABLED);
1538 	optRemixMusic = (opts->musicremix == OPTVAL_ENABLED);
1539 	optSpeech = (opts->speech == OPTVAL_ENABLED);
1540 	optWhichIntro = (opts->intro == OPTVAL_3DO) ? OPT_3DO : OPT_PC;
1541 	optStereoSFX = (opts->stereo == OPTVAL_ENABLED);
1542 	optKeepAspectRatio = (opts->keepaspect == OPTVAL_ENABLED);
1543 	PlayerControls[0] = opts->player1;
1544 	PlayerControls[1] = opts->player2;
1545 
1546 	res_PutBoolean ("config.subtitles", opts->subtitles == OPTVAL_ENABLED);
1547 	res_PutBoolean ("config.textmenu", opts->menu == OPTVAL_PC);
1548 	res_PutBoolean ("config.textgradients", opts->text == OPTVAL_PC);
1549 	res_PutBoolean ("config.iconicscan", opts->cscan == OPTVAL_3DO);
1550 	res_PutBoolean ("config.smoothscroll", opts->scroll == OPTVAL_3DO);
1551 
1552 	res_PutBoolean ("config.3domusic", opts->music3do == OPTVAL_ENABLED);
1553 	res_PutBoolean ("config.remixmusic", opts->musicremix == OPTVAL_ENABLED);
1554 	res_PutBoolean ("config.speech", opts->speech == OPTVAL_ENABLED);
1555 	res_PutBoolean ("config.3domovies", opts->intro == OPTVAL_3DO);
1556 	res_PutBoolean ("config.showfps", opts->fps == OPTVAL_ENABLED);
1557 	res_PutBoolean ("config.smoothmelee", opts->meleezoom == OPTVAL_3DO);
1558 	res_PutBoolean ("config.positionalsfx", opts->stereo == OPTVAL_ENABLED);
1559 	res_PutBoolean ("config.pulseshield", opts->shield == OPTVAL_3DO);
1560 	res_PutBoolean ("config.keepaspectratio", opts->keepaspect == OPTVAL_ENABLED);
1561 	res_PutInteger ("config.gamma", (int) (optGamma * GAMMA_SCALE + 0.5));
1562 	res_PutInteger ("config.player1control", opts->player1);
1563 	res_PutInteger ("config.player2control", opts->player2);
1564 
1565 	switch (opts->adriver) {
1566 	case OPTVAL_SILENCE:
1567 		res_PutString ("config.audiodriver", "none");
1568 		break;
1569 	case OPTVAL_MIXSDL:
1570 		res_PutString ("config.audiodriver", "mixsdl");
1571 		break;
1572 	case OPTVAL_OPENAL:
1573 		res_PutString ("config.audiodriver", "openal");
1574 	default:
1575 		/* Shouldn't happen; leave config untouched */
1576 		break;
1577 	}
1578 
1579 	switch (opts->aquality) {
1580 	case OPTVAL_LOW:
1581 		res_PutString ("config.audioquality", "low");
1582 		break;
1583 	case OPTVAL_MEDIUM:
1584 		res_PutString ("config.audioquality", "medium");
1585 		break;
1586 	case OPTVAL_HIGH:
1587 		res_PutString ("config.audioquality", "high");
1588 		break;
1589 	default:
1590 		/* Shouldn't happen; leave config untouched */
1591 		break;
1592 	}
1593 
1594 	res_PutInteger ("config.musicvol", opts->musicvol);
1595 	res_PutInteger ("config.sfxvol", opts->sfxvol);
1596 	res_PutInteger ("config.speechvol", opts->speechvol);
1597 	musicVolumeScale = opts->musicvol / 100.0f;
1598 	sfxVolumeScale = opts->sfxvol / 100.0f;
1599 	speechVolumeScale = opts->speechvol / 100.0f;
1600 	// update actual volumes
1601 	SetMusicVolume (musicVolume);
1602 	SetSpeechVolume (speechVolumeScale);
1603 
1604 	res_PutString ("keys.1.name", input_templates[0].name);
1605 	res_PutString ("keys.2.name", input_templates[1].name);
1606 	res_PutString ("keys.3.name", input_templates[2].name);
1607 	res_PutString ("keys.4.name", input_templates[3].name);
1608 	res_PutString ("keys.5.name", input_templates[4].name);
1609 	res_PutString ("keys.6.name", input_templates[5].name);
1610 
1611 	SaveResourceIndex (configDir, "uqm.cfg", "config.", TRUE);
1612 	SaveKeyConfiguration (configDir, "flight.cfg");
1613 }
1614