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