1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2018 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
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.
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.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "screen_keydef.hxx"
21 #include "PageMeta.hxx"
22 #include "ListPage.hxx"
23 #include "ListText.hxx"
24 #include "TextListRenderer.hxx"
25 #include "ProxyPage.hxx"
26 #include "screen_status.hxx"
27 #include "screen_find.hxx"
29 #include "KeyName.hxx"
32 #include "Bindings.hxx"
33 #include "GlobalBindings.hxx"
34 #include "screen_utils.hxx"
35 #include "options.hxx"
44 class CommandKeysPage final : public ListPage, ListText {
45 ScreenManager &screen;
47 const KeyBindings *bindings;
51 * The command being edited, represented by a array subscript
52 * to #bindings, or -1, if no command is being edited
56 /** The number of keys assigned to the current command */
57 unsigned subcmd_n_keys = 0;
60 CommandKeysPage(ScreenManager &_screen, WINDOW *w, Size size)
61 :ListPage(w, size), screen(_screen) {}
63 void SetCommand(KeyBindings *_bindings, unsigned _cmd) {
65 binding = &_bindings->key_bindings[_cmd];
68 check_subcmd_length();
72 /** The position of the up ("[..]") item */
73 static constexpr unsigned subcmd_item_up() {
77 /** The position of the "add a key" item */
79 unsigned subcmd_item_add() const {
80 return subcmd_n_keys + 1;
83 /** The number of items in the list_window, if there's a command being edited */
85 unsigned subcmd_length() const {
86 return subcmd_item_add() + 1;
89 /** Check whether a given item is a key */
91 bool subcmd_item_is_key(unsigned i) const {
92 return (i > subcmd_item_up() && i < subcmd_item_add());
96 * Convert an item id (as in lw.selected) into a "key id", which is an array
97 * subscript to cmds[subcmd].keys.
99 static constexpr unsigned subcmd_item_to_key_id(unsigned i) {
103 /* TODO: rename to check_n_keys / subcmd_count_keys? */
104 void check_subcmd_length();
107 * Delete a key from a given command's definition.
109 * @param key_index the key (see below)
111 void DeleteKey(int key_index);
114 * Assigns a new key to a key slot.
116 void OverwriteKey(int key_index);
119 * Assign a new key to a new slot.
124 /* virtual methods from class Page */
125 void OnOpen(struct mpdclient &c) override;
126 void Paint() const override;
127 bool OnCommand(struct mpdclient &c, Command cmd) override;
128 const char *GetTitle(char *s, size_t size) const override;
131 /* virtual methods from class ListText */
132 const char *GetListItemText(char *buffer, size_t size,
133 unsigned i) const override;
136 /* TODO: rename to check_n_keys / subcmd_count_keys? */
138 CommandKeysPage::check_subcmd_length()
140 subcmd_n_keys = binding->GetKeyCount();
142 lw.SetLength(subcmd_length());
146 CommandKeysPage::DeleteKey(int key_index)
148 /* shift the keys to close the gap that appeared */
150 while (i < MAX_COMMAND_KEYS && binding->keys[i])
151 binding->keys[key_index++] = binding->keys[i++];
153 /* As key_index now holds the index of the last key slot that contained
154 a key, we use it to empty this slot, because this key has been copied
155 to the previous slot in the loop above */
156 binding->keys[key_index] = 0;
158 binding->modified = true;
159 check_subcmd_length();
161 screen_status_message(_("Deleted"));
166 /* update key conflict flags */
167 bindings->Check(nullptr, 0);
171 CommandKeysPage::OverwriteKey(int key_index)
173 assert(key_index < MAX_COMMAND_KEYS);
176 snprintf(prompt, sizeof(prompt),
177 _("Enter new key for %s: "),
178 get_key_command_name(Command(subcmd)));
179 const int key = screen_getch(prompt);
182 screen_status_message(_("Aborted"));
187 screen_status_message(_("Ctrl-Space can't be used"));
191 const Command cmd = bindings->FindKey(key);
192 if (cmd != Command::NONE) {
193 screen_status_printf(_("Error: key %s is already used for %s"),
194 key2str(key), get_key_command_name(cmd));
199 binding->keys[key_index] = key;
200 binding->modified = true;
202 screen_status_printf(_("Assigned %s to %s"),
204 get_key_command_name(Command(subcmd)));
205 check_subcmd_length();
210 /* update key conflict flags */
211 bindings->Check(nullptr, 0);
215 CommandKeysPage::AddKey()
217 if (subcmd_n_keys < MAX_COMMAND_KEYS)
218 OverwriteKey(subcmd_n_keys);
222 CommandKeysPage::GetListItemText(char *buffer, size_t size,
225 if (idx == subcmd_item_up())
228 if (idx == subcmd_item_add()) {
229 snprintf(buffer, size, "%d. %s", idx, _("Add new key"));
233 assert(subcmd_item_is_key(idx));
235 snprintf(buffer, size,
236 "%d. %-20s (%d) ", idx,
237 key2str(binding->keys[subcmd_item_to_key_id(idx)]),
238 binding->keys[subcmd_item_to_key_id(idx)]);
243 CommandKeysPage::OnOpen(gcc_unused struct mpdclient &c)
249 CommandKeysPage::GetTitle(char *str, size_t size) const
251 snprintf(str, size, _("Edit keys for %s"),
252 get_key_command_name(Command(subcmd)));
257 CommandKeysPage::Paint() const
259 lw.Paint(TextListRenderer(*this));
263 CommandKeysPage::OnCommand(struct mpdclient &c, Command cmd)
265 if (cmd == Command::LIST_RANGE_SELECT)
268 if (ListPage::OnCommand(c, cmd))
273 if (lw.selected == subcmd_item_up()) {
274 screen.OnCommand(c, Command::GO_PARENT_DIRECTORY);
275 } else if (lw.selected == subcmd_item_add()) {
278 /* just to be sure ;-) */
279 assert(subcmd_item_is_key(lw.selected));
280 OverwriteKey(subcmd_item_to_key_id(lw.selected));
283 case Command::DELETE:
284 if (subcmd_item_is_key(lw.selected))
285 DeleteKey(subcmd_item_to_key_id(lw.selected));
291 case Command::LIST_FIND:
292 case Command::LIST_RFIND:
293 case Command::LIST_FIND_NEXT:
294 case Command::LIST_RFIND_NEXT:
295 screen_find(screen, &lw, cmd, *this);
308 class CommandListPage final : public ListPage, ListText {
309 ScreenManager &screen;
311 KeyBindings *bindings;
313 /** the number of commands */
314 static constexpr size_t command_n_commands = size_t(Command::NONE);
317 CommandListPage(ScreenManager &_screen, WINDOW *w, Size size)
318 :ListPage(w, size), screen(_screen) {}
320 ~CommandListPage() override {
324 KeyBindings *GetBindings() {
328 int GetSelectedCommand() const {
329 return lw.selected < command_n_commands
336 * the position of the "apply" item. It's the same as command_n_commands,
337 * because array subscripts start at 0, while numbers of items start at 1.
339 static constexpr unsigned command_item_apply() {
340 return command_n_commands;
343 /** the position of the "apply and save" item */
344 static constexpr unsigned command_item_save() {
345 return command_item_apply() + 1;
348 /** the number of items in the "command" view */
350 unsigned command_length() const {
351 return command_item_save() + 1;
354 /** The position of the up ("[..]") item */
355 static constexpr unsigned subcmd_item_up() {
360 bool IsModified() const;
366 /* virtual methods from class Page */
367 void OnOpen(struct mpdclient &c) override;
368 void Paint() const override;
369 bool OnCommand(struct mpdclient &c, Command cmd) override;
370 const char *GetTitle(char *s, size_t size) const override;
373 /* virtual methods from class ListText */
374 const char *GetListItemText(char *buffer, size_t size,
375 unsigned i) const override;
379 CommandListPage::IsModified() const
381 const auto &orginal_bindings = GetGlobalKeyBindings();
382 constexpr size_t size = sizeof(orginal_bindings);
384 return memcmp(&orginal_bindings, &bindings, size) != 0;
388 CommandListPage::Apply()
391 auto &orginal_bindings = GetGlobalKeyBindings();
392 orginal_bindings = *bindings;
393 screen_status_message(_("You have new key bindings"));
395 screen_status_message(_("Keybindings unchanged."));
399 CommandListPage::Save()
401 std::string filename;
402 if (options.key_file.empty()) {
403 filename = MakeKeysPath();
404 if (filename.empty()) {
405 screen_status_message(_("Unable to write configuration"));
410 filename = options.key_file;
412 FILE *f = fopen(filename.c_str(), "w");
414 screen_status_printf("%s: %s - %s", _("Error"),
415 filename.c_str(), strerror(errno));
420 if (GetGlobalKeyBindings().WriteToFile(f, KEYDEF_WRITE_HEADER))
421 screen_status_printf(_("Wrote %s"), filename.c_str());
423 screen_status_printf("%s: %s - %s", _("Error"),
424 filename.c_str(), strerror(errno));
430 CommandListPage::GetListItemText(char *buffer, size_t size, unsigned idx) const
432 if (idx == command_item_apply())
433 return _("===> Apply key bindings ");
434 if (idx == command_item_save())
435 return _("===> Apply & Save key bindings ");
437 assert(idx < command_n_commands);
440 * Format the lines in two aligned columnes for the key name and
441 * the description, like this:
443 * this-command - do this
446 const char *name = get_key_command_name(Command(idx));
447 size_t len = strlen(name);
448 strncpy(buffer, name, size);
450 if (len < get_cmds_max_name_width())
451 memset(buffer + len, ' ', get_cmds_max_name_width() - len);
453 snprintf(buffer + get_cmds_max_name_width(),
454 size - get_cmds_max_name_width(),
455 " - %s", gettext(get_command_definitions()[idx].description));
461 CommandListPage::OnOpen(gcc_unused struct mpdclient &c)
463 if (bindings == nullptr)
464 bindings = new KeyBindings(GetGlobalKeyBindings());
466 lw.SetLength(command_length());
470 CommandListPage::GetTitle(char *, size_t) const
472 return _("Edit key bindings");
476 CommandListPage::Paint() const
478 lw.Paint(TextListRenderer(*this));
482 CommandListPage::OnCommand(struct mpdclient &c, Command cmd)
484 if (cmd == Command::LIST_RANGE_SELECT)
487 if (ListPage::OnCommand(c, cmd))
492 if (lw.selected == command_item_apply()) {
495 } else if (lw.selected == command_item_save()) {
503 case Command::LIST_FIND:
504 case Command::LIST_RFIND:
505 case Command::LIST_FIND_NEXT:
506 case Command::LIST_RFIND_NEXT:
507 screen_find(screen, &lw, cmd, *this);
518 class KeyDefPage final : public ProxyPage {
519 CommandListPage command_list_page;
520 CommandKeysPage command_keys_page;
523 KeyDefPage(ScreenManager &screen, WINDOW *_w, Size size)
525 command_list_page(screen, _w, size),
526 command_keys_page(screen, _w, size) {}
529 /* virtual methods from class Page */
530 void OnOpen(struct mpdclient &c) override;
531 void OnClose() override;
532 bool OnCommand(struct mpdclient &c, Command cmd) override;
536 keydef_init(ScreenManager &screen, WINDOW *w, Size size)
538 return new KeyDefPage(screen, w, size);
542 KeyDefPage::OnOpen(struct mpdclient &c)
544 ProxyPage::OnOpen(c);
546 if (GetCurrentPage() == nullptr)
547 SetCurrentPage(c, &command_list_page);
551 KeyDefPage::OnClose()
553 if (command_list_page.IsModified())
554 screen_status_message(_("Note: Did you forget to \'Apply\' your changes?"));
556 ProxyPage::OnClose();
560 KeyDefPage::OnCommand(struct mpdclient &c, Command cmd)
562 if (ProxyPage::OnCommand(c, cmd))
567 if (GetCurrentPage() == &command_list_page) {
568 int s = command_list_page.GetSelectedCommand();
570 command_keys_page.SetCommand(command_list_page.GetBindings(),
572 SetCurrentPage(c, &command_keys_page);
579 case Command::GO_PARENT_DIRECTORY:
580 case Command::GO_ROOT_DIRECTORY:
581 if (GetCurrentPage() != &command_list_page) {
582 SetCurrentPage(c, &command_list_page);
588 case Command::SAVE_PLAYLIST:
589 command_list_page.Apply();
590 command_list_page.Save();
602 const PageMeta screen_keydef = {
605 Command::SCREEN_KEYDEF,