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