1 /*
2 ** optionmenuitems.h
3 ** Control items for option menus
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 2010 Christoph Oelckers
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 #include "v_text.h"
35 #include "gstrings.h"
36 
37 
38 void M_DrawConText (int color, int x, int y, const char *str);
39 void M_SetVideoMode();
40 
41 
42 
43 //=============================================================================
44 //
45 // opens a submenu, action is a submenu name
46 //
47 //=============================================================================
48 
49 class FOptionMenuItemSubmenu : public FOptionMenuItem
50 {
51 	int mParam;
52 public:
53 	FOptionMenuItemSubmenu(const char *label, const char *menu, int param = 0)
FOptionMenuItem(label,menu)54 		: FOptionMenuItem(label, menu)
55 	{
56 		mParam = param;
57 	}
58 
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)59 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
60 	{
61 		drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColorMore);
62 		return indent;
63 	}
64 
Activate()65 	bool Activate()
66 	{
67 		S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE);
68 		M_SetMenu(mAction, mParam);
69 		return true;
70 	}
71 };
72 
73 
74 //=============================================================================
75 //
76 // Executes a CCMD, action is a CCMD name
77 //
78 //=============================================================================
79 
80 class FOptionMenuItemCommand : public FOptionMenuItemSubmenu
81 {
82 public:
FOptionMenuItemCommand(const char * label,const char * menu)83 	FOptionMenuItemCommand(const char *label, const char *menu)
84 		: FOptionMenuItemSubmenu(label, menu)
85 	{
86 	}
87 
Activate()88 	bool Activate()
89 	{
90 		S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE);
91 		C_DoCommand(mAction);
92 		return true;
93 	}
94 
95 };
96 
97 //=============================================================================
98 //
99 // Executes a CCMD after confirmation, action is a CCMD name
100 //
101 //=============================================================================
102 
103 class FOptionMenuItemSafeCommand : public FOptionMenuItemCommand
104 {
105 	// action is a CCMD
106 public:
FOptionMenuItemSafeCommand(const char * label,const char * menu)107 	FOptionMenuItemSafeCommand(const char *label, const char *menu)
108 		: FOptionMenuItemCommand(label, menu)
109 	{
110 	}
111 
MenuEvent(int mkey,bool fromcontroller)112 	bool MenuEvent (int mkey, bool fromcontroller)
113 	{
114 		if (mkey == MKEY_MBYes)
115 		{
116 			C_DoCommand(mAction);
117 			return true;
118 		}
119 		return FOptionMenuItemCommand::MenuEvent(mkey, fromcontroller);
120 	}
121 
Activate()122 	bool Activate()
123 	{
124 		M_StartMessage("Do you really want to do this?", 0);
125 		return true;
126 	}
127 };
128 
129 //=============================================================================
130 //
131 // Base class for option lists
132 //
133 //=============================================================================
134 
135 class FOptionMenuItemOptionBase : public FOptionMenuItem
136 {
137 protected:
138 	// action is a CVAR
139 	FName mValues;	// Entry in OptionValues table
140 	FBaseCVar *mGrayCheck;
141 	int mCenter;
142 public:
143 
144 	enum
145 	{
146 		OP_VALUES = 0x11001
147 	};
148 
FOptionMenuItemOptionBase(const char * label,const char * menu,const char * values,const char * graycheck,int center)149 	FOptionMenuItemOptionBase(const char *label, const char *menu, const char *values, const char *graycheck, int center)
150 		: FOptionMenuItem(label, menu)
151 	{
152 		mValues = values;
153 		mGrayCheck = (FBoolCVar*)FindCVar(graycheck, NULL);
154 		mCenter = center;
155 	}
156 
SetString(int i,const char * newtext)157 	bool SetString(int i, const char *newtext)
158 	{
159 		if (i == OP_VALUES)
160 		{
161 			FOptionValues **opt = OptionValues.CheckKey(newtext);
162 			mValues = newtext;
163 			if (opt != NULL && *opt != NULL)
164 			{
165 				int s = GetSelection();
166 				if (s >= (int)(*opt)->mValues.Size()) s = 0;
167 				SetSelection(s);	// readjust the CVAR if its value is outside the range now
168 				return true;
169 			}
170 		}
171 		return false;
172 	}
173 
174 
175 
176 	//=============================================================================
177 	virtual int GetSelection() = 0;
178 	virtual void SetSelection(int Selection) = 0;
179 
180 	//=============================================================================
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)181 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
182 	{
183 		bool grayed = mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool);
184 
185 		if (mCenter)
186 		{
187 			indent = (screen->GetWidth() / 2);
188 		}
189 		drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, grayed);
190 
191 		int overlay = grayed? MAKEARGB(96,48,0,0) : 0;
192 		const char *text;
193 		int Selection = GetSelection();
194 		FOptionValues **opt = OptionValues.CheckKey(mValues);
195 		if (Selection < 0 || opt == NULL || *opt == NULL)
196 		{
197 			text = "Unknown";
198 		}
199 		else
200 		{
201 			text = (*opt)->mValues[Selection].Text;
202 		}
203 		if (*text == '$') text = GStrings(text + 1);
204 		screen->DrawText (SmallFont, OptionSettings.mFontColorValue, indent + CURSORSPACE, y,
205 			text, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE);
206 		return indent;
207 	}
208 
209 	//=============================================================================
MenuEvent(int mkey,bool fromcontroller)210 	bool MenuEvent (int mkey, bool fromcontroller)
211 	{
212 		FOptionValues **opt = OptionValues.CheckKey(mValues);
213 		if (opt != NULL && *opt != NULL && (*opt)->mValues.Size() > 0)
214 		{
215 			int Selection = GetSelection();
216 			if (mkey == MKEY_Left)
217 			{
218 				if (Selection == -1) Selection = 0;
219 				else if (--Selection < 0) Selection = (*opt)->mValues.Size()-1;
220 			}
221 			else if (mkey == MKEY_Right || mkey == MKEY_Enter)
222 			{
223 				if (++Selection >= (int)(*opt)->mValues.Size()) Selection = 0;
224 			}
225 			else
226 			{
227 				return FOptionMenuItem::MenuEvent(mkey, fromcontroller);
228 			}
229 			SetSelection(Selection);
230 			S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE);
231 		}
232 		return true;
233 	}
234 
Selectable()235 	bool Selectable()
236 	{
237 		return !(mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool));
238 	}
239 };
240 
241 //=============================================================================
242 //
243 // Change a CVAR, action is the CVAR name
244 //
245 //=============================================================================
246 
247 class FOptionMenuItemOption : public FOptionMenuItemOptionBase
248 {
249 	// action is a CVAR
250 	FBaseCVar *mCVar;
251 public:
252 
FOptionMenuItemOption(const char * label,const char * menu,const char * values,const char * graycheck,int center)253 	FOptionMenuItemOption(const char *label, const char *menu, const char *values, const char *graycheck, int center)
254 		: FOptionMenuItemOptionBase(label, menu, values, graycheck, center)
255 	{
256 		mCVar = FindCVar(mAction, NULL);
257 	}
258 
259 	//=============================================================================
GetSelection()260 	int GetSelection()
261 	{
262 		int Selection = -1;
263 		FOptionValues **opt = OptionValues.CheckKey(mValues);
264 		if (opt != NULL && *opt != NULL && mCVar != NULL && (*opt)->mValues.Size() > 0)
265 		{
266 			if ((*opt)->mValues[0].TextValue.IsEmpty())
267 			{
268 				UCVarValue cv = mCVar->GetGenericRep(CVAR_Float);
269 				for(unsigned i = 0; i < (*opt)->mValues.Size(); i++)
270 				{
271 					if (fabs(cv.Float - (*opt)->mValues[i].Value) < FLT_EPSILON)
272 					{
273 						Selection = i;
274 						break;
275 					}
276 				}
277 			}
278 			else
279 			{
280 				UCVarValue cv = mCVar->GetGenericRep(CVAR_String);
281 				for(unsigned i = 0; i < (*opt)->mValues.Size(); i++)
282 				{
283 					if ((*opt)->mValues[i].TextValue.CompareNoCase(cv.String) == 0)
284 					{
285 						Selection = i;
286 						break;
287 					}
288 				}
289 			}
290 		}
291 		return Selection;
292 	}
293 
SetSelection(int Selection)294 	void SetSelection(int Selection)
295 	{
296 		UCVarValue value;
297 		FOptionValues **opt = OptionValues.CheckKey(mValues);
298 		if (opt != NULL && *opt != NULL && mCVar != NULL && (*opt)->mValues.Size() > 0)
299 		{
300 			if ((*opt)->mValues[0].TextValue.IsEmpty())
301 			{
302 				value.Float = (float)(*opt)->mValues[Selection].Value;
303 				mCVar->SetGenericRep (value, CVAR_Float);
304 			}
305 			else
306 			{
307 				value.String = (*opt)->mValues[Selection].TextValue.LockBuffer();
308 				mCVar->SetGenericRep (value, CVAR_String);
309 				(*opt)->mValues[Selection].TextValue.UnlockBuffer();
310 			}
311 		}
312 	}
313 };
314 
315 //=============================================================================
316 //
317 // This class is used to capture the key to be used as the new key binding
318 // for a control item
319 //
320 //=============================================================================
321 
322 class DEnterKey : public DMenu
323 {
DECLARE_CLASS(DEnterKey,DMenu)324 	DECLARE_CLASS(DEnterKey, DMenu)
325 
326 	int *pKey;
327 
328 public:
329 	DEnterKey(DMenu *parent, int *keyptr)
330 	: DMenu(parent)
331 	{
332 		pKey = keyptr;
333 		SetMenuMessage(1);
334 		menuactive = MENU_WaitKey;	// There should be a better way to disable GUI capture...
335 	}
336 
TranslateKeyboardEvents()337 	bool TranslateKeyboardEvents()
338 	{
339 		return false;
340 	}
341 
SetMenuMessage(int which)342 	void SetMenuMessage(int which)
343 	{
344 		if (mParentMenu->IsKindOf(RUNTIME_CLASS(DOptionMenu)))
345 		{
346 			DOptionMenu *m = barrier_cast<DOptionMenu*>(mParentMenu);
347 			FListMenuItem *it = m->GetItem(NAME_Controlmessage);
348 			if (it != NULL)
349 			{
350 				it->SetValue(0, which);
351 			}
352 		}
353 	}
354 
Responder(event_t * ev)355 	bool Responder(event_t *ev)
356 	{
357 		if (ev->type == EV_KeyDown)
358 		{
359 			*pKey = ev->data1;
360 			menuactive = MENU_On;
361 			SetMenuMessage(0);
362 			Close();
363 			mParentMenu->MenuEvent((ev->data1 == KEY_ESCAPE)? MKEY_Abort : MKEY_Input, 0);
364 			return true;
365 		}
366 		return false;
367 	}
368 
Drawer()369 	void Drawer()
370 	{
371 		mParentMenu->Drawer();
372 	}
373 };
374 
375 #ifndef NO_IMP
IMPLEMENT_ABSTRACT_CLASS(DEnterKey)376 IMPLEMENT_ABSTRACT_CLASS(DEnterKey)
377 #endif
378 
379 //=============================================================================
380 //
381 // // Edit a key binding, Action is the CCMD to bind
382 //
383 //=============================================================================
384 
385 class FOptionMenuItemControl : public FOptionMenuItem
386 {
387 	FKeyBindings *mBindings;
388 	int mInput;
389 	bool mWaiting;
390 public:
391 
392 	FOptionMenuItemControl(const char *label, const char *menu, FKeyBindings *bindings)
393 		: FOptionMenuItem(label, menu)
394 	{
395 		mBindings = bindings;
396 		mWaiting = false;
397 	}
398 
399 
400 	//=============================================================================
401 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
402 	{
403 		drawLabel(indent, y, mWaiting? OptionSettings.mFontColorHighlight:
404 			(selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor));
405 
406 		char description[64];
407 		int Key1, Key2;
408 
409 		mBindings->GetKeysForCommand(mAction, &Key1, &Key2);
410 		C_NameKeys (description, Key1, Key2);
411 		if (description[0])
412 		{
413 			M_DrawConText(CR_WHITE, indent + CURSORSPACE, y + (OptionSettings.mLinespacing-8)*CleanYfac_1, description);
414 		}
415 		else
416 		{
417 			screen->DrawText(SmallFont, CR_BLACK, indent + CURSORSPACE, y + (OptionSettings.mLinespacing-8)*CleanYfac_1, "---",
418 				DTA_CleanNoMove_1, true, TAG_DONE);
419 		}
420 		return indent;
421 	}
422 
423 	//=============================================================================
424 	bool MenuEvent(int mkey, bool fromcontroller)
425 	{
426 		if (mkey == MKEY_Input)
427 		{
428 			mWaiting = false;
429 			mBindings->SetBind(mInput, mAction);
430 			return true;
431 		}
432 		else if (mkey == MKEY_Clear)
433 		{
434 			mBindings->UnbindACommand(mAction);
435 			return true;
436 		}
437 		else if (mkey == MKEY_Abort)
438 		{
439 			mWaiting = false;
440 			return true;
441 		}
442 		return false;
443 	}
444 
445 	bool Activate()
446 	{
447 		S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE);
448 		mWaiting = true;
449 		DMenu *input = new DEnterKey(DMenu::CurrentMenu, &mInput);
450 		M_ActivateMenu(input);
451 		return true;
452 	}
453 };
454 
455 //=============================================================================
456 //
457 //
458 //
459 //=============================================================================
460 
461 class FOptionMenuItemStaticText : public FOptionMenuItem
462 {
463 	EColorRange mColor;
464 public:
FOptionMenuItemStaticText(const char * label,bool header)465 	FOptionMenuItemStaticText(const char *label, bool header)
466 		: FOptionMenuItem(label, NAME_None, true)
467 	{
468 		mColor = header? OptionSettings.mFontColorHeader : OptionSettings.mFontColor;
469 	}
470 
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)471 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
472 	{
473 		drawLabel(indent, y, mColor);
474 		return -1;
475 	}
476 
Selectable()477 	bool Selectable()
478 	{
479 		return false;
480 	}
481 
482 };
483 
484 //=============================================================================
485 //
486 //
487 //
488 //=============================================================================
489 
490 class FOptionMenuItemStaticTextSwitchable : public FOptionMenuItem
491 {
492 	EColorRange mColor;
493 	FString mAltText;
494 	int mCurrent;
495 
496 public:
FOptionMenuItemStaticTextSwitchable(const char * label,const char * label2,FName action,bool header)497 	FOptionMenuItemStaticTextSwitchable(const char *label, const char *label2, FName action, bool header)
498 		: FOptionMenuItem(label, action, true)
499 	{
500 		mColor = header? OptionSettings.mFontColorHeader : OptionSettings.mFontColor;
501 		mAltText = label2;
502 		mCurrent = 0;
503 	}
504 
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)505 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
506 	{
507 		const char *txt = mCurrent? (const char*)mAltText : mLabel;
508 		if (*txt == '$') txt = GStrings(txt + 1);
509 		int w = SmallFont->StringWidth(txt) * CleanXfac_1;
510 		int x = (screen->GetWidth() - w) / 2;
511 		screen->DrawText (SmallFont, mColor, x, y, txt, DTA_CleanNoMove_1, true, TAG_DONE);
512 		return -1;
513 	}
514 
SetValue(int i,int val)515 	bool SetValue(int i, int val)
516 	{
517 		if (i == 0)
518 		{
519 			mCurrent = val;
520 			return true;
521 		}
522 		return false;
523 	}
524 
SetString(int i,const char * newtext)525 	bool SetString(int i, const char *newtext)
526 	{
527 		if (i == 0)
528 		{
529 			mAltText = newtext;
530 			return true;
531 		}
532 		return false;
533 	}
534 
Selectable()535 	bool Selectable()
536 	{
537 		return false;
538 	}
539 };
540 
541 //=============================================================================
542 //
543 //
544 //
545 //=============================================================================
546 
547 class FOptionMenuSliderBase : public FOptionMenuItem
548 {
549 	// action is a CVAR
550 	double mMin, mMax, mStep;
551 	int mShowValue;
552 	int mDrawX;
553 	int mSliderShort;
554 
555 public:
FOptionMenuSliderBase(const char * label,double min,double max,double step,int showval)556 	FOptionMenuSliderBase(const char *label, double min, double max, double step, int showval)
557 		: FOptionMenuItem(label, NAME_None)
558 	{
559 		mMin = min;
560 		mMax = max;
561 		mStep = step;
562 		mShowValue = showval;
563 		mDrawX = 0;
564 		mSliderShort = 0;
565 	}
566 
567 	virtual double GetSliderValue() = 0;
568 	virtual void SetSliderValue(double val) = 0;
569 
570 	//=============================================================================
571 	//
572 	// Draw a slider. Set fracdigits negative to not display the current value numerically.
573 	//
574 	//=============================================================================
575 
DrawSlider(int x,int y,double min,double max,double cur,int fracdigits,int indent)576 	void DrawSlider (int x, int y, double min, double max, double cur, int fracdigits, int indent)
577 	{
578 		char textbuf[16];
579 		double range;
580 		int maxlen = 0;
581 		int right = x + (12*8 + 4) * CleanXfac_1;
582 		int cy = y + (OptionSettings.mLinespacing-8)*CleanYfac_1;
583 
584 		range = max - min;
585 		double ccur = clamp(cur, min, max) - min;
586 
587 		if (fracdigits >= 0)
588 		{
589 			mysnprintf(textbuf, countof(textbuf), "%.*f", fracdigits, max);
590 			maxlen = SmallFont->StringWidth(textbuf) * CleanXfac_1;
591 		}
592 
593 		mSliderShort = right + maxlen > screen->GetWidth();
594 
595 		if (!mSliderShort)
596 		{
597 			M_DrawConText(CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12");
598 			M_DrawConText(CR_ORANGE, x + int((5 + ((ccur * 78) / range)) * CleanXfac_1), cy, "\x13");
599 		}
600 		else
601 		{
602 			// On 320x200 we need a shorter slider
603 			M_DrawConText(CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x12");
604 			M_DrawConText(CR_ORANGE, x + int((5 + ((ccur * 38) / range)) * CleanXfac_1), cy, "\x13");
605 			right -= 5*8*CleanXfac_1;
606 		}
607 
608 		if (fracdigits >= 0 && right + maxlen <= screen->GetWidth())
609 		{
610 			mysnprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur);
611 			screen->DrawText(SmallFont, CR_DARKGRAY, right, y, textbuf, DTA_CleanNoMove_1, true, TAG_DONE);
612 		}
613 	}
614 
615 
616 	//=============================================================================
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)617 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
618 	{
619 		drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor);
620 		mDrawX = indent + CURSORSPACE;
621 		DrawSlider (mDrawX, y, mMin, mMax, GetSliderValue(), mShowValue, indent);
622 		return indent;
623 	}
624 
625 	//=============================================================================
MenuEvent(int mkey,bool fromcontroller)626 	bool MenuEvent (int mkey, bool fromcontroller)
627 	{
628 		double value = GetSliderValue();
629 
630 		if (mkey == MKEY_Left)
631 		{
632 			value -= mStep;
633 		}
634 		else if (mkey == MKEY_Right)
635 		{
636 			value += mStep;
637 		}
638 		else
639 		{
640 			return FOptionMenuItem::MenuEvent(mkey, fromcontroller);
641 		}
642 		SetSliderValue(clamp(value, mMin, mMax));
643 		S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE);
644 		return true;
645 	}
646 
MouseEvent(int type,int x,int y)647 	bool MouseEvent(int type, int x, int y)
648 	{
649 		DOptionMenu *lm = static_cast<DOptionMenu*>(DMenu::CurrentMenu);
650 		if (type != DMenu::MOUSE_Click)
651 		{
652 			if (!lm->CheckFocus(this)) return false;
653 		}
654 		if (type == DMenu::MOUSE_Release)
655 		{
656 			lm->ReleaseFocus();
657 		}
658 
659 		int slide_left = mDrawX+8*CleanXfac_1;
660 		int slide_right = slide_left + (10*8*CleanXfac_1 >> mSliderShort);	// 12 char cells with 8 pixels each.
661 
662 		if (type == DMenu::MOUSE_Click)
663 		{
664 			if (x < slide_left || x >= slide_right) return true;
665 		}
666 
667 		x = clamp(x, slide_left, slide_right);
668 		double v = mMin + ((x - slide_left) * (mMax - mMin)) / (slide_right - slide_left);
669 		if (v != GetSliderValue())
670 		{
671 			SetSliderValue(v);
672 			//S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE);
673 		}
674 		if (type == DMenu::MOUSE_Click)
675 		{
676 			lm->SetFocus(this);
677 		}
678 		return true;
679 	}
680 
681 };
682 
683 //=============================================================================
684 //
685 //
686 //
687 //=============================================================================
688 
689 class FOptionMenuSliderCVar : public FOptionMenuSliderBase
690 {
691 	FBaseCVar *mCVar;
692 public:
FOptionMenuSliderCVar(const char * label,const char * menu,double min,double max,double step,int showval)693 	FOptionMenuSliderCVar(const char *label, const char *menu, double min, double max, double step, int showval)
694 		: FOptionMenuSliderBase(label, min, max, step, showval)
695 	{
696 		mCVar = FindCVar(menu, NULL);
697 	}
698 
GetSliderValue()699 	double GetSliderValue()
700 	{
701 		if (mCVar != NULL)
702 		{
703 			return mCVar->GetGenericRep(CVAR_Float).Float;
704 		}
705 		else
706 		{
707 			return 0;
708 		}
709 	}
710 
SetSliderValue(double val)711 	void SetSliderValue(double val)
712 	{
713 		if (mCVar != NULL)
714 		{
715 			UCVarValue value;
716 			value.Float = (float)val;
717 			mCVar->SetGenericRep(value, CVAR_Float);
718 		}
719 	}
720 };
721 
722 //=============================================================================
723 //
724 //
725 //
726 //=============================================================================
727 
728 class FOptionMenuSliderVar : public FOptionMenuSliderBase
729 {
730 	float *mPVal;
731 public:
732 
FOptionMenuSliderVar(const char * label,float * pVal,double min,double max,double step,int showval)733 	FOptionMenuSliderVar(const char *label, float *pVal, double min, double max, double step, int showval)
734 		: FOptionMenuSliderBase(label, min, max, step, showval)
735 	{
736 		mPVal = pVal;
737 	}
738 
GetSliderValue()739 	double GetSliderValue()
740 	{
741 		return *mPVal;
742 	}
743 
SetSliderValue(double val)744 	void SetSliderValue(double val)
745 	{
746 		*mPVal = (float)val;
747 	}
748 };
749 
750 //=============================================================================
751 //
752 // // Edit a key binding, Action is the CCMD to bind
753 //
754 //=============================================================================
755 
756 class FOptionMenuItemColorPicker : public FOptionMenuItem
757 {
758 	FColorCVar *mCVar;
759 public:
760 
761 	enum
762 	{
763 		CPF_RESET = 0x20001,
764 	};
765 
FOptionMenuItemColorPicker(const char * label,const char * menu)766 	FOptionMenuItemColorPicker(const char *label, const char *menu)
767 		: FOptionMenuItem(label, menu)
768 	{
769 		FBaseCVar *cv = FindCVar(menu, NULL);
770 		if (cv != NULL && cv->GetRealType() == CVAR_Color)
771 		{
772 			mCVar = (FColorCVar*)cv;
773 		}
774 		else mCVar = NULL;
775 	}
776 
777 	//=============================================================================
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)778 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
779 	{
780 		drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor);
781 
782 		if (mCVar != NULL)
783 		{
784 			int box_x = indent + CURSORSPACE;
785 			int box_y = y + CleanYfac_1;
786 			screen->Clear (box_x, box_y, box_x + 32*CleanXfac_1, box_y + OptionSettings.mLinespacing*CleanYfac_1,
787 				-1, (uint32)*mCVar | 0xff000000);
788 		}
789 		return indent;
790 	}
791 
SetValue(int i,int v)792 	bool SetValue(int i, int v)
793 	{
794 		if (i == CPF_RESET && mCVar != NULL)
795 		{
796 			mCVar->ResetToDefault();
797 			return true;
798 		}
799 		return false;
800 	}
801 
Activate()802 	bool Activate()
803 	{
804 		if (mCVar != NULL)
805 		{
806 			S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE);
807 			DMenu *picker = StartPickerMenu(DMenu::CurrentMenu, mLabel, mCVar);
808 			if (picker != NULL)
809 			{
810 				M_ActivateMenu(picker);
811 				return true;
812 			}
813 		}
814 		return false;
815 	}
816 };
817 
818 class FOptionMenuScreenResolutionLine : public FOptionMenuItem
819 {
820 	FString mResTexts[3];
821 	int mSelection;
822 	int mHighlight;
823 	int mMaxValid;
824 public:
825 
826 	enum
827 	{
828 		SRL_INDEX = 0x30000,
829 		SRL_SELECTION = 0x30003,
830 		SRL_HIGHLIGHT = 0x30004,
831 	};
832 
FOptionMenuScreenResolutionLine(const char * action)833 	FOptionMenuScreenResolutionLine(const char *action)
834 		: FOptionMenuItem("", action)
835 	{
836 		mSelection = 0;
837 		mHighlight = -1;
838 	}
839 
SetValue(int i,int v)840 	bool SetValue(int i, int v)
841 	{
842 		if (i == SRL_SELECTION)
843 		{
844 			mSelection = v;
845 			return true;
846 		}
847 		else if (i == SRL_HIGHLIGHT)
848 		{
849 			mHighlight = v;
850 			return true;
851 		}
852 		return false;
853 	}
854 
GetValue(int i,int * v)855 	bool GetValue(int i, int *v)
856 	{
857 		if (i == SRL_SELECTION)
858 		{
859 			*v = mSelection;
860 			return true;
861 		}
862 		return false;
863 	}
864 
SetString(int i,const char * newtext)865 	bool SetString(int i, const char *newtext)
866 	{
867 		if (i >= SRL_INDEX && i <= SRL_INDEX+2)
868 		{
869 			mResTexts[i-SRL_INDEX] = newtext;
870 			if (mResTexts[0].IsEmpty()) mMaxValid = -1;
871 			else if (mResTexts[1].IsEmpty()) mMaxValid = 0;
872 			else if (mResTexts[2].IsEmpty()) mMaxValid = 1;
873 			else mMaxValid = 2;
874 			return true;
875 		}
876 		return false;
877 	}
878 
GetString(int i,char * s,int len)879 	bool GetString(int i, char *s, int len)
880 	{
881 		if (i >= SRL_INDEX && i <= SRL_INDEX+2)
882 		{
883 			strncpy(s, mResTexts[i-SRL_INDEX], len-1);
884 			s[len-1] = 0;
885 			return true;
886 		}
887 		return false;
888 	}
889 
MenuEvent(int mkey,bool fromcontroller)890 	bool MenuEvent (int mkey, bool fromcontroller)
891 	{
892 		if (mkey == MKEY_Left)
893 		{
894 			if (--mSelection < 0) mSelection = mMaxValid;
895 			S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE);
896 			return true;
897 		}
898 		else if (mkey == MKEY_Right)
899 		{
900 			if (++mSelection > mMaxValid) mSelection = 0;
901 			S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE);
902 			return true;
903 		}
904 		else
905 		{
906 			return FOptionMenuItem::MenuEvent(mkey, fromcontroller);
907 		}
908 		return false;
909 	}
910 
MouseEvent(int type,int x,int y)911 	bool MouseEvent(int type, int x, int y)
912 	{
913 		int colwidth = screen->GetWidth() / 3;
914 		mSelection = x / colwidth;
915 		return FOptionMenuItem::MouseEvent(type, x, y);
916 	}
917 
Activate()918 	bool Activate()
919 	{
920 		S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE);
921 		M_SetVideoMode();
922 		return true;
923 	}
924 
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)925 	int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected)
926 	{
927 		int colwidth = screen->GetWidth() / 3;
928 		EColorRange color;
929 
930 		for (int x = 0; x < 3; x++)
931 		{
932 			if (selected && mSelection == x)
933 				color = OptionSettings.mFontColorSelection;
934 			else if (x == mHighlight)
935 				color = OptionSettings.mFontColorHighlight;
936 			else
937 				color = OptionSettings.mFontColorValue;
938 
939 			screen->DrawText (SmallFont, color, colwidth * x + 20 * CleanXfac_1, y, mResTexts[x], DTA_CleanNoMove_1, true, TAG_DONE);
940 		}
941 		return colwidth * mSelection + 20 * CleanXfac_1 - CURSORSPACE;
942 	}
943 
Selectable()944 	bool Selectable()
945 	{
946 		return mMaxValid >= 0;
947 	}
948 
Ticker()949 	void Ticker()
950 	{
951 		if (Selectable() && mSelection > mMaxValid)
952 		{
953 			mSelection = mMaxValid;
954 		}
955 	}
956 };
957 
958 
959 //=============================================================================
960 //
961 // [TP] FOptionMenuFieldBase
962 //
963 // Base class for input fields
964 //
965 //=============================================================================
966 
967 class FOptionMenuFieldBase : public FOptionMenuItem
968 {
969 public:
FOptionMenuFieldBase(const char * label,const char * menu,const char * graycheck)970 	FOptionMenuFieldBase ( const char* label, const char* menu, const char* graycheck ) :
971 		FOptionMenuItem ( label, menu ),
972 		mCVar ( FindCVar( mAction, NULL )),
973 		mGrayCheck (( graycheck && strlen( graycheck )) ? FindCVar( graycheck, NULL ) : NULL ) {}
974 
GetCVarString()975 	const char* GetCVarString()
976 	{
977 		if ( mCVar == NULL )
978 			return "";
979 
980 		return mCVar->GetGenericRep( CVAR_String ).String;
981 	}
982 
Represent()983 	virtual FString Represent()
984 	{
985 		return GetCVarString();
986 	}
987 
Draw(FOptionMenuDescriptor *,int y,int indent,bool selected)988 	int Draw ( FOptionMenuDescriptor*, int y, int indent, bool selected )
989 	{
990 		bool grayed = mGrayCheck != NULL && !( mGrayCheck->GetGenericRep( CVAR_Bool ).Bool );
991 		drawLabel( indent, y, selected ? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, grayed );
992 		int overlay = grayed? MAKEARGB( 96, 48, 0, 0 ) : 0;
993 
994 		screen->DrawText( SmallFont, OptionSettings.mFontColorValue, indent + CURSORSPACE, y,
995 			Represent().GetChars(), DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE );
996 		return indent;
997 	}
998 
GetString(int i,char * s,int len)999 	bool GetString ( int i, char* s, int len )
1000 	{
1001 		if ( i == 0 )
1002 		{
1003 			strncpy( s, GetCVarString(), len );
1004 			s[len - 1] = '\0';
1005 			return true;
1006 		}
1007 
1008 		return false;
1009 	}
1010 
SetString(int i,const char * s)1011 	bool SetString ( int i, const char* s )
1012 	{
1013 		if ( i == 0 )
1014 		{
1015 			if ( mCVar )
1016 			{
1017 				UCVarValue vval;
1018 				vval.String = s;
1019 				mCVar->SetGenericRep( vval, CVAR_String );
1020 			}
1021 
1022 			return true;
1023 		}
1024 
1025 		return false;
1026 	}
1027 
1028 protected:
1029 	// Action is a CVar in this class and derivatives.
1030 	FBaseCVar* mCVar;
1031 	FBaseCVar* mGrayCheck;
1032 };
1033 
1034 //=============================================================================
1035 //
1036 // [TP] FOptionMenuTextField
1037 //
1038 // A text input field widget, for use with string CVars.
1039 //
1040 //=============================================================================
1041 
1042 class FOptionMenuTextField : public FOptionMenuFieldBase
1043 {
1044 public:
FOptionMenuTextField(const char * label,const char * menu,const char * graycheck)1045 	FOptionMenuTextField ( const char *label, const char* menu, const char* graycheck ) :
1046 		FOptionMenuFieldBase ( label, menu, graycheck ),
1047 		mEntering ( false ) {}
1048 
Represent()1049 	FString Represent()
1050 	{
1051 		FString text = mEntering ? mEditName : GetCVarString();
1052 
1053 		if ( mEntering )
1054 			text += ( gameinfo.gametype & GAME_DoomStrifeChex ) ? '_' : '[';
1055 
1056 		return text;
1057 	}
1058 
Draw(FOptionMenuDescriptor * desc,int y,int indent,bool selected)1059 	int Draw(FOptionMenuDescriptor*desc, int y, int indent, bool selected)
1060 	{
1061 		if (mEntering)
1062 		{
1063 			// reposition the text so that the cursor is visible when in entering mode.
1064 			FString text = Represent();
1065 			int tlen = SmallFont->StringWidth(text) * CleanXfac_1;
1066 			int newindent = screen->GetWidth() - tlen - CURSORSPACE;
1067 			if (newindent < indent) indent = newindent;
1068 		}
1069 		return FOptionMenuFieldBase::Draw(desc, y, indent, selected);
1070 	}
1071 
MenuEvent(int mkey,bool fromcontroller)1072 	bool MenuEvent ( int mkey, bool fromcontroller )
1073 	{
1074 		if ( mkey == MKEY_Enter )
1075 		{
1076 			S_Sound( CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE );
1077 			strcpy( mEditName, GetCVarString() );
1078 			mEntering = true;
1079 			DMenu* input = new DTextEnterMenu ( DMenu::CurrentMenu, mEditName, sizeof mEditName, 2, fromcontroller );
1080 			M_ActivateMenu( input );
1081 			return true;
1082 		}
1083 		else if ( mkey == MKEY_Input )
1084 		{
1085 			if ( mCVar )
1086 			{
1087 				UCVarValue vval;
1088 				vval.String = mEditName;
1089 				mCVar->SetGenericRep( vval, CVAR_String );
1090 			}
1091 
1092 			mEntering = false;
1093 			return true;
1094 		}
1095 		else if ( mkey == MKEY_Abort )
1096 		{
1097 			mEntering = false;
1098 			return true;
1099 		}
1100 
1101 		return FOptionMenuItem::MenuEvent( mkey, fromcontroller );
1102 	}
1103 
1104 private:
1105 	bool mEntering;
1106 	char mEditName[128];
1107 };
1108 
1109 //=============================================================================
1110 //
1111 // [TP] FOptionMenuNumberField
1112 //
1113 // A numeric input field widget, for use with number CVars where sliders are inappropriate (i.e.
1114 // where the user is interested in the exact value specifically)
1115 //
1116 //=============================================================================
1117 
1118 class FOptionMenuNumberField : public FOptionMenuFieldBase
1119 {
1120 public:
FOptionMenuNumberField(const char * label,const char * menu,float minimum,float maximum,float step,const char * graycheck)1121 	FOptionMenuNumberField ( const char *label, const char* menu, float minimum, float maximum,
1122 		float step, const char* graycheck )
1123 		: FOptionMenuFieldBase ( label, menu, graycheck ),
1124 		mMinimum ( minimum ),
1125 		mMaximum ( maximum ),
1126 		mStep ( step )
1127 	{
1128 		if ( mMaximum <= mMinimum )
1129 			swapvalues( mMinimum, mMaximum );
1130 
1131 		if ( mStep <= 0 )
1132 			mStep = 1;
1133 	}
1134 
MenuEvent(int mkey,bool fromcontroller)1135 	bool MenuEvent ( int mkey, bool fromcontroller )
1136 	{
1137 		if ( mCVar )
1138 		{
1139 			float value = mCVar->GetGenericRep( CVAR_Float ).Float;
1140 
1141 			if ( mkey == MKEY_Left )
1142 			{
1143 				value -= mStep;
1144 
1145 				if ( value < mMinimum )
1146 					value = mMaximum;
1147 			}
1148 			else if ( mkey == MKEY_Right || mkey == MKEY_Enter )
1149 			{
1150 				value += mStep;
1151 
1152 				if ( value > mMaximum )
1153 					value = mMinimum;
1154 			}
1155 			else
1156 				return FOptionMenuItem::MenuEvent( mkey, fromcontroller );
1157 
1158 			UCVarValue vval;
1159 			vval.Float = value;
1160 			mCVar->SetGenericRep( vval, CVAR_Float );
1161 			S_Sound( CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE );
1162 		}
1163 
1164 		return true;
1165 	}
1166 
1167 private:
1168 	float mMinimum;
1169 	float mMaximum;
1170 	float mStep;
1171 };
1172