1 //------------------------------------------------------------------------
2 //  PREFERENCES DIALOG
3 //------------------------------------------------------------------------
4 //
5 //  Eureka DOOM Editor
6 //
7 //  Copyright (C) 2012-2019 Andrew Apted
8 //
9 //  This program is free software; you can redistribute it and/or
10 //  modify it under the terms of the GNU General Public License
11 //  as published by the Free Software Foundation; either version 2
12 //  of the License, or (at your option) any later version.
13 //
14 //  This program is distributed in the hope that it will be useful,
15 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 //  GNU General Public License for more details.
18 //
19 //------------------------------------------------------------------------
20 
21 #include "main.h"
22 #include "m_config.h"
23 
24 #include <algorithm>
25 
26 #include "ui_window.h"
27 #include "ui_prefs.h"
28 
29 #include <FL/Fl_Color_Chooser.H>
30 
31 
32 #define PREF_WINDOW_W  600
33 #define PREF_WINDOW_H  520
34 
35 #define PREF_WINDOW_TITLE  "Eureka Preferences"
36 
37 
38 static int last_active_tab = 0;
39 
40 
41 class UI_EditKey : public UI_Escapable_Window
42 {
43 private:
44 	bool want_close;
45 	bool cancelled;
46 
47 	bool awaiting_key;
48 
49 	keycode_t key;
50 
51 	Fl_Input  *key_name;
52 	Fl_Button *grab_but;
53 
54 	Fl_Output *func_name;
55 	Fl_Menu_Button *func_choose;
56 
57 	Fl_Choice *context;
58 	Fl_Input  *params;
59 
60 	const editor_command_t *cur_cmd;
61 
62 	Fl_Menu_Button *keyword_menu;
63 	Fl_Menu_Button *flag_menu;
64 
65 	Fl_Button *cancel;
66 	Fl_Button *ok_but;
67 
68 private:
BeginGrab()69 	void BeginGrab()
70 	{
71 		SYS_ASSERT(! awaiting_key);
72 
73 		awaiting_key = true;
74 
75 		key_name->color(FL_YELLOW, FL_YELLOW);
76 		key_name->value("<\077\077\077>");
77 		key_name->textcolor(FL_FOREGROUND_COLOR);
78 		grab_but->deactivate();
79 
80 		Fl::focus(this);
81 
82 		redraw();
83 	}
84 
FinishGrab()85 	void FinishGrab()
86 	{
87 		if (! awaiting_key)
88 			return;
89 
90 		awaiting_key = false;
91 
92 		key_name->color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
93 		grab_but->activate();
94 
95 		redraw();
96 	}
97 
handle(int event)98 	int handle(int event)
99 	{
100 		if (awaiting_key)
101 		{
102 			// escape key cancels
103 			if (event == FL_KEYDOWN && Fl::event_key() == FL_Escape)
104 			{
105 				FinishGrab();
106 
107 				if (key)
108 					key_name->value(M_KeyToString(key));
109 
110 				// if previous key was invalid, need to re-enable OK button
111 				validate_callback(this, this);
112 
113 				return 1;
114 			}
115 
116 			if (event == FL_KEYDOWN ||
117 				event == FL_PUSH    ||
118 				event == FL_MOUSEWHEEL)
119 			{
120 				keycode_t new_key = M_CookedKeyForEvent(event);
121 
122 				if (new_key)
123 				{
124 					FinishGrab();
125 
126 					key = new_key;
127 					key_name->value(M_KeyToString(key));
128 
129 					// if previous key was invalid, need to re-enable OK button
130 					validate_callback(this, this);
131 
132 					return 1;
133 				}
134 			}
135 		}
136 
137 		return UI_Escapable_Window::handle(event);
138 	}
139 
140 private:
141 	struct name_CMP_pred
142 	{
operator ()UI_EditKey::name_CMP_pred143 		inline bool operator() (const char * A, const char * B) const
144 		{
145 			return (strcmp(A, B) < 0);
146 		}
147 	};
148 
149 	// need this because menu is in reverse order
ContextFromMenu()150 	key_context_e ContextFromMenu()
151 	{
152 		int i = context->value();
153 		SYS_ASSERT(i >= 0 && i <= 6);
154 		return (key_context_e)((int)KCTX_General - i);
155 	}
156 
SetContext(key_context_e ctx)157 	void SetContext(key_context_e ctx)
158 	{
159 		int i = (int)KCTX_General - (int)ctx;
160 		SYS_ASSERT(i >= 0 && i <= 6);
161 		context->value(i);
162 	}
163 
AddContextToMenu(const char * lab,key_context_e ctx,key_context_e limit_ctx)164 	void AddContextToMenu(const char *lab, key_context_e ctx, key_context_e limit_ctx)
165 	{
166 		int flags = 0;
167 
168 		if (limit_ctx != KCTX_NONE && ctx != limit_ctx)
169 		{
170 			flags = FL_MENU_INACTIVE;
171 		}
172 
173 		context->add(lab, 0, 0, 0, flags);
174 	}
175 
PopulateContextMenu(key_context_e want_ctx)176 	void PopulateContextMenu(key_context_e want_ctx)
177 	{
178 		key_context_e limit_ctx = KCTX_NONE;
179 
180 		if (cur_cmd && cur_cmd->req_context != KCTX_NONE)
181 			limit_ctx = cur_cmd->req_context;
182 
183 		context->clear();
184 
185 		AddContextToMenu("General (Any)",  KCTX_General, limit_ctx);
186 
187 		AddContextToMenu("Linedefs",  KCTX_Line,    limit_ctx);
188 		AddContextToMenu("Sectors",   KCTX_Sector,  limit_ctx);
189 		AddContextToMenu("Things",    KCTX_Thing,   limit_ctx);
190 		AddContextToMenu("Vertices",  KCTX_Vertex,  limit_ctx);
191 		AddContextToMenu("3D View",   KCTX_Render,  limit_ctx);
192 		AddContextToMenu("Browser",   KCTX_Browser, limit_ctx);
193 
194 		if (want_ctx != KCTX_NONE)
195 			SetContext(want_ctx);
196 	}
197 
PopulateFuncMenu(const char * find_name=NULL)198 	void PopulateFuncMenu(const char *find_name = NULL)
199 	{
200 		func_choose->clear();
201 
202 		cur_cmd = NULL;
203 
204 		// add names to menu, and find the current function
205 		char buffer[512];
206 
207 		bool did_separator = false;
208 
209 		for (int i = 0 ; ; i++)
210 		{
211 			const editor_command_t *cmd = LookupEditorCommand(i);
212 
213 			if (! cmd)
214 				break;
215 
216 			if (! did_separator && y_stricmp(cmd->group_name, "General") == 0)
217 			{
218 				func_choose->add("", 0, 0, 0, FL_MENU_DIVIDER|FL_MENU_INACTIVE);
219 				did_separator = true;
220 			}
221 
222 			snprintf(buffer, sizeof(buffer), "%s/%s", cmd->group_name, cmd->name);
223 
224 			func_choose->add(buffer, 0, 0, (void *)(long)i, 0 /* flags */);
225 
226 			if (find_name && strcmp(cmd->name, find_name) == 0)
227 			{
228 				cur_cmd = cmd;
229 			}
230 		}
231 
232 		if (cur_cmd)
233 			func_name->value(cur_cmd->name);
234 		else
235 			func_name->value("");
236 	}
237 
Decode(key_context_e ctx,const char * str)238 	void Decode(key_context_e ctx, const char *str)
239 	{
240 		while (isspace(*str))
241 			str++;
242 
243 		char func_buf[100];
244 		unsigned int pos = 0;
245 
246 		while (*str && ! (isspace(*str) || *str == ':' || *str == '/') &&
247 			   pos + 4 < sizeof(func_buf))
248 		{
249 			func_buf[pos++] = *str++;
250 		}
251 
252 		func_buf[pos] = 0;
253 
254 		// this sets the 'cur_cmd' variable
255 		PopulateFuncMenu(func_buf);
256 
257 		PopulateMenuList(keyword_menu, cur_cmd ? cur_cmd->keyword_list : NULL);
258 		PopulateMenuList(   flag_menu, cur_cmd ? cur_cmd->   flag_list : NULL);
259 
260 		if (*str == ':')
261 			str++;
262 
263 		while (isspace(*str))
264 			str++;
265 
266 		params->value(str);
267 	}
268 
Encode()269 	const char * Encode()
270 	{
271 		static char buffer[1024];
272 
273 		// should not happen
274 		if (! cur_cmd)
275 			return "ERROR";
276 
277 		strcpy(buffer, cur_cmd->name);
278 
279 		if (strlen(buffer) + strlen(params->value()) + 10 >= sizeof(buffer))
280 			return "OVERFLOW";
281 
282 		strcat(buffer, ": ");
283 		strcat(buffer, params->value());
284 
285 		return buffer;
286 	}
287 
PopulateMenuList(Fl_Menu_Button * menu,const char * list)288 	void PopulateMenuList(Fl_Menu_Button *menu, const char *list)
289 	{
290 		menu->clear();
291 
292 		if (! list || ! list[0])
293 		{
294 			menu->deactivate();
295 			return;
296 		}
297 
298 		const char * tokens[32];
299 
300 		int num_tok = M_ParseLine(list, tokens, 32, 0 /* do_strings */);
301 
302 		if (num_tok < 1)	// shouldn't happen
303 		{
304 			menu->deactivate();
305 			return;
306 		}
307 
308 		for (int i = 0 ; i < num_tok ; i++)
309 			menu->add(tokens[i]);
310 
311 		M_FreeLine(tokens, num_tok);
312 
313 		menu->activate();
314 	}
315 
ValidateKey()316 	bool ValidateKey()
317 	{
318 		keycode_t new_key = M_ParseKeyString(key_name->value());
319 
320 		if (new_key > 0)
321 		{
322 			key = new_key;
323 			return true;
324 		}
325 
326 		return false;
327 	}
328 
validate_callback(Fl_Widget * w,void * data)329 	static void validate_callback(Fl_Widget *w, void *data)
330 	{
331 		UI_EditKey *dialog = (UI_EditKey *)data;
332 
333 		bool valid_key = dialog->ValidateKey();
334 
335 		dialog->key_name->textcolor(valid_key ? FL_FOREGROUND_COLOR : FL_RED);
336 
337 		// need to redraw the input box (otherwise get a mix of colors)
338 		dialog->key_name->redraw();
339 
340 		if (valid_key)
341 			dialog->ok_but->activate();
342 		else
343 			dialog->ok_but->deactivate();
344 	}
345 
grab_key_callback(Fl_Button * w,void * data)346 	static void grab_key_callback(Fl_Button *w, void *data)
347 	{
348 		UI_EditKey *dialog = (UI_EditKey *)data;
349 
350 		dialog->BeginGrab();
351 	}
352 
close_callback(Fl_Widget * w,void * data)353 	static void close_callback(Fl_Widget *w, void *data)
354 	{
355 		UI_EditKey *dialog = (UI_EditKey *)data;
356 
357 		dialog->want_close = true;
358 		dialog->cancelled  = true;
359 	}
360 
ok_callback(Fl_Button * w,void * data)361 	static void ok_callback(Fl_Button *w, void *data)
362 	{
363 		UI_EditKey *dialog = (UI_EditKey *)data;
364 
365 		dialog->want_close = true;
366 	}
367 
SetNewFunction(int cmd_index)368 	void SetNewFunction(int cmd_index)
369 	{
370 		const editor_command_t *old_cmd = cur_cmd;
371 
372 		cur_cmd = LookupEditorCommand(cmd_index);
373 
374 		if (! cur_cmd)  // shouldn't happen
375 			return;
376 
377 		func_name->value(cur_cmd->name);
378 
379 		key_context_e want_ctx = ContextFromMenu();
380 
381 		if (cur_cmd->req_context)
382 			want_ctx = cur_cmd->req_context;
383 		else if (y_strnicmp(cur_cmd->name, "BR_", 3) == 0)
384 			want_ctx = KCTX_Browser;
385 		else if (old_cmd && old_cmd->req_context)
386 			want_ctx = KCTX_General;
387 
388 		PopulateContextMenu(want_ctx);
389 
390 		PopulateMenuList(keyword_menu, cur_cmd ? cur_cmd->keyword_list : NULL);
391 		PopulateMenuList(   flag_menu, cur_cmd ? cur_cmd->   flag_list : NULL);
392 
393 		redraw();
394 	}
395 
func_callback(Fl_Menu_Button * w,void * data)396 	static void func_callback(Fl_Menu_Button *w, void *data)
397 	{
398 		UI_EditKey *dialog = (UI_EditKey *)data;
399 
400 		int cmd_index = (int)(long)w->mvalue()->user_data_;
401 		SYS_ASSERT(cmd_index >= 0);
402 
403 		dialog->SetNewFunction(cmd_index);
404 	}
405 
ReplaceKeyword(const char * new_word)406 	void ReplaceKeyword(const char *new_word)
407 	{
408 		// delete existing keyword, if any
409 		if (isalnum(params->value()[0]))
410 		{
411 			const char *str = params->value();
412 
413 			int len = 0;
414 
415 			while (str[len] && (isalnum(str[len]) || str[len] == '_'))
416 				len++;
417 
418 			while (str[len] && isspace(str[len]))
419 				len++;
420 
421 			params->replace(0, len, NULL);
422 		}
423 
424 		if (params->size() > 0)
425 			params->replace(0, 0, " ");
426 
427 		params->replace(0, 0, new_word);
428 	}
429 
ReplaceFlag(const char * new_flag)430 	void ReplaceFlag(const char *new_flag)
431 	{
432 		const char *str = params->value();
433 
434 		// if flag is already present, remove it
435 		const char *pos = strstr(str, new_flag);
436 
437 		if (pos)
438 		{
439 			int a = (int)(pos - str);
440 			int b = a + (int)strlen(new_flag);
441 
442 			while (str[b] && isspace(str[b]))
443 				b++;
444 
445 			params->replace(a, b, NULL);
446 
447 			return;
448 		}
449 
450 		// append the flag, adding a space if necessary
451 		int a = params->size();
452 
453 		if (a > 0 && !isspace(str[a-1]))
454 		{
455 			params->replace(a, a, " ");
456 			a += 1;
457 		}
458 
459 		params->replace(a, a, new_flag);
460 	}
461 
keyword_callback(Fl_Menu_Button * w,void * data)462 	static void keyword_callback(Fl_Menu_Button *w, void *data)
463 	{
464 		UI_EditKey *dialog = (UI_EditKey *)data;
465 
466 		dialog->ReplaceKeyword(w->text());
467 	}
468 
flag_callback(Fl_Menu_Button * w,void * data)469 	static void flag_callback(Fl_Menu_Button *w, void *data)
470 	{
471 		UI_EditKey *dialog = (UI_EditKey *)data;
472 
473 		dialog->ReplaceFlag(w->text());
474 	}
475 
476 public:
UI_EditKey(keycode_t _key,key_context_e ctx,const char * _funcname)477 	UI_EditKey(keycode_t _key, key_context_e ctx, const char *_funcname) :
478 		UI_Escapable_Window(400, 306, "Edit Key Binding"),
479 		want_close(false), cancelled(false),
480 		awaiting_key(false),
481 		key(_key)
482 	{
483 		// _key may be zero (when "Add" button is used)
484 
485 		if (ctx == KCTX_NONE)
486 			ctx = KCTX_General;
487 
488 		callback(close_callback, this);
489 
490 		key_name = new Fl_Input(85, 25, 150, 25, "Key:");
491 		key_name->when(FL_WHEN_CHANGED);
492 		key_name->callback((Fl_Callback*)validate_callback, this);
493 
494 		if (key)
495 			key_name->value(M_KeyToString(key));
496 
497 		grab_but = new Fl_Button(255, 25, 90, 25, "Re-bind");
498 		grab_but->callback((Fl_Callback*)grab_key_callback, this);
499 
500 		func_name = new Fl_Output(85,  65, 150, 25, "Function:");
501 
502 		func_choose = new Fl_Menu_Button(255,  65, 90, 25, "Choose");
503 		func_choose->callback((Fl_Callback*) func_callback, this);
504 
505 		context = new Fl_Choice(85, 105, 150, 25, "Mode:");
506 
507 		params = new Fl_Input(85, 145, 300, 25, "Params:");
508 		params->value("");
509 		params->when(FL_WHEN_CHANGED);
510 
511 		keyword_menu = new Fl_Menu_Button( 85, 180, 135, 25, "Keywords...");
512 		keyword_menu->callback((Fl_Callback*) keyword_callback, this);
513 
514 		flag_menu = new Fl_Menu_Button(250, 180, 135, 25, "Flags...");
515 		flag_menu->callback((Fl_Callback*) flag_callback, this);
516 
517 		{ Fl_Group *o = new Fl_Group(0, 240, 400, 66);
518 
519 			o->box(FL_FLAT_BOX);
520 			o->color(WINDOW_BG, WINDOW_BG);
521 
522 			cancel = new Fl_Button(170, 254, 80, 35, "Cancel");
523 			cancel->callback((Fl_Callback*)close_callback, this);
524 
525 			ok_but = new Fl_Button(295, 254, 80, 35, "OK");
526 			ok_but->labelfont(FL_BOLD);
527 			ok_but->callback((Fl_Callback*)ok_callback, this);
528 			ok_but->deactivate();
529 
530 			o->end();
531 		}
532 
533 		end();
534 
535 		// parse line into function name and parameters
536 		Decode(ctx, _funcname);
537 
538 		PopulateContextMenu(ctx == KCTX_NONE ? KCTX_General : ctx);
539 	}
540 
541 
Run(keycode_t * key_v,key_context_e * ctx_v,const char ** func_v,bool start_grabbed)542 	bool Run(keycode_t *key_v, key_context_e *ctx_v,
543 			 const char **func_v, bool start_grabbed)
544 	{
545 		*key_v  = 0;
546 		*ctx_v  = KCTX_NONE;
547 		*func_v = NULL;
548 
549 		// check the initial state
550 		validate_callback(this, this);
551 
552 		set_modal();
553 		show();
554 
555 		// need this for the 'start_grabbed' feature, get FLTK to
556 		// actually put (map) the window onto the screen.
557 		Fl::wait(0.1);
558 		Fl::wait(0.1);
559 
560 		if (start_grabbed)
561 			BeginGrab();
562 		else
563 			Fl::focus(params);
564 
565 		while (! want_close)
566 		{
567 			Fl::wait(0.2);
568 		}
569 
570 		if (cancelled)
571 			return false;
572 
573 		*key_v  = key;
574 		*ctx_v  = ContextFromMenu();
575 		*func_v = Encode();
576 
577 		return true;
578 	}
579 };
580 
581 
582 //------------------------------------------------------------------------
583 
584 
585 class UI_Preferences : public Fl_Double_Window
586 {
587 private:
588 	bool want_quit;
589 	bool want_discard;
590 
591 	char key_sort_mode;
592 	bool key_sort_rev;
593 
594 	// normally zero (not waiting for a key)
595 	int awaiting_line;
596 
597 	static void  close_callback(Fl_Widget *w, void *data);
598 	static void  color_callback(Fl_Button *w, void *data);
599 
600 	static void sort_key_callback(Fl_Button *w, void *data);
601 	static void bind_key_callback(Fl_Button *w, void *data);
602 	static void edit_key_callback(Fl_Button *w, void *data);
603 	static void  del_key_callback(Fl_Button *w, void *data);
604 
605 	static void reset_callback(Fl_Button *w, void *data);
606 
607 public:
608 	UI_Preferences();
609 
610 	void Run();
611 
612 	void LoadValues();
613 	void SaveValues();
614 
615 	void LoadKeys();
616 	void ReloadKeys();
617 
618 	int GridSizeToChoice(int size);
619 
620 	/* FLTK override */
621 	int handle(int event);
622 
623 	void ClearWaiting();
624 	void SetBinding(keycode_t key);
625 
626 	void EnsureKeyVisible(int line);
627 
628 public:
629 	Fl_Tabs *tabs;
630 
631 	Fl_Button *apply_but;
632 	Fl_Button *discard_but;
633 
634 	/* General Tab */
635 
636 	Fl_Round_Button *theme_plastic;
637 	Fl_Round_Button *theme_GTK;
638 	Fl_Round_Button *theme_FLTK;
639 
640 	Fl_Round_Button *cols_bright;
641 	Fl_Round_Button *cols_default;
642 	Fl_Round_Button *cols_custom;
643 
644 	Fl_Button *bg_colorbox;
645 	Fl_Button *ig_colorbox;
646 	Fl_Button *fg_colorbox;
647 
648 	Fl_Check_Button *gen_autoload;
649 	Fl_Check_Button *gen_maximized;
650 	Fl_Check_Button *gen_swapsides;
651 
652 	/* Keys Tab */
653 
654 	Fl_Hold_Browser *key_list;
655 
656 	Fl_Button *key_group;
657 	Fl_Button *key_key;
658 	Fl_Button *key_func;
659 
660 	Fl_Button *key_add;
661 	Fl_Button *key_copy;
662 	Fl_Button *key_edit;
663 	Fl_Button *key_delete;
664 	Fl_Button *key_rebind;
665 
666 	/* Edit Tab */
667 
668 	Fl_Input  *edit_def_port;
669 	Fl_Choice *edit_def_mode;
670 
671 	Fl_Check_Button *edit_samemode;
672 	Fl_Check_Button *edit_autoadjustX;
673 	Fl_Check_Button *edit_add_del;
674 	Fl_Check_Button *edit_full_1S;
675 	Fl_Int_Input    *edit_sectorsize;
676 	Fl_Input        *edit_userratio;
677 	Fl_Choice       *edit_lineinfo;
678 
679 	Fl_Check_Button *brow_smalltex;
680 	Fl_Check_Button *brow_combo;
681 
682 	/* Grid Tab */
683 
684 	Fl_Check_Button *gen_scrollbars;
685 
686 	Fl_Choice *grid_cur_style;
687 	Fl_Check_Button *grid_snap;
688 	Fl_Check_Button *grid_enabled;
689 	Fl_Choice *grid_size;
690 
691 	Fl_Check_Button *grid_hide_free;
692 	Fl_Check_Button *grid_flatrender;
693 	Fl_Check_Button *grid_spriterend;
694 	Fl_Check_Button *grid_indicator;
695 
696 	Fl_Button *dotty_axis;
697 	Fl_Button *dotty_major;
698 	Fl_Button *dotty_minor;
699 	Fl_Button *dotty_point;
700 
701 	Fl_Button *normal_axis;
702 	Fl_Button *normal_main;
703 	Fl_Button *normal_flat;
704 	Fl_Button *normal_small;
705 
706 	/* 3D Tab */
707 
708 	Fl_Float_Input  *rend_aspect;;
709 
710 	Fl_Check_Button *rend_high_detail;
711 	Fl_Check_Button *rend_lock_grav;
712 
713 	Fl_Choice *rend_far_clip;
714 
715 	/* Nodes Tab */
716 
717 	Fl_Check_Button *nod_on_save;
718 	Fl_Check_Button *nod_fast;
719 	Fl_Check_Button *nod_warn;
720 
721 	Fl_Choice *nod_factor;
722 
723 	Fl_Check_Button *nod_gl_nodes;
724 	Fl_Check_Button *nod_force_v5;
725 	Fl_Check_Button *nod_force_zdoom;
726 	Fl_Check_Button *nod_compress;
727 
728 	/* Other Tab */
729 
730 	Fl_Button * reset_conf;
731 	Fl_Button * reset_keys;
732 };
733 
734 
735 #define R_SPACES  "  "
736 
737 
UI_Preferences()738 UI_Preferences::UI_Preferences() :
739 	  Fl_Double_Window(PREF_WINDOW_W, PREF_WINDOW_H, PREF_WINDOW_TITLE),
740 	  want_quit(false), want_discard(false),
741 	  key_sort_mode('k'), key_sort_rev(false),
742 	  awaiting_line(0)
743 {
744 	if (gui_color_set == 2)
745 		color(fl_gray_ramp(4));
746 	else
747 		color(WINDOW_BG);
748 
749 	callback(close_callback, this);
750 
751 	{ tabs = new Fl_Tabs(0, 0, PREF_WINDOW_W-15, PREF_WINDOW_H-70);
752 	  // tabs->selection_color(FL_WHITE);
753 
754 	  /* ---- General Tab ---- */
755 
756 	  { Fl_Group* o = new Fl_Group(0, 25, 585, 405, " General" R_SPACES);
757 		o->labelsize(16);
758 		o->selection_color(FL_DARK2);
759 		// o->hide();
760 
761 		{ Fl_Box* o = new Fl_Box(25, 45, 145, 30, "GUI Appearance");
762 		  o->labelfont(FL_BOLD);
763 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
764 		}
765 		{ Fl_Group* o = new Fl_Group(45, 90, 250, 115);
766 		  { theme_plastic = new Fl_Round_Button(50, 120, 165, 25, " Plastic theme ");
767 			theme_plastic->type(102);
768 			theme_plastic->down_box(FL_ROUND_DOWN_BOX);
769 		  }
770 		  { theme_GTK = new Fl_Round_Button(50, 90, 150, 25, " GTK+ theme ");
771 			theme_GTK->type(102);
772 			theme_GTK->down_box(FL_ROUND_DOWN_BOX);
773 		  }
774 		  { theme_FLTK = new Fl_Round_Button(50, 150, 150, 25, " FLTK theme");
775 			theme_FLTK->type(102);
776 			theme_FLTK->down_box(FL_ROUND_DOWN_BOX);
777 		  }
778 		  o->end();
779 		}
780 		{ Fl_Group* o = new Fl_Group(220, 90, 190, 90);
781 		  { cols_bright = new Fl_Round_Button(245, 90, 140, 25, "bright colors");
782 			cols_bright->type(102);
783 			cols_bright->down_box(FL_ROUND_DOWN_BOX);
784 		  }
785 		  { cols_default = new Fl_Round_Button(245, 120, 135, 25, "default colors");
786 			cols_default->type(102);
787 			cols_default->down_box(FL_ROUND_DOWN_BOX);
788 		  }
789 		  { cols_custom = new Fl_Round_Button(245, 150, 165, 25, "custom colors   ---->");
790 			cols_custom->type(102);
791 			cols_custom->down_box(FL_ROUND_DOWN_BOX);
792 		  }
793 		  o->end();
794 		}
795 		{ Fl_Group* o = new Fl_Group(385, 80, 205, 100);
796 		  o->color(FL_LIGHT1);
797 		  o->align(Fl_Align(FL_ALIGN_BOTTOM_LEFT|FL_ALIGN_INSIDE));
798 		  { bg_colorbox = new Fl_Button(430, 90, 45, 25, "background");
799 			bg_colorbox->box(FL_BORDER_BOX);
800 			bg_colorbox->align(Fl_Align(FL_ALIGN_RIGHT));
801 			bg_colorbox->callback((Fl_Callback*)color_callback, this);
802 		  }
803 		  { ig_colorbox = new Fl_Button(430, 120, 45, 25, "input bg");
804 			ig_colorbox->box(FL_BORDER_BOX);
805 			ig_colorbox->color(FL_BACKGROUND2_COLOR);
806 			ig_colorbox->align(Fl_Align(FL_ALIGN_RIGHT));
807 			ig_colorbox->callback((Fl_Callback*)color_callback, this);
808 		  }
809 		  { fg_colorbox = new Fl_Button(430, 150, 45, 25, "text color");
810 			fg_colorbox->box(FL_BORDER_BOX);
811 			fg_colorbox->color(FL_GRAY0);
812 			fg_colorbox->align(Fl_Align(FL_ALIGN_RIGHT));
813 			fg_colorbox->callback((Fl_Callback*)color_callback, this);
814 		  }
815 		  o->end();
816 		}
817 		{ Fl_Box* o = new Fl_Box(30, 240, 280, 35, "Miscellaneous");
818 		  o->labelfont(FL_BOLD);
819 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
820 		}
821 		{ gen_autoload = new Fl_Check_Button(50, 280, 380, 25, " automatically open the most recent pwad");
822 		}
823 		{ gen_swapsides = new Fl_Check_Button(50, 310, 380, 25, " swap upper and lower sidedefs in Linedef panel");
824 		}
825 		{ gen_maximized = new Fl_Check_Button(50, 340, 380, 25, " maximize the window when Eureka starts");
826 		  // not supported on MacOS X
827 		  // (on that platform we should restore last window position, but I don't
828 		  //  know how to code that)
829 #ifdef __APPLE__
830 		  gen_maximized->hide();
831 #endif
832 		}
833 		o->end();
834 	  }
835 
836 	  /* ---- Key bindings Tab ---- */
837 
838 	  { Fl_Group* o = new Fl_Group(0, 25, 585, 410, " Keys" R_SPACES);
839 		o->labelsize(16);
840 		o->selection_color(FL_DARK2);
841 		o->hide();
842 
843 		{ Fl_Box* o = new Fl_Box(20, 45, 355, 30, "Key Bindings");
844 		  o->labelfont(FL_BOLD);
845 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
846 		}
847 
848 		{ key_key = new Fl_Button(25, 87, 125, 25, "KEY");
849 		  key_key->color((Fl_Color)231);
850 		  key_key->align(Fl_Align(FL_ALIGN_INSIDE));
851 		  key_key->callback((Fl_Callback*)sort_key_callback, this);
852 		}
853 		{ key_group = new Fl_Button(155, 87, 75, 25, "MODE");
854 		  key_group->color((Fl_Color)231);
855 		  key_group->align(Fl_Align(FL_ALIGN_INSIDE));
856 		  key_group->callback((Fl_Callback*)sort_key_callback, this);
857 		}
858 		{ key_func = new Fl_Button(235, 87, 205, 25, "FUNCTION");
859 		  key_func->color((Fl_Color)231);
860 		  key_func->align(Fl_Align(FL_ALIGN_INSIDE));
861 		  key_func->callback((Fl_Callback*)sort_key_callback, this);
862 		}
863 		{ key_list = new Fl_Hold_Browser(20, 115, 442, 308);
864 		  key_list->textfont(FL_COURIER);
865 		  key_list->has_scrollbar(Fl_Browser_::VERTICAL);
866 		}
867 		{ key_add = new Fl_Button(480, 155, 85, 30, "&Add");
868 		  key_add->callback((Fl_Callback*)edit_key_callback, this);
869 		}
870 		{ key_copy = new Fl_Button(480, 195, 85, 30, "&Copy");
871 		  key_copy->callback((Fl_Callback*)edit_key_callback, this);
872 		}
873 		{ key_edit = new Fl_Button(480, 235, 85, 30, "&Edit");
874 		  key_edit->callback((Fl_Callback*)edit_key_callback, this);
875 		}
876 		{ key_delete = new Fl_Button(480, 275, 85, 30, "Delete");
877 		  key_delete->callback((Fl_Callback*)del_key_callback, this);
878 		  key_delete->shortcut(FL_Delete);
879 		}
880 		{ key_rebind = new Fl_Button(480, 370, 85, 30, "&Re-bind");
881 		  key_rebind->callback((Fl_Callback*)bind_key_callback, this);
882 		  // key_rebind->shortcut(FL_Enter);
883 		}
884 		o->end();
885 	  }
886 
887 	  /* ---- Editing Tab ---- */
888 
889 	  { Fl_Group* o = new Fl_Group(0, 25, 585, 410, " Editing" R_SPACES);
890 		o->labelsize(16);
891 		o->selection_color(FL_DARK2);
892 		o->hide();
893 
894 		{ Fl_Box* o = new Fl_Box(25, 45, 355, 30, "Editing Options");
895 		  o->labelfont(FL_BOLD);
896 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
897 		}
898 		{ edit_def_port = new Fl_Input(150, 85, 95, 25, "default port: ");
899 		  edit_def_port->align(FL_ALIGN_LEFT);
900 		}
901 		{ edit_def_mode = new Fl_Choice(440, 85, 105, 25, "default edit mode: ");
902 		  edit_def_mode->align(FL_ALIGN_LEFT);
903 		  edit_def_mode->add("Things|Linedefs|Sectors|Vertices");
904 		}
905 		{ edit_autoadjustX = new Fl_Check_Button(50, 150, 260, 30, " auto-adjust X offsets");
906 		}
907 		{ edit_samemode = new Fl_Check_Button(50, 180, 270, 30, " same mode key will clear selection");
908 		}
909 		{ edit_add_del = new Fl_Check_Button(50, 210, 270, 30, " enable sidedef ADD / DEL buttons");
910 		}
911 		{ edit_full_1S = new Fl_Check_Button(50, 240, 270, 30, " show all textures on a one-sided linedef");
912 		}
913 		{ edit_sectorsize = new Fl_Int_Input(440, 120, 105, 25, "new sector size:");
914 		}
915 		{ edit_userratio = new Fl_Input(440, 150, 105, 25, "user ratio:");
916 		}
917 		{ edit_lineinfo = new Fl_Choice(440, 180, 105, 25, "line info:");
918 		  edit_lineinfo->add("NONE|Length|Angle|Ratio|Len+Ang|Len+Ratio");
919 		}
920 
921 		{ Fl_Box* o = new Fl_Box(25, 295, 355, 30, "Browser Options");
922 		  o->labelfont(FL_BOLD);
923 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
924 		}
925 		{ brow_smalltex = new Fl_Check_Button(50, 330, 265, 30, " smaller textures");
926 		}
927 		{ brow_combo = new Fl_Check_Button(50, 360, 265, 30, " combine flats and textures in a single browser");
928 		}
929 		o->end();
930 	  }
931 
932 	  /* ---- Grid Tab ---- */
933 
934 	  { Fl_Group* o = new Fl_Group(0, 25, 585, 410, " Grid" R_SPACES);
935 		o->labelsize(16);
936 		o->selection_color(FL_DARK2);
937 		o->hide();
938 
939 		{ Fl_Box* o = new Fl_Box(25, 45, 355, 30, "Map Grid and Scrolling");
940 		  o->labelfont(FL_BOLD);
941 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
942 		}
943 		{ grid_cur_style = new Fl_Choice(125, 90, 95, 25, "grid style ");
944 		  grid_cur_style->add("Squares|Dotty");
945 		}
946 		{ grid_enabled = new Fl_Check_Button(50, 125, 95, 25, " default grid to ON");
947 		}
948 		{ grid_snap = new Fl_Check_Button(50, 155, 235, 25, " default snap to ON");
949 		}
950 		{ grid_flatrender = new Fl_Check_Button(50, 185, 270, 25, " default sector-render to ON");
951 		}
952 		{ grid_spriterend = new Fl_Check_Button(50, 215, 270, 25, " default sprites to ON");
953 		}
954 		{ grid_size = new Fl_Choice(420, 90, 95, 25, "default grid size ");
955 		  grid_size->add("1024|512|256|192|128|64|32|16|8|4|2");
956 		}
957 		{ gen_scrollbars = new Fl_Check_Button(300, 125, 245, 25, " enable scroll-bars for map view");
958 		}
959 		{ grid_indicator = new Fl_Check_Button(300, 155, 245, 25, " enable snap-pos indicator");
960 		}
961 		{ grid_hide_free = new Fl_Check_Button(300, 185, 245, 25, " hide grid in FREE mode");
962 		}
963 
964 		{ Fl_Box* o = new Fl_Box(25, 270, 355, 30, "Grid Colors");
965 		  o->labelfont(FL_BOLD);
966 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
967 		}
968 
969 		{ normal_axis = new Fl_Button(150 + 0*55, 300, 45, 25, "Normal Grid : ");
970 		  normal_axis->box(FL_BORDER_BOX);
971 		  normal_axis->align(FL_ALIGN_LEFT);
972 		  normal_axis->callback((Fl_Callback*)color_callback, this);
973 		  normal_axis->tooltip("X/Y axis color");
974 		}
975 		{ normal_main = new Fl_Button(150 + 1*55, 300, 45, 25, "");
976 		  normal_main->box(FL_BORDER_BOX);
977 		  normal_main->align(FL_ALIGN_RIGHT);
978 		  normal_main->callback((Fl_Callback*)color_callback, this);
979 		  normal_main->tooltip("large square color");
980 		}
981 		{ normal_flat = new Fl_Button(150 + 2*55, 300, 45, 25, "");
982 		  normal_flat->box(FL_BORDER_BOX);
983 		  normal_flat->align(FL_ALIGN_RIGHT);
984 		  normal_flat->callback((Fl_Callback*)color_callback, this);
985 		  normal_flat->tooltip("64x64 square color");
986 		}
987 		{ normal_small = new Fl_Button(150 + 3*55, 300, 45, 25, "");
988 		  normal_small->box(FL_BORDER_BOX);
989 		  normal_small->align(FL_ALIGN_RIGHT);
990 		  normal_small->callback((Fl_Callback*)color_callback, this);
991 		  normal_small->tooltip("small square color");
992 		}
993 
994 		{ dotty_axis = new Fl_Button(150 + 0*55, 340, 45, 25, "Dotty Grid : ");
995 		  dotty_axis->box(FL_BORDER_BOX);
996 		  dotty_axis->align(FL_ALIGN_LEFT);
997 		  dotty_axis->callback((Fl_Callback*)color_callback, this);
998 		  dotty_axis->tooltip("X/Y axis color");
999 		}
1000 		{ dotty_major = new Fl_Button(150 + 1*55, 340, 45, 25, "");
1001 		  dotty_major->box(FL_BORDER_BOX);
1002 		  dotty_major->align(FL_ALIGN_RIGHT);
1003 		  dotty_major->callback((Fl_Callback*)color_callback, this);
1004 		  dotty_major->tooltip("large square color");
1005 		}
1006 		{ dotty_minor = new Fl_Button(150 + 2*55, 340, 45, 25, "");
1007 		  dotty_minor->box(FL_BORDER_BOX);
1008 		  dotty_minor->align(FL_ALIGN_RIGHT);
1009 		  dotty_minor->callback((Fl_Callback*)color_callback, this);
1010 		  dotty_minor->tooltip("small square color");
1011 		}
1012 		{ dotty_point = new Fl_Button(150 + 3*55, 340, 45, 25, "");
1013 		  dotty_point->box(FL_BORDER_BOX);
1014 		  dotty_point->align(FL_ALIGN_RIGHT);
1015 		  dotty_point->callback((Fl_Callback*)color_callback, this);
1016 		  dotty_point->tooltip("dot color");
1017 		}
1018 
1019 		o->end();
1020 	  }
1021 
1022 	  /* ---- 3D Tab ---- */
1023 
1024 	  { Fl_Group* o = new Fl_Group(0, 25, 585, 410, " 3D View" R_SPACES);
1025 		o->labelsize(16);
1026 		o->selection_color(FL_DARK2);
1027 		o->hide();
1028 
1029 		{ Fl_Box* o = new Fl_Box(25, 45, 280, 30, "3D View Settings");
1030 		  o->labelfont(FL_BOLD);
1031 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
1032 		}
1033 		{ rend_aspect = new Fl_Float_Input(190, 90, 95, 25, "Pixel aspect ratio: ");
1034 
1035 		  Fl_Box* o = new Fl_Box(300, 90, 150, 25, "(higher is wider, default is 0.83)");
1036 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
1037 		}
1038 		{ rend_lock_grav = new Fl_Check_Button(50, 125, 360, 30, " Locked gravity -- cannot move up or down");
1039 		}
1040 		{ rend_high_detail = new Fl_Check_Button(50, 155, 360, 30, " High detail -- slower but looks better");
1041 #ifndef NO_OPENGL
1042 		  rend_high_detail->hide();
1043 #endif
1044 		}
1045 		{ rend_far_clip = new Fl_Choice(195, 160, 100, 30, "Far clip distance: ");
1046 		  rend_far_clip->add("32768|16384|8192|4096|2048|1024");
1047 #ifdef NO_OPENGL
1048 		  rend_far_clip->hide();
1049 #endif
1050 		}
1051 
1052 		o->end();
1053 	  }
1054 
1055 	  /* ---- Nodes Tab ---- */
1056 
1057 	  { Fl_Group* o = new Fl_Group(0, 25, 585, 410, " Nodes" R_SPACES);
1058 		o->labelsize(16);
1059 		o->selection_color(FL_DARK2);
1060 		o->hide();
1061 
1062 		{ Fl_Box* o = new Fl_Box(25, 45, 280, 30, "Node Building");
1063 		  o->labelfont(FL_BOLD);
1064 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
1065 		}
1066 		{ nod_on_save = new Fl_Check_Button(50, 80, 220, 30, " Always build nodes after saving   (recommended)");
1067 		}
1068 		{ nod_fast = new Fl_Check_Button(50, 110, 440, 30, " Fast mode   (the nodes may not be as good)");
1069 		}
1070 		{ nod_warn = new Fl_Check_Button(50, 140, 220, 30, " Warning messages in the logs");
1071 		}
1072 
1073 		{ Fl_Box* o = new Fl_Box(25, 205, 250, 30, "Advanced BSP Settings");
1074 		  o->labelfont(FL_BOLD);
1075 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
1076 		}
1077 		{ nod_gl_nodes = new Fl_Check_Button(50, 245, 150, 30, " Build GL-Nodes");
1078 		}
1079 		{ nod_force_v5 = new Fl_Check_Button(50, 275, 250, 30, " Force V5 of GL-Nodes");
1080 		}
1081 		{ nod_force_zdoom = new Fl_Check_Button(50, 305, 250, 30, " Force ZDoom format of normal nodes");
1082 		}
1083 		// CURRENTLY HIDDEN -- NOT SURE IT IS WORTH HAVING
1084 		{ nod_compress = new Fl_Check_Button(50, 335, 250, 30, " Force zlib compression");
1085 		  nod_compress->hide();
1086 		}
1087 		{ nod_factor = new Fl_Choice(160, 345, 180, 30, "Seg split logic: ");
1088 		  nod_factor->add("NORMAL|Minimize Splits|Balance BSP Tree");
1089 		}
1090 		o->end();
1091 	  }
1092 
1093 	  /* ---- Other Tab ---- */
1094 
1095 	  { Fl_Group* o = new Fl_Group(0, 25, 585, 410, " Other" R_SPACES);
1096 		o->labelsize(16);
1097 		o->selection_color(FL_DARK2);
1098 		o->hide();
1099 
1100 		{ Fl_Box* o = new Fl_Box(25, 255, 280, 30, "Configuration Reset");
1101 		  o->labelfont(FL_BOLD);
1102 		  o->align(Fl_Align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE));
1103 		}
1104 		{ reset_conf = new Fl_Button(100, 290, 200, 30, "Reset Config Settings");
1105 		  reset_conf->callback((Fl_Callback*)reset_callback, this);
1106 		}
1107 		{ reset_keys = new Fl_Button(100, 330, 200, 30, "Reset Key Bindings");
1108 		  reset_keys->callback((Fl_Callback*)reset_callback, this);
1109 		}
1110 		o->end();
1111 	  }
1112 	  tabs->end();
1113 	}
1114 	{ apply_but = new Fl_Button(PREF_WINDOW_W-150, PREF_WINDOW_H-50, 95, 35, "Apply");
1115 	  apply_but->labelfont(FL_BOLD);
1116 	  apply_but->callback(close_callback, this);
1117 	}
1118 	{ discard_but = new Fl_Button(PREF_WINDOW_W-290, PREF_WINDOW_H-50, 95, 35, "Discard");
1119 	  discard_but->callback(close_callback, this);
1120 	}
1121 
1122 	end();
1123 }
1124 
1125 
1126 //------------------------------------------------------------------------
1127 
close_callback(Fl_Widget * w,void * data)1128 void UI_Preferences::close_callback(Fl_Widget *w, void *data)
1129 {
1130 	UI_Preferences *prefs = (UI_Preferences *)data;
1131 
1132 	prefs->want_quit = true;
1133 
1134 	if (w == prefs->discard_but)
1135 		prefs->want_discard = true;
1136 }
1137 
1138 
color_callback(Fl_Button * w,void * data)1139 void UI_Preferences::color_callback(Fl_Button *w, void *data)
1140 {
1141 //	UI_Preferences *dialog = (UI_Preferences *)data;
1142 
1143 	uchar r, g, b;
1144 
1145 	Fl::get_color(w->color(), r, g, b);
1146 
1147 	if (! fl_color_chooser("New color:", r, g, b, 3))
1148 		return;
1149 
1150 	w->color(fl_rgb_color(r, g, b));
1151 
1152 	w->redraw();
1153 }
1154 
1155 
bind_key_callback(Fl_Button * w,void * data)1156 void UI_Preferences::bind_key_callback(Fl_Button *w, void *data)
1157 {
1158 	UI_Preferences *prefs = (UI_Preferences *)data;
1159 
1160 	int line = prefs->key_list->value();
1161 	if (line < 1)
1162 	{
1163 		fl_beep();
1164 		return;
1165 	}
1166 
1167 	prefs->EnsureKeyVisible(line);
1168 
1169 
1170 	int bind_idx = line - 1;
1171 
1172 	// show we're ready to accept a new key
1173 
1174 	const char *str = M_StringForBinding(bind_idx, true /* changing_key */);
1175 	SYS_ASSERT(str);
1176 
1177 	prefs->key_list->text(line, str);
1178 	prefs->key_list->selection_color(FL_YELLOW);
1179 
1180 	Fl::focus(prefs);
1181 
1182 	prefs->awaiting_line = line;
1183 }
1184 
1185 
sort_key_callback(Fl_Button * w,void * data)1186 void UI_Preferences::sort_key_callback(Fl_Button *w, void *data)
1187 {
1188 	UI_Preferences *prefs = (UI_Preferences *)data;
1189 
1190 	if (w == prefs->key_group)
1191 	{
1192 		if (prefs->key_sort_mode != 'c')
1193 		{
1194 			prefs->key_sort_mode  = 'c';
1195 			prefs->key_sort_rev = false;
1196 		}
1197 		else
1198 			prefs->key_sort_rev = !prefs->key_sort_rev;
1199 	}
1200 	else if (w == prefs->key_key)
1201 	{
1202 		if (prefs->key_sort_mode != 'k')
1203 		{
1204 			prefs->key_sort_mode  = 'k';
1205 			prefs->key_sort_rev = false;
1206 		}
1207 		else
1208 			prefs->key_sort_rev = !prefs->key_sort_rev;
1209 	}
1210 	else if (w == prefs->key_func)
1211 	{
1212 		if (prefs->key_sort_mode != 'f')
1213 		{
1214 			prefs->key_sort_mode  = 'f';
1215 			prefs->key_sort_rev = false;
1216 		}
1217 		else
1218 			prefs->key_sort_rev = !prefs->key_sort_rev;
1219 	}
1220 
1221 	prefs->LoadKeys();
1222 }
1223 
1224 
edit_key_callback(Fl_Button * w,void * data)1225 void UI_Preferences::edit_key_callback(Fl_Button *w, void *data)
1226 {
1227 	UI_Preferences *prefs = (UI_Preferences *)data;
1228 
1229 	bool is_add  = (w == prefs->key_add);
1230 	bool is_copy = (w == prefs->key_copy);
1231 
1232 
1233 	keycode_t     new_key  = 0;
1234 	key_context_e new_context = KCTX_General;
1235 	const char *  new_func = "Nothing";
1236 
1237 
1238 	int bind_idx = -1;
1239 
1240 
1241 	if (! is_add)
1242 	{
1243 		int line = prefs->key_list->value();
1244 		if (line < 1)
1245 		{
1246 			fl_beep();
1247 			return;
1248 		}
1249 
1250 		prefs->EnsureKeyVisible(line);
1251 
1252 		bind_idx = line - 1;
1253 		SYS_ASSERT(bind_idx >= 0);
1254 
1255 		M_GetBindingInfo(bind_idx, &new_key, &new_context);
1256 		new_func = M_StringForFunc(bind_idx);
1257 	}
1258 
1259 
1260 	bool start_grabbed = false;  //???  is_add || is_copy;
1261 
1262 	UI_EditKey *dialog = new UI_EditKey(new_key, new_context, new_func);
1263 
1264 	bool was_ok = dialog->Run(&new_key, &new_context, &new_func, start_grabbed);
1265 
1266 	if (was_ok)
1267 	{
1268 		// assume we can set it, since the dialog validated it
1269 
1270 		if (is_add || is_copy)
1271 		{
1272 			M_AddLocalBinding(bind_idx, new_key, new_context, new_func);
1273 
1274 			if (is_copy)
1275 				bind_idx++;
1276 			else
1277 				bind_idx = M_NumBindings() - 1;
1278 		}
1279 		else
1280 		{
1281 			M_SetLocalBinding(bind_idx, new_key, new_context, new_func);
1282 		}
1283 	}
1284 
1285 	delete dialog;
1286 
1287 
1288 	// for a new binding, make sure it is visible and selected
1289 
1290 	if ((is_add || is_copy) && was_ok && bind_idx >= 0)
1291 	{
1292 		// expand the browser size with a dummy line
1293 		// [ the ReloadKeys() below will grab the correct text ]
1294 		prefs->key_list->add("");
1295 
1296 		SYS_ASSERT(bind_idx >= 0);
1297 		int line = 1 + bind_idx;
1298 
1299 		prefs->key_list->select(line);
1300 		prefs->EnsureKeyVisible(line);
1301 	}
1302 
1303 	prefs->ReloadKeys();
1304 	prefs->redraw();
1305 
1306 	Fl::focus(prefs->key_list);
1307 }
1308 
1309 
del_key_callback(Fl_Button * w,void * data)1310 void UI_Preferences::del_key_callback(Fl_Button *w, void *data)
1311 {
1312 	UI_Preferences *prefs = (UI_Preferences *)data;
1313 
1314 	int line = prefs->key_list->value();
1315 	if (line < 1)
1316 	{
1317 		fl_beep();
1318 		return;
1319 	}
1320 
1321 	M_DeleteLocalBinding(line - 1);
1322 
1323 	prefs->key_list->remove(line);
1324 	prefs->ReloadKeys();
1325 
1326 	if (line <= prefs->key_list->size())
1327 	{
1328 		prefs->key_list->select(line);
1329 
1330 		Fl::focus(prefs->key_list);
1331 	}
1332 }
1333 
1334 
reset_callback(Fl_Button * w,void * data)1335 void UI_Preferences::reset_callback(Fl_Button *w, void *data)
1336 {
1337 	UI_Preferences *prefs = (UI_Preferences *)data;
1338 
1339 	bool is_keys = (w == prefs->reset_keys);
1340 
1341 	int res = DLG_Confirm("Cancel|&Reset",
1342 		"This will reset all %s to their default values, "
1343 		"removing any changes you may have made."
1344 		"\n\n"
1345 		"If you continue, you can still back out by "
1346 		"using the \"Discard\" button at the bottom of "
1347 		"the Preferences window."
1348 		"\n  ",
1349 		is_keys ? "key bindings" : "preferences");
1350 
1351 	if (res <= 0)
1352 		return;
1353 
1354 	if (is_keys)
1355 	{
1356 		M_CopyBindings(true /* from_defaults */);
1357 
1358 		prefs->LoadKeys();
1359 	}
1360 	else
1361 	{
1362 		if (M_ParseDefaultConfigFile() != 0)
1363 		{
1364 			DLG_Notify("Installation problem: failed to find the \"defaults.cfg\" file!");
1365 		}
1366 		else
1367 		{
1368 			prefs->LoadValues();
1369 		}
1370 	}
1371 }
1372 
1373 
Run()1374 void UI_Preferences::Run()
1375 {
1376 	if (last_active_tab < tabs->children())
1377 		tabs->value(tabs->child(last_active_tab));
1378 
1379 	M_CopyBindings();
1380 
1381 	LoadValues();
1382 	LoadKeys();
1383 
1384 	set_modal();
1385 
1386 	show();
1387 
1388 	while (! want_quit)
1389 	{
1390 		Fl::wait(0.2);
1391 	}
1392 
1393 	last_active_tab = tabs->find(tabs->value());
1394 
1395 	if (want_discard)
1396 	{
1397 		LogPrintf("Preferences: discarded changes\n");
1398 		return;
1399 	}
1400 
1401 	SaveValues();
1402 	M_WriteConfigFile();
1403 
1404 	M_ApplyBindings();
1405 	M_SaveBindings();
1406 }
1407 
1408 
GridSizeToChoice(int size)1409 int UI_Preferences::GridSizeToChoice(int size)
1410 {
1411 	if (size > 512) return 0;
1412 	if (size > 256) return 1;
1413 	if (size > 128) return 2;
1414 	if (size >  64) return 3;
1415 	if (size >  32) return 4;
1416 	if (size >  16) return 5;
1417 	if (size >   8) return 6;
1418 	if (size >   4) return 7;
1419 	if (size >   2) return 8;
1420 
1421 	return 9;
1422 }
1423 
1424 
LoadValues()1425 void UI_Preferences::LoadValues()
1426 {
1427 	/* Theme stuff */
1428 
1429 	switch (gui_scheme)
1430 	{
1431 		case 0: theme_FLTK->value(1); break;
1432 		case 1: theme_GTK->value(1); break;
1433 		case 2: theme_plastic->value(1); break;
1434 	}
1435 
1436 	switch (gui_color_set)
1437 	{
1438 		case 0: cols_default->value(1); break;
1439 		case 1: cols_bright->value(1); break;
1440 		case 2: cols_custom->value(1); break;
1441 	}
1442 
1443 	bg_colorbox->color(gui_custom_bg);
1444 	ig_colorbox->color(gui_custom_ig);
1445 	fg_colorbox->color(gui_custom_fg);
1446 
1447 	/* General Tab */
1448 
1449 	gen_autoload   ->value(auto_load_recent ? 1 : 0);
1450 	gen_maximized  ->value(begin_maximized  ? 1 : 0);
1451 	gen_swapsides  ->value(swap_sidedefs    ? 1 : 0);
1452 
1453 	/* Edit Tab */
1454 
1455 	edit_def_port->value(default_port);
1456 	edit_def_mode->value(CLAMP(0, default_edit_mode, 3));
1457 	edit_lineinfo->value(CLAMP(0, highlight_line_info, 5));
1458 
1459 	edit_sectorsize->value(Int_TmpStr(new_sector_size));
1460 	edit_samemode->value(same_mode_clears_selection ? 1 : 0);
1461 	edit_add_del->value(sidedef_add_del_buttons ? 1 : 0);
1462 	edit_full_1S->value(show_full_one_sided ? 1 : 0);
1463 	edit_autoadjustX->value(leave_offsets_alone ? 0 : 1);
1464 
1465 	brow_smalltex->value(browser_small_tex ? 1 : 0);
1466 	brow_combo->value(browser_combine_tex ? 1 : 0);
1467 
1468 	char ratio_buf[256];
1469 	snprintf(ratio_buf, sizeof(ratio_buf), "%d:%d", grid_ratio_high, grid_ratio_low);
1470 	edit_userratio->value(ratio_buf);
1471 
1472 	/* Grid Tab */
1473 
1474 	if (grid_style < 0 || grid_style > 1)
1475 		grid_style = 1;
1476 
1477 	if (grid_default_mode < 0 || grid_default_mode > 1)
1478 		grid_default_mode = 1;
1479 
1480 	grid_cur_style->value(grid_style);
1481 	grid_enabled->value(grid_default_mode);
1482 	grid_snap->value(grid_default_snap ? 1 : 0);
1483 	grid_size->value(GridSizeToChoice(grid_default_size));
1484 	grid_hide_free ->value(grid_hide_in_free_mode ? 1 : 0);
1485 	grid_flatrender->value(sector_render_default ? 1 : 0);
1486 	grid_spriterend->value(thing_render_default ? 1 : 0);
1487 	grid_indicator->value(grid_snap_indicator ? 1 : 0);
1488 
1489 	gen_scrollbars ->value(map_scroll_bars ? 1 : 0);
1490 
1491 	dotty_axis ->color(dotty_axis_col);
1492 	dotty_major->color(dotty_major_col);
1493 	dotty_minor->color(dotty_minor_col);
1494 	dotty_point->color(dotty_point_col);
1495 
1496 	normal_axis ->color(normal_axis_col);
1497 	normal_main ->color(normal_main_col);
1498 	normal_flat ->color(normal_flat_col);
1499 	normal_small->color(normal_small_col);
1500 
1501 	/* 3D Tab */
1502 
1503 	render_pixel_aspect = CLAMP(25, render_pixel_aspect, 400);
1504 
1505 	char aspect_buf[64];
1506 	snprintf(aspect_buf, sizeof(aspect_buf), "%1.2f", render_pixel_aspect / 100.0);
1507 	rend_aspect->value(aspect_buf);
1508 
1509 	rend_high_detail->value(render_high_detail ? 1 : 0);
1510 	rend_lock_grav->value(render_lock_gravity ? 1 : 0);
1511 
1512 	if (render_far_clip > 24000)
1513 		rend_far_clip->value(0);
1514 	else if (render_far_clip > 12000)
1515 		rend_far_clip->value(1);
1516 	else if (render_far_clip > 6000)
1517 		rend_far_clip->value(2);
1518 	else if (render_far_clip > 3000)
1519 		rend_far_clip->value(3);
1520 	else if (render_far_clip > 1500)
1521 		rend_far_clip->value(4);
1522 	else
1523 		rend_far_clip->value(5);
1524 
1525 	/* Nodes Tab */
1526 
1527 	nod_on_save->value(bsp_on_save ? 1 : 0);
1528 	nod_fast->value(bsp_fast ? 1 : 0);
1529 	nod_warn->value(bsp_warnings ? 1 : 0);
1530 
1531 	if (bsp_split_factor < 7)
1532 		nod_factor->value(2);	// Balanced BSP tree
1533 	else if (bsp_split_factor > 15)
1534 		nod_factor->value(1);	// Minimize Splits
1535 	else
1536 		nod_factor->value(0);	// NORMAL
1537 
1538 	nod_gl_nodes->value(bsp_gl_nodes ? 1 : 0);
1539 	nod_force_v5->value(bsp_force_v5 ? 1 : 0);
1540 	nod_force_zdoom->value(bsp_force_zdoom ? 1 : 0);
1541 	nod_compress->value(bsp_compressed ? 1 : 0);
1542 
1543 	/* Other Tab */
1544 
1545 }
1546 
1547 
SaveValues()1548 void UI_Preferences::SaveValues()
1549 {
1550 	/* Theme stuff */
1551 
1552 	if (theme_FLTK->value())
1553 		gui_scheme = 0;
1554 	else if (theme_GTK->value())
1555 		gui_scheme = 1;
1556 	else
1557 		gui_scheme = 2;
1558 
1559 	if (cols_default->value())
1560 		gui_color_set = 0;
1561 	else if (cols_bright->value())
1562 		gui_color_set = 1;
1563 	else
1564 		gui_color_set = 2;
1565 
1566 	gui_custom_bg = (rgb_color_t) bg_colorbox->color();
1567 	gui_custom_ig = (rgb_color_t) ig_colorbox->color();
1568 	gui_custom_fg = (rgb_color_t) fg_colorbox->color();
1569 
1570 	// update the colors
1571 	// FIXME: how to reset the "default" colors??
1572 	if (gui_color_set == 1)
1573 	{
1574 		Fl::background(236, 232, 228);
1575 		Fl::background2(255, 255, 255);
1576 		Fl::foreground(0, 0, 0);
1577 
1578 		main_win->redraw();
1579 	}
1580 	else if (gui_color_set == 2)
1581 	{
1582 		Fl::background (RGB_RED(gui_custom_bg), RGB_GREEN(gui_custom_bg), RGB_BLUE(gui_custom_bg));
1583 		Fl::background2(RGB_RED(gui_custom_ig), RGB_GREEN(gui_custom_ig), RGB_BLUE(gui_custom_ig));
1584 		Fl::foreground (RGB_RED(gui_custom_fg), RGB_GREEN(gui_custom_fg), RGB_BLUE(gui_custom_fg));
1585 
1586 		main_win->redraw();
1587 	}
1588 
1589 	/* General Tab */
1590 
1591 	auto_load_recent  = gen_autoload   ->value() ? true : false;
1592 	begin_maximized   = gen_maximized  ->value() ? true : false;
1593 	swap_sidedefs     = gen_swapsides  ->value() ? true : false;
1594 
1595 	/* Edit Tab */
1596 
1597 	default_port = StringDup(edit_def_port->value());
1598 	default_edit_mode = edit_def_mode->value();
1599 	highlight_line_info = edit_lineinfo->value();
1600 
1601 	new_sector_size = atoi(edit_sectorsize->value());
1602 	new_sector_size = CLAMP(4, new_sector_size, 8192);
1603 
1604 	same_mode_clears_selection = edit_samemode->value() ? true : false;
1605 	sidedef_add_del_buttons = edit_add_del->value() ? true : false;
1606 	show_full_one_sided = edit_full_1S->value() ? true : false;
1607 	leave_offsets_alone = edit_autoadjustX->value() ? false : true;
1608 
1609 	// changing this requires re-populating the browser
1610 	bool new_small_tex = brow_smalltex->value() ? true : false;
1611 	bool new_combo = brow_combo->value() ? true : false;
1612 
1613 	if (new_small_tex != browser_small_tex || new_combo != browser_combine_tex)
1614 	{
1615 		browser_small_tex = new_small_tex;
1616 		browser_combine_tex = new_combo;
1617 
1618 		main_win->browser->Populate();
1619 	}
1620 
1621 	// decode the user ratio
1622 	grid_ratio_low = grid_ratio_high = -1;
1623 	sscanf(edit_userratio->value(), "%d:%d", &grid_ratio_high, &grid_ratio_low);
1624 	if (grid_ratio_high < 1) grid_ratio_high = 3;
1625 	if (grid_ratio_low  < 1) grid_ratio_low  = 1;
1626 
1627 	if (grid_ratio_low > grid_ratio_high)
1628 		std::swap(grid_ratio_low, grid_ratio_high);
1629 
1630 	main_win->info_bar->UpdateRatio();
1631 
1632 	/* Grid Tab */
1633 
1634 	grid_style        = grid_cur_style->value();
1635 	grid_default_mode = grid_enabled->value();
1636 	grid_default_snap = grid_snap->value() ? true : false;
1637 	grid_default_size = atoi(grid_size->mvalue()->text);
1638 	grid_hide_in_free_mode = grid_hide_free ->value() ? true : false;
1639 	grid_snap_indicator    = grid_indicator ->value() ? true : false;
1640 	sector_render_default  = grid_flatrender->value() ? 1 : 0;
1641 	thing_render_default   = grid_spriterend->value() ? 1 : 0;
1642 
1643 	map_scroll_bars = gen_scrollbars ->value() ? true : false;
1644 
1645 	dotty_axis_col  = (rgb_color_t) dotty_axis ->color();
1646 	dotty_major_col = (rgb_color_t) dotty_major->color();
1647 	dotty_minor_col = (rgb_color_t) dotty_minor->color();
1648 	dotty_point_col = (rgb_color_t) dotty_point->color();
1649 
1650 	normal_axis_col  = (rgb_color_t) normal_axis ->color();
1651 	normal_main_col  = (rgb_color_t) normal_main ->color();
1652 	normal_flat_col  = (rgb_color_t) normal_flat ->color();
1653 	normal_small_col = (rgb_color_t) normal_small->color();
1654 
1655 	/* Nodes Tab */
1656 
1657 	bsp_on_save = nod_on_save->value() ? true : false;
1658 	bsp_fast = nod_fast->value() ? true : false;
1659 	bsp_warnings = nod_warn->value() ? true : false;
1660 
1661 	if (nod_factor->value() == 1)			// Minimize Splits
1662 		bsp_split_factor = 29;
1663 	else if (nod_factor->value() == 2)		// Balanced BSP tree
1664 		bsp_split_factor = 2;
1665 	else
1666 		bsp_split_factor = 11;
1667 
1668 	bsp_gl_nodes = nod_gl_nodes->value() ? true : false;
1669 	bsp_force_v5 = nod_force_v5->value() ? true : false;
1670 	bsp_force_zdoom = nod_force_zdoom->value() ? true : false;
1671 	bsp_compressed = nod_compress->value() ? true : false;
1672 
1673 	/* Other Tab */
1674 
1675 	render_pixel_aspect = (int)(100 * atof(rend_aspect->value()) + 0.2);
1676 	render_pixel_aspect = CLAMP(25, render_pixel_aspect, 400);
1677 
1678 	render_high_detail  = rend_high_detail->value() ? true : false;
1679 	render_lock_gravity = rend_lock_grav->value() ? true : false;
1680 	render_far_clip     = atoi(rend_far_clip->mvalue()->text);
1681 }
1682 
1683 
LoadKeys()1684 void UI_Preferences::LoadKeys()
1685 {
1686 	M_SortBindings(key_sort_mode, key_sort_rev);
1687 	M_DetectConflictingBinds();
1688 
1689 	key_list->clear();
1690 
1691 	for (int i = 0 ; i < M_NumBindings() ; i++)
1692 	{
1693 		const char *str = M_StringForBinding(i);
1694 		SYS_ASSERT(str);
1695 
1696 		key_list->add(str);
1697 	}
1698 
1699 	key_list->select(1);
1700 }
1701 
1702 
ReloadKeys()1703 void UI_Preferences::ReloadKeys()
1704 {
1705 	M_DetectConflictingBinds();
1706 
1707 	for (int i = 0 ; i < M_NumBindings() ; i++)
1708 	{
1709 		const char *str = M_StringForBinding(i);
1710 
1711 		key_list->text(i + 1, str);
1712 	}
1713 }
1714 
1715 
EnsureKeyVisible(int line)1716 void UI_Preferences::EnsureKeyVisible(int line)
1717 {
1718 	if (! key_list->displayed(line))
1719 	{
1720 		key_list->middleline(line);
1721 	}
1722 }
1723 
1724 
ClearWaiting()1725 void UI_Preferences::ClearWaiting()
1726 {
1727 	if (awaiting_line > 0)
1728 	{
1729 		// restore the text line
1730 		ReloadKeys();
1731 
1732 		Fl::focus(key_list);
1733 	}
1734 
1735 	awaiting_line = 0;
1736 
1737 	key_list->selection_color(FL_SELECTION_COLOR);
1738 }
1739 
1740 
SetBinding(keycode_t key)1741 void UI_Preferences::SetBinding(keycode_t key)
1742 {
1743 	int bind_idx = awaiting_line - 1;
1744 
1745 	M_ChangeBindingKey(bind_idx, key);
1746 
1747 	ClearWaiting();
1748 }
1749 
1750 
handle(int event)1751 int UI_Preferences::handle(int event)
1752 {
1753 	if (awaiting_line > 0)
1754 	{
1755 		// escape key cancels
1756 		if (event == FL_KEYDOWN && Fl::event_key() == FL_Escape)
1757 		{
1758 			ClearWaiting();
1759 			return 1;
1760 		}
1761 
1762 		if (event == FL_KEYDOWN ||
1763 			event == FL_PUSH    ||
1764 			event == FL_MOUSEWHEEL)
1765 		{
1766 			keycode_t new_key = M_CookedKeyForEvent(event);
1767 
1768 			if (new_key)
1769 			{
1770 				SetBinding(new_key);
1771 				return 1;
1772 			}
1773 		}
1774 	}
1775 
1776 	return Fl_Double_Window::handle(event);
1777 }
1778 
1779 
1780 //------------------------------------------------------------------------
1781 
1782 
CMD_Preferences()1783 void CMD_Preferences()
1784 {
1785 	UI_Preferences * dialog = new UI_Preferences();
1786 
1787 	dialog->Run();
1788 
1789 	delete dialog;
1790 }
1791 
1792 
1793 //--- editor settings ---
1794 // vi:ts=4:sw=4:noexpandtab
1795