1 //------------------------------------------------------------------------
2 //  KEY BINDINGS
3 //------------------------------------------------------------------------
4 //
5 //  Eureka DOOM Editor
6 //
7 //  Copyright (C) 2013-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 
27 const char * EXEC_Param[MAX_EXEC_PARAM];
28 const char * EXEC_Flags[MAX_EXEC_PARAM];
29 
30 int EXEC_Errno;
31 
32 keycode_t EXEC_CurKey;
33 
34 
35 // this fake mod represents the "LAX-" prefix and is NOT used
36 // anywhere except in the key_binding_t structure.
37 #define MOD_LAX_SHIFTCTRL	FL_SCROLL_LOCK
38 
39 
40 static std::vector< editor_command_t * > all_commands;
41 
42 
RequiredContextFromName(const char * name)43 static key_context_e RequiredContextFromName(const char *name)
44 {
45 	if (strncmp(name, "LIN_", 4) == 0) return KCTX_Line;
46 	if (strncmp(name, "SEC_", 4) == 0) return KCTX_Sector;
47 	if (strncmp(name, "TH_",  3) == 0) return KCTX_Thing;
48 	if (strncmp(name, "VT_",  3) == 0) return KCTX_Vertex;
49 
50 	// we don't need anything for KCTX_Browser or KCTX_Render
51 
52 	return KCTX_NONE;
53 }
54 
55 
CalcGroupName(const char * given,key_context_e ctx)56 static const char * CalcGroupName(const char *given, key_context_e ctx)
57 {
58 	if (given)
59 		return given;
60 
61 	switch (ctx)
62 	{
63 		case KCTX_Line:    return "Line";
64 		case KCTX_Sector:  return "Sector";
65 		case KCTX_Thing:   return "Thing";
66 		case KCTX_Vertex:  return "Vertex";
67 		case KCTX_Render:  return "3D View";
68 		case KCTX_Browser: return "Browser";
69 
70 		default: return "General";
71 	}
72 }
73 
74 
75 //
76 // this should only be called during startup
77 //
M_RegisterCommandList(editor_command_t * list)78 void M_RegisterCommandList(editor_command_t * list)
79 {
80 	// the structures are used directly
81 
82 	for ( ; list->name ; list++)
83 	{
84 		list->req_context = RequiredContextFromName(list->name);
85 		list->group_name  = CalcGroupName(list->group_name, list->req_context);
86 
87 		all_commands.push_back(list);
88 	}
89 }
90 
91 
FindEditorCommand(const char * name)92 const editor_command_t * FindEditorCommand(const char *name)
93 {
94 	// backwards compatibility
95 	if (y_stricmp(name, "GRID_Step") == 0)
96 		name = "GRID_Bump";
97 	else if (y_stricmp(name, "Check") == 0)
98 		name = "MapCheck";
99 	else if (y_stricmp(name, "3D_Click") == 0)
100 		name = "ACT_Click";
101 	else if (y_stricmp(name, "3D_NAV_MouseMove") == 0)
102 		name = "NAV_MouseScroll";
103 	else if (y_stricmp(name, "OperationMenu") == 0)
104 		name = "OpMenu";
105 
106 	for (unsigned int i = 0 ; i < all_commands.size() ; i++)
107 		if (y_stricmp(all_commands[i]->name, name) == 0)
108 			return all_commands[i];
109 
110 	return NULL;
111 }
112 
113 
LookupEditorCommand(int idx)114 const editor_command_t * LookupEditorCommand(int idx)
115 {
116 	if (idx >= (int)all_commands.size())
117 		return NULL;
118 
119 	return all_commands[idx];
120 }
121 
122 
123 //------------------------------------------------------------------------
124 
125 
126 typedef struct
127 {
128 	keycode_t key;
129 	const char * name;
130 
131 } key_mapping_t;
132 
133 
134 static const key_mapping_t key_map[] =
135 {
136 	{ ' ',			"SPACE" },
137 	{ '"',			"DBLQUOTE" },
138 	{ FL_BackSpace,	"BS" },
139 	{ FL_Tab,		"TAB" },
140 	{ FL_Enter,		"ENTER" },
141 	{ FL_Pause,		"PAUSE" },
142 	{ FL_Escape,	"ESC" },
143 	{ FL_Left,		"LEFT" },
144 	{ FL_Up,		"UP" },
145 	{ FL_Right,		"RIGHT" },
146 	{ FL_Down,		"DOWN" },
147 	{ FL_Page_Up,	"PGUP" },
148 	{ FL_Page_Down,	"PGDN" },
149 	{ FL_Home,		"HOME" },
150 	{ FL_End,		"END" },
151 	{ FL_Print,		"PRINT" },
152 	{ FL_Insert,	"INS" },
153 	{ FL_Delete,	"DEL" },
154 	{ FL_Menu,		"MENU" },
155 	{ FL_Help,		"HELP" },
156 
157 	{ FL_KP_Enter,	"KP_Enter"},
158 
159 	{ FL_Volume_Down,	"VOL_DOWN" },
160 	{ FL_Volume_Mute,	"VOL_MUTE" },
161 	{ FL_Volume_Up,		"VOL_UP" },
162 	{ FL_Media_Play,	"CD_PLAY" },
163 	{ FL_Media_Stop,	"CD_STOP" },
164 	{ FL_Media_Prev,	"CD_PREV" },
165 	{ FL_Media_Next,	"CD_NEXT" },
166 	{ FL_Home_Page,		"HOME_PAGE" },
167 
168 	{ FL_Mail,		"MAIL" },
169 	{ FL_Search,	"SEARCH" },
170 	{ FL_Back,		"BACK" },
171 	{ FL_Forward,	"FORWARD" },
172 	{ FL_Stop,		"STOP" },
173 	{ FL_Refresh,	"REFRESH" },
174 	{ FL_Sleep,		"SLEEP" },
175 	{ FL_Favorites,	"FAVORITES" },
176 
177 	// special stuff (not in FLTK)
178 	{ FL_WheelUp,    "WHEEL_UP" },
179 	{ FL_WheelDown,  "WHEEL_DOWN" },
180 	{ FL_WheelLeft,  "WHEEL_LEFT" },
181 	{ FL_WheelRight, "WHEEL_RIGHT" },
182 
183 	// some synonyms for user input
184 	{ ' ',			"SPC" },
185 	{ FL_BackSpace,	"BACKSPACE" },
186 	{ FL_Enter,		"RETURN" },
187 	{ FL_Escape,	"ESCAPE" },
188 	{ FL_Insert,	"INSERT" },
189 	{ FL_Delete,	"DELETE" },
190 	{ FL_Page_Up,	"PAGEUP" },
191 	{ FL_Page_Down,	"PAGEDOWN" },
192 
193 	{ 0, NULL } // the end
194 };
195 
196 
is_mouse_wheel(keycode_t key)197 bool is_mouse_wheel(keycode_t key)
198 {
199 	key &= FL_KEY_MASK;
200 
201 	return (FL_WheelUp <= key && key <= FL_WheelRight);
202 }
203 
is_mouse_button(keycode_t key)204 bool is_mouse_button(keycode_t key)
205 {
206 	key &= FL_KEY_MASK;
207 
208 	return (FL_Button < key && key <= FL_Button + 8);
209 }
210 
211 
212 //
213 // returns zero (an invalid key) if parsing fails
214 //
M_ParseKeyString(const char * str)215 keycode_t M_ParseKeyString(const char *str)
216 {
217 	int key = 0;
218 
219 	// for MOD_COMMAND, accept both CMD and CTRL prefixes
220 
221 	if (y_strnicmp(str, "CMD-", 4) == 0)
222 	{
223 		key |= MOD_COMMAND;  str += 4;
224 	}
225 	else if (y_strnicmp(str, "CTRL-", 5) == 0)
226 	{
227 		key |= MOD_COMMAND;  str += 5;
228 	}
229 	else if (y_strnicmp(str, "META-", 5) == 0)
230 	{
231 		key |= MOD_META;  str += 5;
232 	}
233 	else if (y_strnicmp(str, "ALT-", 4) == 0)
234 	{
235 		key |= MOD_ALT;  str += 4;
236 	}
237 	else if (y_strnicmp(str, "SHIFT-", 6) == 0)
238 	{
239 		key |= MOD_SHIFT;  str += 6;
240 	}
241 	else if (y_strnicmp(str, "LAX-", 4) == 0)
242 	{
243 		key |= MOD_LAX_SHIFTCTRL;  str += 4;
244 	}
245 
246 	// convert uppercase letter --> lowercase + MOD_SHIFT
247 	if (strlen(str) == 1 && str[0] >= 'A' && str[0] <= 'Z')
248 		return MOD_SHIFT | (unsigned char) tolower(str[0]);
249 
250 	if (strlen(str) == 1 && str[0] > 32 && str[0] < 127 && isprint(str[0]))
251 		return key | (unsigned char) str[0];
252 
253 	if (y_strnicmp(str, "F", 1) == 0 && isdigit(str[1]))
254 		return key | (FL_F + atoi(str + 1));
255 
256 	if (y_strnicmp(str, "MOUSE", 5) == 0 && isdigit(str[5]))
257 		return key | (FL_Button + atoi(str + 5));
258 
259 	// find name in mapping table
260 	for (int k = 0 ; key_map[k].name ; k++)
261 		if (y_stricmp(str, key_map[k].name) == 0)
262 			return key | key_map[k].key;
263 
264 	if (y_strnicmp(str, "KP_", 3) == 0 && 33 < str[3] && (FL_KP + str[3]) <= FL_KP_Last)
265 		return key | (FL_KP + str[3]);
266 
267 	if (str[0] == '0' && str[1] == 'x')
268 		return key | (int)strtol(str, NULL, 0);
269 
270 	return 0;
271 }
272 
273 
BareKeyName(keycode_t key)274 static const char * BareKeyName(keycode_t key)
275 {
276 	static char buffer[200];
277 
278 	if (key < 127 && key > 32 && isprint(key) && key != '"')
279 	{
280 		buffer[0] = (char) key;
281 		buffer[1] = 0;
282 
283 		return buffer;
284 	}
285 
286 	if (FL_F < key && key <= FL_F_Last)
287 	{
288 		snprintf(buffer, sizeof(buffer), "F%d", key - FL_F);
289 		return buffer;
290 	}
291 
292 	if (is_mouse_button(key))
293 	{
294 		snprintf(buffer, sizeof(buffer), "MOUSE%d", key - FL_Button);
295 		return buffer;
296 	}
297 
298 	// find key in mapping table
299 	for (int k = 0 ; key_map[k].name ; k++)
300 		if (key == key_map[k].key)
301 			return key_map[k].name;
302 
303 	if (FL_KP + 33 <= key && key <= FL_KP_Last)
304 	{
305 		snprintf(buffer, sizeof(buffer), "KP_%c", (char)(key & 127));
306 		return buffer;
307 	}
308 
309 	// fallback : hex code
310 
311 	snprintf(buffer, sizeof(buffer), "0x%04x", key);
312 	return buffer;
313 }
314 
315 
ModName_Dash(keycode_t mod)316 static const char *ModName_Dash(keycode_t mod)
317 {
318 #ifdef __APPLE__
319 	if (mod & MOD_COMMAND) return "CMD-";
320 #else
321 	if (mod & MOD_COMMAND) return "CTRL-";
322 #endif
323 	if (mod & MOD_META)    return "META-";
324 	if (mod & MOD_ALT)     return "ALT-";
325 	if (mod & MOD_SHIFT)   return "SHIFT-";
326 
327 	if (mod & MOD_LAX_SHIFTCTRL) return "LAX-";
328 
329 	return "";
330 }
331 
332 
ModName_Space(keycode_t mod)333 static const char *ModName_Space(keycode_t mod)
334 {
335 #ifdef __APPLE__
336 	if (mod & MOD_COMMAND) return "CMD ";
337 #else
338 	if (mod & MOD_COMMAND) return "CTRL ";
339 #endif
340 	if (mod & MOD_META)    return "META ";
341 	if (mod & MOD_ALT)     return "ALT ";
342 	if (mod & MOD_SHIFT)   return "SHIFT ";
343 
344 	if (mod & MOD_LAX_SHIFTCTRL) return "LAX ";
345 
346 	return "";
347 }
348 
349 
M_KeyToString(keycode_t key)350 const char * M_KeyToString(keycode_t key)
351 {
352 	static char buffer[200];
353 
354 	// convert SHIFT + letter --> uppercase letter
355 	if ((key & MOD_ALL_MASK) == MOD_SHIFT &&
356 		(key & FL_KEY_MASK)  <  127 &&
357 		isalpha(key & FL_KEY_MASK))
358 	{
359 		snprintf(buffer, sizeof(buffer), "%c", toupper(key & FL_KEY_MASK));
360 		return buffer;
361 	}
362 
363 	snprintf(buffer, sizeof(buffer), "%s%s", ModName_Dash(key),
364 		BareKeyName(key & FL_KEY_MASK));
365 
366 	return buffer;
367 }
368 
369 
M_KeyCmp(keycode_t A,keycode_t B)370 int M_KeyCmp(keycode_t A, keycode_t B)
371 {
372 	keycode_t A_mod = A & MOD_ALL_MASK;
373 	keycode_t B_mod = B & MOD_ALL_MASK;
374 
375 	A &= FL_KEY_MASK;
376 	B &= FL_KEY_MASK;
377 
378 	// make mouse buttons separate from everything else
379 
380 	if (is_mouse_button(A) || is_mouse_wheel(A))
381 		A += 0x10000;
382 
383 	if (is_mouse_button(B) || is_mouse_wheel(B))
384 		B += 0x10000;
385 
386 	// base key is most important
387 
388 	if (A != B)
389 		return (int)A - (int)B;
390 
391 	// modifiers are the least important
392 
393 	return (int)A_mod - (int)B_mod;
394 }
395 
396 
397 //------------------------------------------------------------------------
398 
399 
M_ParseKeyContext(const char * str)400 key_context_e M_ParseKeyContext(const char *str)
401 {
402 	if (y_stricmp(str, "browser") == 0) return KCTX_Browser;
403 	if (y_stricmp(str, "render")  == 0) return KCTX_Render;
404 	if (y_stricmp(str, "general") == 0) return KCTX_General;
405 
406 	if (y_stricmp(str, "line")    == 0) return KCTX_Line;
407 	if (y_stricmp(str, "sector")  == 0) return KCTX_Sector;
408 	if (y_stricmp(str, "thing")   == 0) return KCTX_Thing;
409 	if (y_stricmp(str, "vertex")  == 0) return KCTX_Vertex;
410 
411 	return KCTX_NONE;
412 }
413 
M_KeyContextString(key_context_e context)414 const char * M_KeyContextString(key_context_e context)
415 {
416 	switch (context)
417 	{
418 		case KCTX_Browser: return "browser";
419 		case KCTX_Render:  return "render";
420 		case KCTX_General: return "general";
421 
422 		case KCTX_Line:    return "line";
423 		case KCTX_Sector:  return "sector";
424 		case KCTX_Thing:   return "thing";
425 		case KCTX_Vertex:  return "vertex";
426 
427 		default: break;
428 	}
429 
430 	return "INVALID";
431 }
432 
433 
434 //------------------------------------------------------------------------
435 
436 typedef struct
437 {
438 	keycode_t key;
439 
440 	key_context_e context;
441 
442 	const editor_command_t *cmd;
443 
444 	char param[MAX_EXEC_PARAM][MAX_BIND_LENGTH];
445 
446 	// this field ONLY used by M_DetectConflictingBinds()
447 	bool is_duplicate;
448 
449 } key_binding_t;
450 
451 
452 static std::vector<key_binding_t> all_bindings;
453 static std::vector<key_binding_t> install_binds;
454 
455 
M_IsKeyBound(keycode_t key,key_context_e context)456 bool M_IsKeyBound(keycode_t key, key_context_e context)
457 {
458 	for (unsigned int i = 0 ; i < all_bindings.size() ; i++)
459 	{
460 		key_binding_t& bind = all_bindings[i];
461 
462 		if (bind.key == key && bind.context == context)
463 			return true;
464 	}
465 
466 	return false;
467 }
468 
469 
M_RemoveBinding(keycode_t key,key_context_e context)470 void M_RemoveBinding(keycode_t key, key_context_e context)
471 {
472 	std::vector<key_binding_t>::iterator IT;
473 
474 	for (IT = all_bindings.begin() ; IT != all_bindings.end() ; IT++)
475 	{
476 		if (IT->key == key && IT->context == context)
477 		{
478 			// found it
479 			all_bindings.erase(IT);
480 
481 			// there should never be more than one
482 			// (besides, our iterator is now invalid)
483 			return;
484 		}
485 	}
486 }
487 
488 
ParseKeyBinding(const char ** tokens,int num_tok)489 static void ParseKeyBinding(const char ** tokens, int num_tok)
490 {
491 	key_binding_t temp;
492 
493 	// this ensures all parameters are NUL terminated
494 	memset(&temp, 0, sizeof(temp));
495 
496 	temp.key = M_ParseKeyString(tokens[1]);
497 
498 	if (! temp.key)
499 	{
500 		LogPrintf("bindings.cfg: cannot parse key name: %s\n", tokens[1]);
501 		return;
502 	}
503 
504 	temp.context = M_ParseKeyContext(tokens[0]);
505 
506 	if (temp.context == KCTX_NONE)
507 	{
508 		LogPrintf("bindings.cfg: unknown context: %s\n", tokens[0]);
509 		return;
510 	}
511 
512 
513 	// handle un-bound keys
514 	if (y_stricmp(tokens[2], "UNBOUND") == 0)
515 	{
516 #if 0
517 fprintf(stderr, "REMOVED BINDING key:%04x (%s)\n", temp.key, tokens[0]);
518 #endif
519 		M_RemoveBinding(temp.key, temp.context);
520 		return;
521 	}
522 
523 	temp.cmd = FindEditorCommand(tokens[2]);
524 
525 	if (! temp.cmd)
526 	{
527 		LogPrintf("bindings.cfg: unknown function: %s\n", tokens[2]);
528 		return;
529 	}
530 
531 	if (temp.cmd->req_context != KCTX_NONE &&
532 	    temp.context != temp.cmd->req_context)
533 	{
534 		LogPrintf("bindings.cfg: function '%s' in wrong context '%s'\n",
535 				  tokens[2], tokens[0]);
536 		return;
537 	}
538 
539 	for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
540 		if (num_tok >= 4 + p)
541 			strncpy(temp.param[p], tokens[3 + p], MAX_BIND_LENGTH-1);
542 
543 #if 0  // DEBUG
544 fprintf(stderr, "ADDED BINDING key:%04x --> %s\n", temp.key, tokens[2]);
545 #endif
546 
547 	M_RemoveBinding(temp.key, temp.context);
548 
549 	all_bindings.push_back(temp);
550 }
551 
552 
553 #define MAX_TOKENS  (MAX_EXEC_PARAM + 8)
554 
LoadBindingsFromPath(const char * path,bool required)555 static bool LoadBindingsFromPath(const char *path, bool required)
556 {
557 	static char filename[FL_PATH_MAX];
558 
559 	snprintf(filename, sizeof(filename), "%s/bindings.cfg", path);
560 
561 	FILE *fp = fopen(filename, "r");
562 
563 	if (! fp)
564 	{
565 		if (! required)
566 			return false;
567 
568 		FatalError("Missing key bindings file:\n\n%s\n", filename);
569 	}
570 
571 	LogPrintf("Reading key bindings from: %s\n", filename);
572 
573 	static char line_buf[FL_PATH_MAX];
574 
575 	const char * tokens[MAX_TOKENS];
576 
577 	while (! feof(fp))
578 	{
579 		char *line = fgets(line_buf, FL_PATH_MAX, fp);
580 
581 		if (! line)
582 			break;
583 
584 		StringRemoveCRLF(line);
585 
586 		int num_tok = M_ParseLine(line, tokens, MAX_TOKENS, 2 /* do_strings, keep quotes */);
587 
588 		if (num_tok == 0)
589 			continue;
590 
591 		if (num_tok < 3)
592 		{
593 			LogPrintf("Syntax error in bindings: %s\n", line);
594 			continue;
595 		}
596 
597 		ParseKeyBinding(tokens, num_tok);
598 	}
599 
600 	fclose(fp);
601 
602 	return true;
603 }
604 
605 
CopyInstallBindings()606 static void CopyInstallBindings()
607 {
608 	install_binds.clear();
609 
610 	for (unsigned int i = 0 ; i < all_bindings.size() ; i++)
611 	{
612 		install_binds.push_back(all_bindings[i]);
613 	}
614 }
615 
616 
BindingExists(std::vector<key_binding_t> & list,key_binding_t & bind,bool full_match)617 static bool BindingExists(std::vector<key_binding_t>& list, key_binding_t& bind,
618                           bool full_match)
619 {
620 	for (unsigned int i = 0 ; i < list.size() ; i++)
621 	{
622 		key_binding_t& other = list[i];
623 
624 		if (bind.key != other.key)
625 			continue;
626 
627 		if (bind.context != other.context)
628 			continue;
629 
630 		if (! full_match)
631 			return true;
632 
633 		if (bind.cmd != other.cmd)
634 			continue;
635 
636 		bool same_params = true;
637 
638 		for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
639 		{
640 			if (strcmp(bind.param[p], other.param[p]) != 0)
641 			{
642 				same_params = false;
643 				break;
644 			}
645 		}
646 
647 		if (same_params)
648 			return true;
649 	}
650 
651 	return false;
652 }
653 
654 
M_LoadBindings()655 void M_LoadBindings()
656 {
657 	all_bindings.clear();
658 
659 	LoadBindingsFromPath(install_dir, true /* required */);
660 
661 	// keep a copy of the install_dir bindings
662 	CopyInstallBindings();
663 
664 	LoadBindingsFromPath(home_dir, false);
665 }
666 
667 
M_SaveBindings()668 void M_SaveBindings()
669 {
670 	static char filename[FL_PATH_MAX];
671 
672 	snprintf(filename, sizeof(filename), "%s/bindings.cfg", home_dir);
673 
674 	FILE *fp = fopen(filename, "w");
675 
676 	if (! fp)
677 	{
678 		LogPrintf("Failed to save key bindings to: %s\n", filename);
679 
680 		DLG_Notify("Warning: failed to save key bindings\n"
681 		           "(filename: %s)", filename);
682 		return;
683 	}
684 
685 	LogPrintf("Writing key bindings to: %s\n", filename);
686 
687 	fprintf(fp, "# Eureka key bindings (local)\n");
688 	fprintf(fp, "# vi:ts=16:noexpandtab\n\n");
689 
690 	for (int ctx = KCTX_Browser ; ctx <= KCTX_General ; ctx++)
691 	{
692 		int count = 0;
693 
694 		for (unsigned int i = 0 ; i < all_bindings.size() ; i++)
695 		{
696 			key_binding_t& bind = all_bindings[i];
697 
698 			if (bind.context != (key_context_e)ctx)
699 				continue;
700 
701 			// no need to write it if unchanged from install_dir
702 			if (BindingExists(install_binds, bind, true /* full match */))
703 				continue;
704 
705 			fprintf(fp, "%s\t%s\t%s", M_KeyContextString(bind.context),
706 					M_KeyToString(bind.key), bind.cmd->name);
707 
708 			for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
709 			{
710 				if (bind.param[p][0])
711 					fprintf(fp, "\t%s", bind.param[p]);
712 			}
713 
714 			fprintf(fp, "\n");
715 			count++;
716 		}
717 
718 		// find un-bound keys (relative to installation)
719 
720 		for (unsigned int i = 0 ; i < install_binds.size() ; i++)
721 		{
722 			key_binding_t& bind = install_binds[i];
723 
724 			if (bind.context != ctx)
725 				continue;
726 
727 			if (! BindingExists(all_bindings, bind, false /* full match */))
728 			{
729 				fprintf(fp, "%s\t%s\t%s\n", M_KeyContextString(bind.context),
730 						M_KeyToString(bind.key), "UNBOUND");
731 				count++;
732 			}
733 		}
734 
735 		if (count > 0)
736 			fprintf(fp, "\n");
737 	}
738 
739 	fclose(fp);
740 }
741 
742 
743 //------------------------------------------------------------------------
744 //  PREFERENCE DIALOG STUFF
745 //------------------------------------------------------------------------
746 
747 // local copy of the bindings
748 // these only become live after M_ApplyBindings()
749 static std::vector<key_binding_t> pref_binds;
750 
751 
M_CopyBindings(bool from_defaults)752 void M_CopyBindings(bool from_defaults)
753 {
754 	// returns # of bindings
755 
756 	pref_binds.clear();
757 
758 	if (from_defaults)
759 	{
760 		for (unsigned int i = 0 ; i < install_binds.size() ; i++)
761 			pref_binds.push_back(install_binds[i]);
762 	}
763 	else
764 	{
765 		for (unsigned int i = 0 ; i < all_bindings.size() ; i++)
766 			pref_binds.push_back(all_bindings[i]);
767 	}
768 }
769 
770 
M_ApplyBindings()771 void M_ApplyBindings()
772 {
773 	all_bindings.clear();
774 
775 	for (unsigned int i = 0 ; i < pref_binds.size() ; i++)
776 		all_bindings.push_back(pref_binds[i]);
777 }
778 
779 
M_NumBindings()780 int M_NumBindings()
781 {
782 	return (int)pref_binds.size();
783 }
784 
785 
786 struct KeyBind_CMP_pred
787 {
788 private:
789 	char column;
790 
791 public:
KeyBind_CMP_predKeyBind_CMP_pred792 	KeyBind_CMP_pred(char _col) : column(_col)
793 	{ }
794 
operator ()KeyBind_CMP_pred795 	inline bool operator() (const key_binding_t& k1, const key_binding_t& k2) const
796 	{
797 		if (column == 'c' && k1.context != k2.context)
798 			return k1.context > k2.context;
799 
800 		if (column != 'f' && k1.key != k2.key)
801 			return M_KeyCmp(k1.key, k2.key) < 0;
802 
803 ///		if (column == 'k' && k1.context != k2.context)
804 ///			return k1.context > k2.context;
805 
806 		if (k1.cmd != k2.cmd)
807 			return y_stricmp(k1.cmd->name, k2.cmd->name) < 0;
808 
809 		for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
810 		{
811 			int cmp = y_stricmp(k1.param[p], k2.param[p]);
812 
813 			if (cmp != 0)
814 				return cmp < 0;
815 		}
816 
817 		if (column == 'f' && k1.key != k2.key)
818 			return M_KeyCmp(k1.key, k2.key) < 0;
819 
820 		return k1.context < k2.context;
821 	}
822 };
823 
824 
M_SortBindings(char column,bool reverse)825 void M_SortBindings(char column, bool reverse)
826 {
827 	std::sort(pref_binds.begin(), pref_binds.end(),
828 	          KeyBind_CMP_pred(column));
829 
830 	if (reverse)
831 		std::reverse(pref_binds.begin(), pref_binds.end());
832 }
833 
834 
M_DetectConflictingBinds()835 void M_DetectConflictingBinds()
836 {
837 	// copy the local bindings and sort them.
838 	// duplicate bindings will be contiguous in the list.
839 
840 	std::vector<key_binding_t> list;
841 
842 	for (unsigned int i = 0 ; i < pref_binds.size() ; i++)
843 	{
844 		pref_binds[i].is_duplicate = false;
845 
846 		list.push_back(pref_binds[i]);
847 	}
848 
849 	std::sort(list.begin(), list.end(), KeyBind_CMP_pred('c'));
850 
851 	for (unsigned int k = 0 ; k + 1 < list.size() ; k++)
852 	{
853 		if (! (list[k].key     == list[k+1].key &&
854 			   list[k].context == list[k+1].context))
855 			continue;
856 
857 		// mark these in the local bindings
858 
859 		for (unsigned int n = 0 ; n < pref_binds.size() ; n++)
860 		{
861 			if (pref_binds[n].key     == list[k].key &&
862 			    pref_binds[n].context == list[k].context)
863 			{
864 				pref_binds[n].is_duplicate = true;
865 			}
866 		}
867 	}
868 }
869 
870 
M_StringForFunc(int index)871 const char * M_StringForFunc(int index)
872 {
873 	static char buffer[2048];
874 
875 	key_binding_t& bind = pref_binds[index];
876 
877 	strcpy(buffer, bind.cmd->name);
878 
879 	// add the parameters
880 
881 	char *pos = buffer;
882 
883 	for (int k = 0 ; k < MAX_EXEC_PARAM ; k++)
884 	{
885 		const char *param = bind.param[k];
886 
887 		if (! param[0])
888 			break;
889 
890 		pos = buffer + strlen(buffer);
891 
892 		if (k == 0)
893 			*pos++ = ':';
894 
895 		*pos++ = ' ';
896 
897 		sprintf(pos, "%.30s", param);
898 	}
899 
900 	return buffer;
901 }
902 
903 
M_StringForBinding(int index,bool changing_key)904 const char * M_StringForBinding(int index, bool changing_key)
905 {
906 	SYS_ASSERT(index < (int)pref_binds.size());
907 
908 	key_binding_t& bind = pref_binds[index];
909 
910 	static char buffer[600];
911 
912 	// we prefer the UI to say "3D view" instead of "render"
913 	const char *ctx_name = M_KeyContextString(bind.context);
914 	if (y_stricmp(ctx_name, "render") == 0)
915 		ctx_name = "3D view";
916 
917 	// display SHIFT + letter as an uppercase letter
918 	keycode_t tempk = bind.key;
919 	if ((tempk & MOD_ALL_MASK) == MOD_SHIFT &&
920 		(tempk & FL_KEY_MASK)  <  127 &&
921 		isalpha(tempk & FL_KEY_MASK))
922 	{
923 		tempk = toupper(tempk & FL_KEY_MASK);
924 	}
925 
926 	snprintf(buffer, sizeof(buffer), "%s%6.6s%-10.10s %-9.9s %.32s",
927 			bind.is_duplicate ? "@C1" : "",
928 			changing_key ? "<?"     : ModName_Space(tempk),
929 			changing_key ? "\077?>" : BareKeyName(tempk & FL_KEY_MASK),
930 			ctx_name,
931 			M_StringForFunc(index) );
932 
933 	return buffer;
934 }
935 
936 
M_GetBindingInfo(int index,keycode_t * key,key_context_e * context)937 void M_GetBindingInfo(int index, keycode_t *key, key_context_e *context)
938 {
939 	// hmmm... exposing key_binding_t may have been easier...
940 
941 	*key     = pref_binds[index].key;
942 	*context = pref_binds[index].context;
943 }
944 
945 
M_ChangeBindingKey(int index,keycode_t key)946 void M_ChangeBindingKey(int index, keycode_t key)
947 {
948 	SYS_ASSERT(0 <= index && index < (int)pref_binds.size());
949 	SYS_ASSERT(key != 0);
950 
951 	pref_binds[index].key = key;
952 }
953 
954 
DoParseBindingFunc(key_binding_t & bind,const char * func_str)955 static const char * DoParseBindingFunc(key_binding_t& bind, const char * func_str)
956 {
957 	static char error_msg[1024];
958 
959 	// convert the brackets and commas into spaces and use the
960 	// line tokeniser.
961 
962 	static char buffer[600];
963 	strncpy(buffer, func_str, sizeof(buffer));
964 	buffer[sizeof(buffer) - 1] = 0;
965 
966 	for (unsigned int k = 0 ; buffer[k] ; k++)
967 		if (buffer[k] == ',' || buffer[k] == ':')
968 			buffer[k] = ' ';
969 
970 	const char * tokens[MAX_TOKENS];
971 
972 	int num_tok = M_ParseLine(buffer, tokens, MAX_TOKENS, 2 /* do_strings, keep quotes */);
973 
974 	if (num_tok <= 0)
975 		return "Missing function name";
976 
977 	const editor_command_t * cmd = FindEditorCommand(tokens[0]);
978 
979 	if (! cmd)
980 	{
981 		snprintf(error_msg, sizeof(error_msg), "Unknown function name: %s", tokens[0]);
982 		return error_msg;
983 	}
984 
985 	// check context is suitable
986 
987 	if (cmd->req_context != KCTX_NONE &&
988 	    bind.context != cmd->req_context)
989 	{
990 		char *mode = StringUpper(M_KeyContextString(cmd->req_context));
991 
992 		snprintf(error_msg, sizeof(error_msg), "%s can only be used in %s mode",
993 		        tokens[0], mode);
994 
995 		StringFree(mode);
996 
997 		return error_msg;
998 	}
999 
1000 	/* OK : change the binding function */
1001 
1002 	bind.cmd = cmd;
1003 
1004 	memset(bind.param, 0, sizeof(bind.param));
1005 
1006 	for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
1007 		if (num_tok >= 2 + p)
1008 			strncpy(bind.param[p], tokens[1 + p], MAX_BIND_LENGTH-1);
1009 
1010 	return NULL;
1011 }
1012 
1013 
M_IsBindingFuncValid(key_context_e context,const char * func_str)1014 bool M_IsBindingFuncValid(key_context_e context, const char * func_str)
1015 {
1016 	key_binding_t temp;
1017 
1018 	temp.key = 'a';  // dummy key
1019 	temp.context = context;
1020 
1021 	return (DoParseBindingFunc(temp, func_str) == NULL);
1022 }
1023 
1024 
1025 // returns an error message, or NULL if OK
M_SetLocalBinding(int index,keycode_t key,key_context_e context,const char * func_str)1026 const char * M_SetLocalBinding(int index, keycode_t key, key_context_e context,
1027                        const char *func_str)
1028 {
1029 	SYS_ASSERT(0 <= index && index < (int)pref_binds.size());
1030 
1031 	pref_binds[index].key = key;
1032 	pref_binds[index].context = context;
1033 
1034 	const char *err_msg = DoParseBindingFunc(pref_binds[index], func_str);
1035 
1036 	return err_msg;
1037 }
1038 
1039 
M_AddLocalBinding(int after,keycode_t key,key_context_e context,const char * func_str)1040 const char * M_AddLocalBinding(int after, keycode_t key, key_context_e context,
1041                        const char *func_str)
1042 {
1043 	key_binding_t temp;
1044 
1045 	// this ensures the parameters are NUL terminated
1046 	memset(&temp, 0, sizeof(temp));
1047 
1048 	temp.key = key;
1049 	temp.context = context;
1050 
1051 	const char *err_msg = DoParseBindingFunc(temp, func_str);
1052 
1053 	if (err_msg)
1054 		return err_msg;
1055 
1056 	if (after < 0)
1057 		pref_binds.push_back(temp);
1058 	else
1059 		pref_binds.insert(pref_binds.begin() + (1 + after), temp);
1060 
1061 	return NULL;  // OK
1062 }
1063 
1064 
M_DeleteLocalBinding(int index)1065 void M_DeleteLocalBinding(int index)
1066 {
1067 	SYS_ASSERT(0 <= index && index < (int)pref_binds.size());
1068 
1069 	pref_binds.erase(pref_binds.begin() + index);
1070 }
1071 
1072 
1073 //------------------------------------------------------------------------
1074 //  COMMAND EXECUTION STUFF
1075 //------------------------------------------------------------------------
1076 
1077 
M_TranslateKey(int key,int state)1078 keycode_t M_TranslateKey(int key, int state)
1079 {
1080 	// ignore modifier keys themselves
1081 	switch (key)
1082 	{
1083 		case FL_Num_Lock:
1084 		case FL_Caps_Lock:
1085 
1086 		case FL_Shift_L: case FL_Control_L:
1087 		case FL_Shift_R: case FL_Control_R:
1088 		case FL_Meta_L:  case FL_Alt_L:
1089 		case FL_Meta_R:  case FL_Alt_R:
1090 			return 0;
1091 	}
1092 
1093 	if (key == '\t') key = FL_Tab;
1094 	if (key == '\b') key = FL_BackSpace;
1095 
1096 	// modifier logic -- only allow a single one
1097 
1098 	     if (state & MOD_COMMAND) key |= MOD_COMMAND;
1099 	else if (state & MOD_META)    key |= MOD_META;
1100 	else if (state & MOD_ALT)     key |= MOD_ALT;
1101 	else if (state & MOD_SHIFT)   key |= MOD_SHIFT;
1102 
1103 	return key;
1104 }
1105 
1106 
M_KeyToShortcut(keycode_t key)1107 int M_KeyToShortcut(keycode_t key)
1108 {
1109 	int shortcut = key & FL_KEY_MASK;
1110 
1111 	     if (key & MOD_COMMAND) shortcut |= MOD_COMMAND;
1112 	else if (key & MOD_ALT)     shortcut |= MOD_ALT;
1113 	else if (key & MOD_SHIFT)   shortcut |= MOD_SHIFT;
1114 
1115 	return shortcut;
1116 }
1117 
1118 
M_ModeToKeyContext(obj_type_e mode)1119 key_context_e M_ModeToKeyContext(obj_type_e mode)
1120 {
1121 	switch (mode)
1122 	{
1123 		case OBJ_THINGS:   return KCTX_Thing;
1124 		case OBJ_LINEDEFS: return KCTX_Line;
1125 		case OBJ_SECTORS:  return KCTX_Sector;
1126 		case OBJ_VERTICES: return KCTX_Vertex;
1127 
1128 		default: break;
1129 	}
1130 
1131 	return KCTX_NONE;  /* shouldn't happen */
1132 }
1133 
1134 
Exec_HasFlag(const char * flag)1135 bool Exec_HasFlag(const char *flag)
1136 {
1137 	// the parameter should include a leading '/'
1138 
1139 	for (int i = 0 ; i < MAX_EXEC_PARAM ; i++)
1140 	{
1141 		if (! EXEC_Flags[i][0])
1142 			break;
1143 
1144 		if (y_stricmp(EXEC_Flags[i], flag) == 0)
1145 			return true;
1146 	}
1147 
1148 	return false;
1149 }
1150 
1151 
1152 extern void Debug_CheckUnusedStuff();
1153 
1154 
DoExecuteCommand(const editor_command_t * cmd)1155 static void DoExecuteCommand(const editor_command_t *cmd)
1156 {
1157 	(* cmd->func)();
1158 
1159 //	Debug_CheckUnusedStuff();
1160 }
1161 
1162 
FindBinding(keycode_t key,key_context_e context,bool lax_only)1163 static int FindBinding(keycode_t key, key_context_e context, bool lax_only)
1164 {
1165 	for (int i = 0 ; i < (int)all_bindings.size() ; i++)
1166 	{
1167 		key_binding_t& bind = all_bindings[i];
1168 
1169 		SYS_ASSERT(bind.cmd);
1170 
1171 		if (bind.context != context)
1172 			continue;
1173 
1174 		// match modifiers "loosely" for certain commands (esp. NAV_xxx)
1175 		bool is_lax = (bind.key & MOD_LAX_SHIFTCTRL) ? true : false;
1176 
1177 		if (lax_only != is_lax)
1178 			continue;
1179 
1180 		keycode_t key1 = key;
1181 		keycode_t key2 = bind.key;
1182 
1183 		if (is_lax)
1184 		{
1185 			key1 &= ~ (MOD_SHIFT | MOD_COMMAND | MOD_LAX_SHIFTCTRL);
1186 			key2 &= ~ (MOD_SHIFT | MOD_COMMAND | MOD_LAX_SHIFTCTRL);
1187 		}
1188 
1189 		if (key1 != key2)
1190 			continue;
1191 
1192 		// found a match
1193 		return i;
1194 	}
1195 
1196 	// not found
1197 	return -1;
1198 }
1199 
1200 
ExecuteKey(keycode_t key,key_context_e context)1201 bool ExecuteKey(keycode_t key, key_context_e context)
1202 {
1203 	for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
1204 	{
1205 		EXEC_Param[p] = "";
1206 		EXEC_Flags[p] = "";
1207 	}
1208 
1209 	EXEC_Errno = 0;
1210 
1211 	// this logic means we only use a LAX binding if an exact match
1212 	// could not be found.
1213 	int idx = FindBinding(key, context, false);
1214 
1215 	if (idx < 0)
1216 		idx = FindBinding(key, context, true);
1217 
1218 	if (idx < 0)
1219 		return false;
1220 
1221 	key_binding_t& bind = all_bindings[idx];
1222 
1223 	int p_idx = 0;
1224 	int f_idx = 0;
1225 
1226 	for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
1227 	{
1228 		if (! bind.param[p][0])
1229 			break;
1230 
1231 		// separate flags from normal parameters
1232 		if (bind.param[p][0] == '/')
1233 			EXEC_Flags[f_idx++] = bind.param[p];
1234 		else
1235 			EXEC_Param[p_idx++] = bind.param[p];
1236 	}
1237 
1238 	EXEC_CurKey = key;
1239 
1240 	// commands can test for LAX mode via a special flag
1241 	bool is_lax = (bind.key & MOD_LAX_SHIFTCTRL) ? true : false;
1242 
1243 	if (is_lax && f_idx < MAX_EXEC_PARAM)
1244 	{
1245 		EXEC_Flags[f_idx++] = "/LAX";
1246 	}
1247 
1248 	DoExecuteCommand(bind.cmd);
1249 
1250 	return true;
1251 }
1252 
1253 
ExecuteCommand(const editor_command_t * cmd,const char * param1,const char * param2,const char * param3,const char * param4)1254 bool ExecuteCommand(const editor_command_t *cmd,
1255 					const char *param1, const char *param2,
1256                     const char *param3, const char *param4)
1257 {
1258 	for (int p = 0 ; p < MAX_EXEC_PARAM ; p++)
1259 	{
1260 		EXEC_Param[p] = "";
1261 		EXEC_Flags[p] = "";
1262 	}
1263 
1264 	// separate flags from normal parameters
1265 	int p_idx = 0;
1266 	int f_idx = 0;
1267 
1268 	if (param1[0] == '/') EXEC_Flags[f_idx++] = param1;
1269 	else if (param1[0])   EXEC_Param[p_idx++] = param1;
1270 
1271 	if (param2[0] == '/') EXEC_Flags[f_idx++] = param2;
1272 	else if (param2[0])   EXEC_Param[p_idx++] = param2;
1273 
1274 	if (param3[0] == '/') EXEC_Flags[f_idx++] = param3;
1275 	else if (param3[0])   EXEC_Param[p_idx++] = param3;
1276 
1277 	if (param4[0] == '/') EXEC_Flags[f_idx++] = param4;
1278 	else if (param4[0])   EXEC_Param[p_idx++] = param4;
1279 
1280 	EXEC_Errno  = 0;
1281 	EXEC_CurKey = 0;
1282 
1283 	DoExecuteCommand(cmd);
1284 
1285 	return true;
1286 }
1287 
1288 
ExecuteCommand(const char * name,const char * param1,const char * param2,const char * param3,const char * param4)1289 bool ExecuteCommand(const char *name,
1290 					const char *param1, const char *param2,
1291 					const char *param3, const char *param4)
1292 {
1293 	const editor_command_t * cmd = FindEditorCommand(name);
1294 
1295 	if (! cmd)
1296 		return false;
1297 
1298 	return ExecuteCommand(cmd, param1, param2, param3, param4);
1299 }
1300 
1301 
1302 //
1303 //  play a fascinating tune
1304 //
Beep(const char * fmt,...)1305 void Beep(const char *fmt, ...)
1306 {
1307 	va_list arg_ptr;
1308 
1309 	static char buffer[MSG_BUF_LEN];
1310 
1311 	va_start(arg_ptr, fmt);
1312 	vsnprintf(buffer, MSG_BUF_LEN-1, fmt, arg_ptr);
1313 	va_end(arg_ptr);
1314 
1315 	buffer[MSG_BUF_LEN-1] = 0;
1316 
1317 	Status_Set("%s", buffer);
1318 	LogPrintf("BEEP: %s\n", buffer);
1319 
1320 	fl_beep();
1321 
1322 	EXEC_Errno = 1;
1323 }
1324 
1325 //--- editor settings ---
1326 // vi:ts=4:sw=4:noexpandtab
1327