1 /***************************************************************************
2 * Copyright (C) 2008-2021 by Andrzej Rybczak *
3 * andrzej@rybczak.net *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include <boost/algorithm/string/trim.hpp>
22 #include <fstream>
23 #include <iostream>
24 #include "global.h"
25 #include "bindings.h"
26 #include "utility/string.h"
27 #include "utility/wide_string.h"
28
29 BindingsConfiguration Bindings;
30
31 namespace {
32
warning(const char * msg)33 void warning(const char *msg)
34 {
35 std::cerr << "WARNING: " << msg << "\n";
36 }
37
38 NC::Key::Type stringToKey(const std::string &s);
39
stringToSpecialKey(const std::string & s)40 NC::Key::Type stringToSpecialKey(const std::string &s)
41 {
42 NC::Key::Type result = NC::Key::None;
43 if (!s.compare(0, 4, "ctrl") && s.length() == 6 && s[4] == '-')
44 {
45 if (s[5] >= 'a' && s[5] <= 'z')
46 result = NC::Key::Ctrl_A + (s[5] - 'a');
47 else if (s[5] == '[')
48 result = NC::Key::Ctrl_LeftBracket;
49 else if (s[5] == '\\')
50 result = NC::Key::Ctrl_Backslash;
51 else if (s[5] == ']')
52 result = NC::Key::Ctrl_RightBracket;
53 else if (s[5] == '^')
54 result = NC::Key::Ctrl_Caret;
55 else if (s[5] == '_')
56 result = NC::Key::Ctrl_Underscore;
57 }
58 else if (!s.compare(0, 3, "alt") && s.length() > 3 && s[3] == '-')
59 {
60 result = NC::Key::Alt | stringToKey(s.substr(4));
61 }
62 else if (!s.compare(0, 4, "ctrl") && s.length() > 4 && s[4] == '-')
63 {
64 result = NC::Key::Ctrl | stringToKey(s.substr(5));
65 }
66 else if (!s.compare(0, 5, "shift") && s.length() > 5 && s[5] == '-')
67 {
68 result = NC::Key::Shift | stringToKey(s.substr(6));
69 }
70 else if (!s.compare("escape"))
71 result = NC::Key::Escape;
72 else if (!s.compare("mouse"))
73 result = NC::Key::Mouse;
74 else if (!s.compare("up"))
75 result = NC::Key::Up;
76 else if (!s.compare("down"))
77 result = NC::Key::Down;
78 else if (!s.compare("page_up"))
79 result = NC::Key::PageUp;
80 else if (!s.compare("page_down"))
81 result = NC::Key::PageDown;
82 else if (!s.compare("home"))
83 result = NC::Key::Home;
84 else if (!s.compare("end"))
85 result = NC::Key::End;
86 else if (!s.compare("space"))
87 result = NC::Key::Space;
88 else if (!s.compare("enter"))
89 result = NC::Key::Enter;
90 else if (!s.compare("insert"))
91 result = NC::Key::Insert;
92 else if (!s.compare("delete"))
93 result = NC::Key::Delete;
94 else if (!s.compare("left"))
95 result = NC::Key::Left;
96 else if (!s.compare("right"))
97 result = NC::Key::Right;
98 else if (!s.compare("tab"))
99 result = NC::Key::Tab;
100 else if ((s.length() == 2 || s.length() == 3) && s[0] == 'f')
101 {
102 int n = atoi(s.c_str() + 1);
103 if (n >= 1 && n <= 12)
104 result = NC::Key::F1 + n - 1;
105 }
106 else if (!s.compare("backspace"))
107 result = NC::Key::Backspace;
108 return result;
109 }
110
stringToKey(const std::string & s)111 NC::Key::Type stringToKey(const std::string &s)
112 {
113 NC::Key::Type result = stringToSpecialKey(s);
114 if (result == NC::Key::None)
115 {
116 std::wstring ws = ToWString(s);
117 if (ws.length() == 1)
118 result = ws[0];
119 }
120 return result;
121 }
122
123 template <typename F>
parseActionLine(const std::string & line,F error)124 std::shared_ptr<Actions::BaseAction> parseActionLine(const std::string &line, F error)
125 {
126 std::shared_ptr<Actions::BaseAction> result;
127 size_t i = 0;
128 for (; i < line.size() && !isspace(line[i]); ++i) { }
129 if (i == line.size()) // only action name
130 {
131 if (line == "set_visualizer_sample_multiplier")
132 {
133 warning("action 'set_visualizer_sample_multiplier' is deprecated"
134 " and will be removed in 0.9");
135 result = Actions::get_(Actions::Type::Dummy);
136 }
137 else
138 result = Actions::get_(line);
139 }
140 else // there is something else
141 {
142 std::string action_name = line.substr(0, i);
143 if (action_name == "push_character")
144 {
145 // push single character into input queue
146 std::string arg = getEnclosedString(line, '"', '"', 0);
147 NC::Key::Type k = stringToSpecialKey(arg);
148 if (k != NC::Key::None)
149 result = std::static_pointer_cast<Actions::BaseAction>(
150 std::make_shared<Actions::PushCharacters>(
151 &Global::wFooter,
152 std::vector<NC::Key::Type>{k}));
153 else
154 error() << "invalid character passed to push_character: '" << arg << "'\n";
155 }
156 else if (action_name == "push_characters")
157 {
158 // push sequence of characters into input queue
159 std::string arg = getEnclosedString(line, '"', '"', 0);
160 if (!arg.empty())
161 {
162 // if char is signed, erase 1s from char -> int conversion
163 for (auto it = arg.begin(); it != arg.end(); ++it)
164 *it &= 0xff;
165 result = std::static_pointer_cast<Actions::BaseAction>(
166 std::make_shared<Actions::PushCharacters>(
167 &Global::wFooter,
168 std::vector<NC::Key::Type>{arg.begin(), arg.end()}));
169 }
170 else
171 error() << "empty argument passed to push_characters\n";
172 }
173 else if (action_name == "require_screen")
174 {
175 // require screen of given type
176 std::string arg = getEnclosedString(line, '"', '"', 0);
177 ScreenType screen_type = stringToScreenType(arg);
178 if (screen_type != ScreenType::Unknown)
179 result = std::static_pointer_cast<Actions::BaseAction>(
180 std::make_shared<Actions::RequireScreen>(screen_type));
181 else
182 error() << "unknown screen passed to require_screen: '" << arg << "'\n";
183 }
184 else if (action_name == "require_runnable")
185 {
186 // require that given action is runnable
187 std::string arg = getEnclosedString(line, '"', '"', 0);
188 auto action = Actions::get_(arg);
189 if (action)
190 result = std::static_pointer_cast<Actions::BaseAction>(
191 std::make_shared<Actions::RequireRunnable>(action));
192 else
193 error() << "unknown action passed to require_runnable: '" << arg << "'\n";
194 }
195 else if (action_name == "run_external_command")
196 {
197 std::string command = getEnclosedString(line, '"', '"', 0);
198 if (!command.empty())
199 result = std::static_pointer_cast<Actions::BaseAction>(
200 std::make_shared<Actions::RunExternalCommand>(std::move(command)));
201 else
202 error() << "empty command passed to run_external_command\n";
203 }
204 else if (action_name == "run_external_console_command")
205 {
206 std::string command = getEnclosedString(line, '"', '"', 0);
207 if (!command.empty())
208 result = std::static_pointer_cast<Actions::BaseAction>(
209 std::make_shared<Actions::RunExternalConsoleCommand>(std::move(command)));
210 else
211 error() << "empty command passed to run_external_console_command\n";
212 }
213 }
214 return result;
215 }
216
217 }
218
readKey(NC::Window & w)219 NC::Key::Type readKey(NC::Window &w)
220 {
221 NC::Key::Type result = NC::Key::None;
222 std::string tmp;
223 NC::Key::Type input;
224 bool alt_pressed = false;
225 while (true)
226 {
227 input = w.readKey();
228 if (input == NC::Key::None)
229 break;
230 if (input & NC::Key::Alt)
231 {
232 // Complete the key and reapply the mask at the end.
233 alt_pressed = true;
234 input &= ~NC::Key::Alt;
235 }
236 if (input > 255) // NC special character
237 {
238 result = input;
239 break;
240 }
241 else
242 {
243 wchar_t wc;
244 tmp += input;
245 size_t conv_res = mbrtowc(&wc, tmp.c_str(), MB_CUR_MAX, 0);
246 if (conv_res == size_t(-1)) // incomplete multibyte character
247 continue;
248 else if (conv_res == size_t(-2)) // garbage character sequence
249 break;
250 else // character complete
251 {
252 result = wc;
253 break;
254 }
255 }
256 }
257 if (alt_pressed)
258 result |= NC::Key::Alt;
259 return result;
260 }
261
keyToWString(const NC::Key::Type key)262 std::wstring keyToWString(const NC::Key::Type key)
263 {
264 std::wstring result;
265
266 if (key == NC::Key::Tab)
267 result += L"Tab";
268 else if (key == NC::Key::Enter)
269 result += L"Enter";
270 else if (key == NC::Key::Escape)
271 result += L"Escape";
272 else if (key >= NC::Key::Ctrl_A && key <= NC::Key::Ctrl_Z)
273 {
274 result += L"Ctrl-";
275 result += 'A' + (key - NC::Key::Ctrl_A);
276 }
277 else if (key == NC::Key::Ctrl_LeftBracket)
278 result += L"Ctrl-[";
279 else if (key == NC::Key::Ctrl_Backslash)
280 result += L"Ctrl-\\";
281 else if (key == NC::Key::Ctrl_RightBracket)
282 result += L"Ctrl-]";
283 else if (key == NC::Key::Ctrl_Caret)
284 result += L"Ctrl-^";
285 else if (key == NC::Key::Ctrl_Underscore)
286 result += L"Ctrl-_";
287 else if (key & NC::Key::Alt)
288 {
289 result += L"Alt-";
290 result += keyToWString(key & ~NC::Key::Alt);
291 }
292 else if (key & NC::Key::Ctrl)
293 {
294 result += L"Ctrl-";
295 result += keyToWString(key & ~NC::Key::Ctrl);
296 }
297 else if (key & NC::Key::Shift)
298 {
299 result += L"Shift-";
300 result += keyToWString(key & ~NC::Key::Shift);
301 }
302 else if (key == NC::Key::Space)
303 result += L"Space";
304 else if (key == NC::Key::Backspace)
305 result += L"Backspace";
306 else if (key == NC::Key::Insert)
307 result += L"Insert";
308 else if (key == NC::Key::Delete)
309 result += L"Delete";
310 else if (key == NC::Key::Home)
311 result += L"Home";
312 else if (key == NC::Key::End)
313 result += L"End";
314 else if (key == NC::Key::PageUp)
315 result += L"PageUp";
316 else if (key == NC::Key::PageDown)
317 result += L"PageDown";
318 else if (key == NC::Key::Up)
319 result += L"Up";
320 else if (key == NC::Key::Down)
321 result += L"Down";
322 else if (key == NC::Key::Left)
323 result += L"Left";
324 else if (key == NC::Key::Right)
325 result += L"Right";
326 else if (key == NC::Key::EoF)
327 result += L"EoF";
328 else if (key >= NC::Key::F1 && key <= NC::Key::F9)
329 {
330 result += L"F";
331 result += '1' + (key - NC::Key::F1);
332 }
333 else if (key >= NC::Key::F10 && key <= NC::Key::F12)
334 {
335 result += L"F1";
336 result += '0' + (key - NC::Key::F10);
337 }
338 else
339 result += std::wstring(1, key);
340
341 return result;
342 }
343
read(const std::string & file)344 bool BindingsConfiguration::read(const std::string &file)
345 {
346 enum class InProgress { None, Command, Key };
347
348 bool result = true;
349
350 std::ifstream f(file);
351 if (!f.is_open())
352 return result;
353
354 // shared variables
355 InProgress in_progress = InProgress::None;
356 size_t line_no = 0;
357 std::string line;
358 Binding::ActionChain actions;
359
360 // def_key specific variables
361 NC::Key::Type key = NC::Key::None;
362 std::string strkey;
363
364 // def_command specific variables
365 bool cmd_immediate = false;
366 std::string cmd_name;
367
368 auto error = [&]() -> std::ostream & {
369 std::cerr << file << ":" << line_no << ": error: ";
370 in_progress = InProgress::None;
371 result = false;
372 return std::cerr;
373 };
374
375 auto bind_in_progress = [&]() -> bool {
376 if (in_progress == InProgress::Command)
377 {
378 if (!actions.empty())
379 {
380 m_commands.insert(std::make_pair(cmd_name, Command(std::move(actions), cmd_immediate)));
381 actions.clear();
382 return true;
383 }
384 else
385 {
386 error() << "definition of command '" << cmd_name << "' cannot be empty\n";
387 return false;
388 }
389 }
390 else if (in_progress == InProgress::Key)
391 {
392 if (!actions.empty())
393 {
394 bind(key, actions);
395 actions.clear();
396 return true;
397 }
398 else
399 {
400 error() << "definition of key '" << strkey << "' cannot be empty\n";
401 return false;
402 }
403 }
404 return true;
405 };
406
407 const char def_command[] = "def_command";
408 const char def_key[] = "def_key";
409
410 while (!f.eof() && ++line_no)
411 {
412 getline(f, line);
413 if (line.empty() || line[0] == '#')
414 continue;
415
416 // beginning of command definition
417 if (!line.compare(0, const_strlen(def_command), def_command))
418 {
419 if (!bind_in_progress())
420 break;
421 in_progress = InProgress::Command;
422 cmd_name = getEnclosedString(line, '"', '"', 0);
423 if (cmd_name.empty())
424 {
425 error() << "command must have non-empty name\n";
426 break;
427 }
428 if (m_commands.find(cmd_name) != m_commands.end())
429 {
430 error() << "redefinition of command '" << cmd_name << "'\n";
431 break;
432 }
433 std::string cmd_type = getEnclosedString(line, '[', ']', 0);
434 if (cmd_type == "immediate")
435 cmd_immediate = true;
436 else if (cmd_type == "deferred")
437 cmd_immediate = false;
438 else
439 {
440 error() << "invalid type of command: '" << cmd_type << "'\n";
441 break;
442 }
443 }
444 // beginning of key definition
445 else if (!line.compare(0, const_strlen(def_key), def_key))
446 {
447 if (!bind_in_progress())
448 break;
449 in_progress = InProgress::Key;
450 strkey = getEnclosedString(line, '"', '"', 0);
451 key = stringToKey(strkey);
452 if (key == NC::Key::None)
453 {
454 error() << "invalid key: '" << strkey << "'\n";
455 break;
456 }
457 }
458 else if (isspace(line[0])) // name of action to be bound
459 {
460 boost::trim(line);
461 auto action = parseActionLine(line, error);
462 if (action)
463 actions.push_back(action);
464 else
465 {
466 error() << "unknown action: '" << line << "'\n";
467 break;
468 }
469 }
470 else
471 {
472 error() << "invalid line: '" << line << "'\n";
473 break;
474 }
475 }
476 bind_in_progress();
477 f.close();
478 return result;
479 }
480
read(const std::vector<std::string> & binding_paths)481 bool BindingsConfiguration::read(const std::vector<std::string> &binding_paths)
482 {
483 return std::all_of(
484 binding_paths.begin(),
485 binding_paths.end(),
486 [&](const std::string &binding_path) {
487 return read(binding_path);
488 }
489 );
490 }
491
generateDefaults()492 void BindingsConfiguration::generateDefaults()
493 {
494 NC::Key::Type k = NC::Key::None;
495 bind(NC::Key::EoF, Actions::Type::Quit);
496 if (notBound(k = stringToKey("mouse")))
497 bind(k, Actions::Type::MouseEvent);
498 if (notBound(k = stringToKey("up")))
499 bind(k, Actions::Type::ScrollUp);
500 if (notBound(k = stringToKey("shift-up")))
501 bind(k, Binding::ActionChain({Actions::get_(Actions::Type::SelectItem), Actions::get_(Actions::Type::ScrollUp)}));
502 if (notBound(k = stringToKey("down")))
503 bind(k, Actions::Type::ScrollDown);
504 if (notBound(k = stringToKey("shift-down")))
505 bind(k, Binding::ActionChain({Actions::get_(Actions::Type::SelectItem), Actions::get_(Actions::Type::ScrollDown)}));
506 if (notBound(k = stringToKey("[")))
507 bind(k, Actions::Type::ScrollUpAlbum);
508 if (notBound(k = stringToKey("]")))
509 bind(k, Actions::Type::ScrollDownAlbum);
510 if (notBound(k = stringToKey("{")))
511 bind(k, Actions::Type::ScrollUpArtist);
512 if (notBound(k = stringToKey("}")))
513 bind(k, Actions::Type::ScrollDownArtist);
514 if (notBound(k = stringToKey("page_up")))
515 bind(k, Actions::Type::PageUp);
516 if (notBound(k = stringToKey("page_down")))
517 bind(k, Actions::Type::PageDown);
518 if (notBound(k = stringToKey("home")))
519 bind(k, Actions::Type::MoveHome);
520 if (notBound(k = stringToKey("end")))
521 bind(k, Actions::Type::MoveEnd);
522 if (notBound(k = stringToKey("insert")))
523 bind(k, Actions::Type::SelectItem);
524 if (notBound(k = stringToKey("enter")))
525 {
526 bind(k, Actions::Type::EnterDirectory);
527 bind(k, Actions::Type::ToggleOutput);
528 bind(k, Actions::Type::RunAction);
529 bind(k, Actions::Type::PlayItem);
530 }
531 if (notBound(k = stringToKey("space")))
532 {
533 bind(k, Actions::Type::AddItemToPlaylist);
534 bind(k, Actions::Type::ToggleLyricsUpdateOnSongChange);
535 bind(k, Actions::Type::ToggleVisualizationType);
536 }
537 if (notBound(k = stringToKey("delete")))
538 {
539 bind(k, Actions::Type::DeletePlaylistItems);
540 bind(k, Actions::Type::DeleteBrowserItems);
541 bind(k, Actions::Type::DeleteStoredPlaylist);
542 }
543 if (notBound(k = stringToKey("right")))
544 {
545 bind(k, Actions::Type::NextColumn);
546 bind(k, Actions::Type::SlaveScreen);
547 bind(k, Actions::Type::VolumeUp);
548 }
549 if (notBound(k = stringToKey("+")))
550 bind(k, Actions::Type::VolumeUp);
551 if (notBound(k = stringToKey("left")))
552 {
553 bind(k, Actions::Type::PreviousColumn);
554 bind(k, Actions::Type::MasterScreen);
555 bind(k, Actions::Type::VolumeDown);
556 }
557 if (notBound(k = stringToKey("-")))
558 bind(k, Actions::Type::VolumeDown);
559 if (notBound(k = stringToKey(":")))
560 bind(k, Actions::Type::ExecuteCommand);
561 if (notBound(k = stringToKey("tab")))
562 bind(k, Actions::Type::NextScreen);
563 if (notBound(k = stringToKey("shift-tab")))
564 bind(k, Actions::Type::PreviousScreen);
565 if (notBound(k = stringToKey("f1")))
566 bind(k, Actions::Type::ShowHelp);
567 if (notBound(k = stringToKey("1")))
568 bind(k, Actions::Type::ShowPlaylist);
569 if (notBound(k = stringToKey("2")))
570 {
571 bind(k, Actions::Type::ShowBrowser);
572 bind(k, Actions::Type::ChangeBrowseMode);
573 }
574 if (notBound(k = stringToKey("3")))
575 {
576 bind(k, Actions::Type::ShowSearchEngine);
577 bind(k, Actions::Type::ResetSearchEngine);
578 }
579 if (notBound(k = stringToKey("4")))
580 {
581 bind(k, Actions::Type::ShowMediaLibrary);
582 bind(k, Actions::Type::ToggleMediaLibraryColumnsMode);
583 }
584 if (notBound(k = stringToKey("5")))
585 bind(k, Actions::Type::ShowPlaylistEditor);
586 if (notBound(k = stringToKey("6")))
587 bind(k, Actions::Type::ShowTagEditor);
588 if (notBound(k = stringToKey("7")))
589 bind(k, Actions::Type::ShowOutputs);
590 if (notBound(k = stringToKey("8")))
591 bind(k, Actions::Type::ShowVisualizer);
592 if (notBound(k = stringToKey("=")))
593 bind(k, Actions::Type::ShowClock);
594 if (notBound(k = stringToKey("@")))
595 bind(k, Actions::Type::ShowServerInfo);
596 if (notBound(k = stringToKey("s")))
597 bind(k, Actions::Type::Stop);
598 if (notBound(k = stringToKey("p")))
599 bind(k, Actions::Type::Pause);
600 if (notBound(k = stringToKey(">")))
601 bind(k, Actions::Type::Next);
602 if (notBound(k = stringToKey("<")))
603 bind(k, Actions::Type::Previous);
604 if (notBound(k = stringToKey("ctrl-h")))
605 {
606 bind(k, Actions::Type::JumpToParentDirectory);
607 bind(k, Actions::Type::ReplaySong);
608 }
609 if (notBound(k = stringToKey("backspace")))
610 {
611 bind(k, Actions::Type::JumpToParentDirectory);
612 bind(k, Actions::Type::ReplaySong);
613 bind(k, Actions::Type::Play);
614 }
615 if (notBound(k = stringToKey("f")))
616 bind(k, Actions::Type::SeekForward);
617 if (notBound(k = stringToKey("b")))
618 bind(k, Actions::Type::SeekBackward);
619 if (notBound(k = stringToKey("r")))
620 bind(k, Actions::Type::ToggleRepeat);
621 if (notBound(k = stringToKey("z")))
622 bind(k, Actions::Type::ToggleRandom);
623 if (notBound(k = stringToKey("y")))
624 {
625 bind(k, Actions::Type::SaveTagChanges);
626 bind(k, Actions::Type::StartSearching);
627 bind(k, Actions::Type::ToggleSingle);
628 }
629 if (notBound(k = stringToKey("R")))
630 bind(k, Actions::Type::ToggleConsume);
631 if (notBound(k = stringToKey("Y")))
632 bind(k, Actions::Type::ToggleReplayGainMode);
633 if (notBound(k = stringToKey("T")))
634 bind(k, Actions::Type::ToggleAddMode);
635 if (notBound(k = stringToKey("|")))
636 bind(k, Actions::Type::ToggleMouse);
637 if (notBound(k = stringToKey("#")))
638 bind(k, Actions::Type::ToggleBitrateVisibility);
639 if (notBound(k = stringToKey("Z")))
640 bind(k, Actions::Type::Shuffle);
641 if (notBound(k = stringToKey("x")))
642 bind(k, Actions::Type::ToggleCrossfade);
643 if (notBound(k = stringToKey("X")))
644 bind(k, Actions::Type::SetCrossfade);
645 if (notBound(k = stringToKey("u")))
646 bind(k, Actions::Type::UpdateDatabase);
647 if (notBound(k = stringToKey("ctrl-s")))
648 {
649 bind(k, Actions::Type::SortPlaylist);
650 bind(k, Actions::Type::ToggleBrowserSortMode);
651 bind(k, Actions::Type::ToggleMediaLibrarySortMode);
652 }
653 if (notBound(k = stringToKey("ctrl-r")))
654 bind(k, Actions::Type::ReversePlaylist);
655 if (notBound(k = stringToKey("ctrl-f")))
656 bind(k, Actions::Type::ApplyFilter);
657 if (notBound(k = stringToKey("ctrl-_")))
658 bind(k, Actions::Type::SelectFoundItems);
659 if (notBound(k = stringToKey("/")))
660 {
661 bind(k, Actions::Type::Find);
662 bind(k, Actions::Type::FindItemForward);
663 }
664 if (notBound(k = stringToKey("?")))
665 {
666 bind(k, Actions::Type::Find);
667 bind(k, Actions::Type::FindItemBackward);
668 }
669 if (notBound(k = stringToKey(".")))
670 bind(k, Actions::Type::NextFoundItem);
671 if (notBound(k = stringToKey(",")))
672 bind(k, Actions::Type::PreviousFoundItem);
673 if (notBound(k = stringToKey("w")))
674 bind(k, Actions::Type::ToggleFindMode);
675 if (notBound(k = stringToKey("e")))
676 {
677 bind(k, Actions::Type::EditSong);
678 bind(k, Actions::Type::EditLibraryTag);
679 bind(k, Actions::Type::EditLibraryAlbum);
680 bind(k, Actions::Type::EditDirectoryName);
681 bind(k, Actions::Type::EditPlaylistName);
682 bind(k, Actions::Type::EditLyrics);
683 }
684 if (notBound(k = stringToKey("i")))
685 bind(k, Actions::Type::ShowSongInfo);
686 if (notBound(k = stringToKey("I")))
687 bind(k, Actions::Type::ShowArtistInfo);
688 if (notBound(k = stringToKey("g")))
689 bind(k, Actions::Type::JumpToPositionInSong);
690 if (notBound(k = stringToKey("l")))
691 bind(k, Actions::Type::ShowLyrics);
692 if (notBound(k = stringToKey("ctrl-v")))
693 bind(k, Actions::Type::SelectRange);
694 if (notBound(k = stringToKey("v")))
695 bind(k, Actions::Type::ReverseSelection);
696 if (notBound(k = stringToKey("V")))
697 bind(k, Actions::Type::RemoveSelection);
698 if (notBound(k = stringToKey("B")))
699 bind(k, Actions::Type::SelectAlbum);
700 if (notBound(k = stringToKey("a")))
701 bind(k, Actions::Type::AddSelectedItems);
702 if (notBound(k = stringToKey("c")))
703 {
704 bind(k, Actions::Type::ClearPlaylist);
705 bind(k, Actions::Type::ClearMainPlaylist);
706 }
707 if (notBound(k = stringToKey("C")))
708 {
709 bind(k, Actions::Type::CropPlaylist);
710 bind(k, Actions::Type::CropMainPlaylist);
711 }
712 if (notBound(k = stringToKey("m")))
713 {
714 bind(k, Actions::Type::MoveSortOrderUp);
715 bind(k, Actions::Type::MoveSelectedItemsUp);
716 }
717 if (notBound(k = stringToKey("n")))
718 {
719 bind(k, Actions::Type::MoveSortOrderDown);
720 bind(k, Actions::Type::MoveSelectedItemsDown);
721 }
722 if (notBound(k = stringToKey("M")))
723 bind(k, Actions::Type::MoveSelectedItemsTo);
724 if (notBound(k = stringToKey("A")))
725 bind(k, Actions::Type::Add);
726 if (notBound(k = stringToKey("S")))
727 bind(k, Actions::Type::SavePlaylist);
728 if (notBound(k = stringToKey("o")))
729 bind(k, Actions::Type::JumpToPlayingSong);
730 if (notBound(k = stringToKey("G")))
731 {
732 bind(k, Actions::Type::JumpToBrowser);
733 bind(k, Actions::Type::JumpToPlaylistEditor);
734 }
735 if (notBound(k = stringToKey("~")))
736 bind(k, Actions::Type::JumpToMediaLibrary);
737 if (notBound(k = stringToKey("E")))
738 bind(k, Actions::Type::JumpToTagEditor);
739 if (notBound(k = stringToKey("U")))
740 bind(k, Actions::Type::TogglePlayingSongCentering);
741 if (notBound(k = stringToKey("P")))
742 bind(k, Actions::Type::ToggleDisplayMode);
743 if (notBound(k = stringToKey("\\")))
744 bind(k, Actions::Type::ToggleInterface);
745 if (notBound(k = stringToKey("!")))
746 bind(k, Actions::Type::ToggleSeparatorsBetweenAlbums);
747 if (notBound(k = stringToKey("L")))
748 bind(k, Actions::Type::ToggleLyricsFetcher);
749 if (notBound(k = stringToKey("F")))
750 bind(k, Actions::Type::FetchLyricsInBackground);
751 if (notBound(k = stringToKey("alt-l")))
752 bind(k, Actions::Type::ToggleFetchingLyricsInBackground);
753 if (notBound(k = stringToKey("ctrl-l")))
754 bind(k, Actions::Type::ToggleScreenLock);
755 if (notBound(k = stringToKey("`")))
756 {
757 bind(k, Actions::Type::ToggleLibraryTagType);
758 bind(k, Actions::Type::RefetchLyrics);
759 bind(k, Actions::Type::AddRandomItems);
760 }
761 if (notBound(k = stringToKey("ctrl-p")))
762 bind(k, Actions::Type::SetSelectedItemsPriority);
763 if (notBound(k = stringToKey("q")))
764 bind(k, Actions::Type::Quit);
765 }
766
findCommand(const std::string & name)767 const Command *BindingsConfiguration::findCommand(const std::string &name)
768 {
769 const Command *ptr = nullptr;
770 auto it = m_commands.find(name);
771 if (it != m_commands.end())
772 ptr = &it->second;
773 return ptr;
774 }
775
get(const NC::Key::Type & k)776 BindingsConfiguration::BindingIteratorPair BindingsConfiguration::get(const NC::Key::Type &k)
777 {
778 std::pair<BindingIterator, BindingIterator> result;
779 auto it = m_bindings.find(k);
780 if (it != m_bindings.end()) {
781 result.first = it->second.begin();
782 result.second = it->second.end();
783 } else {
784 auto list_end = m_bindings.begin()->second.end();
785 result.first = list_end;
786 result.second = list_end;
787 }
788 return result;
789 }
790