Compiler.h: move to util/
[ncmpc-debian.git] / src / screen_keydef.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2018 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
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 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.
18  */
19
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"
28 #include "screen.hxx"
29 #include "KeyName.hxx"
30 #include "i18n.h"
31 #include "conf.hxx"
32 #include "Bindings.hxx"
33 #include "GlobalBindings.hxx"
34 #include "screen_utils.hxx"
35 #include "options.hxx"
36 #include "util/Compiler.h"
37
38 #include <algorithm>
39
40 #include <assert.h>
41 #include <errno.h>
42 #include <string.h>
43
44 class CommandKeysPage final : public ListPage, ListText {
45         ScreenManager &screen;
46
47         const KeyBindings *bindings;
48         KeyBinding *binding;
49
50         /**
51          * The command being edited, represented by a array subscript
52          * to #bindings, or -1, if no command is being edited
53          */
54         int subcmd = -1;
55
56         /** The number of keys assigned to the current command */
57         unsigned subcmd_n_keys = 0;
58
59 public:
60         CommandKeysPage(ScreenManager &_screen, WINDOW *w, Size size)
61                 :ListPage(w, size), screen(_screen) {}
62
63         void SetCommand(KeyBindings *_bindings, unsigned _cmd) {
64                 bindings = _bindings;
65                 binding = &_bindings->key_bindings[_cmd];
66                 subcmd = _cmd;
67                 lw.Reset();
68                 check_subcmd_length();
69         }
70
71 private:
72         /** The position of the up ("[..]") item */
73         static constexpr unsigned subcmd_item_up() {
74                 return 0;
75         }
76
77         /** The position of the "add a key" item */
78         gcc_pure
79         unsigned subcmd_item_add() const {
80                 return subcmd_n_keys + 1;
81         }
82
83         /** The number of items in the list_window, if there's a command being edited */
84         gcc_pure
85         unsigned subcmd_length() const {
86                 return subcmd_item_add() + 1;
87         }
88
89         /** Check whether a given item is a key */
90         gcc_pure
91         bool subcmd_item_is_key(unsigned i) const {
92                 return (i > subcmd_item_up() && i < subcmd_item_add());
93         }
94
95         /**
96          * Convert an item id (as in lw.selected) into a "key id", which is an array
97          * subscript to cmds[subcmd].keys.
98          */
99         static constexpr unsigned subcmd_item_to_key_id(unsigned i) {
100                 return i - 1;
101         }
102
103         /* TODO: rename to check_n_keys / subcmd_count_keys? */
104         void check_subcmd_length();
105
106         /**
107          * Delete a key from a given command's definition.
108          *
109          * @param key_index the key (see below)
110          */
111         void DeleteKey(int key_index);
112
113         /**
114          * Assigns a new key to a key slot.
115          */
116         void OverwriteKey(int key_index);
117
118         /**
119          * Assign a new key to a new slot.
120          */
121         void AddKey();
122
123 public:
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;
129
130 private:
131         /* virtual methods from class ListText */
132         const char *GetListItemText(char *buffer, size_t size,
133                                     unsigned i) const override;
134 };
135
136 /* TODO: rename to check_n_keys / subcmd_count_keys? */
137 void
138 CommandKeysPage::check_subcmd_length()
139 {
140         subcmd_n_keys = binding->GetKeyCount();
141
142         lw.SetLength(subcmd_length());
143 }
144
145 void
146 CommandKeysPage::DeleteKey(int key_index)
147 {
148         /* shift the keys to close the gap that appeared */
149         int i = key_index+1;
150         while (i < MAX_COMMAND_KEYS && binding->keys[i])
151                 binding->keys[key_index++] = binding->keys[i++];
152
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;
157
158         binding->modified = true;
159         check_subcmd_length();
160
161         screen_status_message(_("Deleted"));
162
163         /* repaint */
164         SetDirty();
165
166         /* update key conflict flags */
167         bindings->Check(nullptr, 0);
168 }
169
170 void
171 CommandKeysPage::OverwriteKey(int key_index)
172 {
173         assert(key_index < MAX_COMMAND_KEYS);
174
175         char prompt[256];
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);
180
181         if (key == ERR) {
182                 screen_status_message(_("Aborted"));
183                 return;
184         }
185
186         if (key == '\0') {
187                 screen_status_message(_("Ctrl-Space can't be used"));
188                 return;
189         }
190
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));
195                 screen_bell();
196                 return;
197         }
198
199         binding->keys[key_index] = key;
200         binding->modified = true;
201
202         screen_status_printf(_("Assigned %s to %s"),
203                              key2str(key),
204                              get_key_command_name(Command(subcmd)));
205         check_subcmd_length();
206
207         /* repaint */
208         SetDirty();
209
210         /* update key conflict flags */
211         bindings->Check(nullptr, 0);
212 }
213
214 void
215 CommandKeysPage::AddKey()
216 {
217         if (subcmd_n_keys < MAX_COMMAND_KEYS)
218                 OverwriteKey(subcmd_n_keys);
219 }
220
221 const char *
222 CommandKeysPage::GetListItemText(char *buffer, size_t size,
223                                  unsigned idx) const
224 {
225         if (idx == subcmd_item_up())
226                 return "[..]";
227
228         if (idx == subcmd_item_add()) {
229                 snprintf(buffer, size, "%d. %s", idx, _("Add new key"));
230                 return buffer;
231         }
232
233         assert(subcmd_item_is_key(idx));
234
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)]);
239         return buffer;
240 }
241
242 void
243 CommandKeysPage::OnOpen(gcc_unused struct mpdclient &c)
244 {
245         // TODO
246 }
247
248 const char *
249 CommandKeysPage::GetTitle(char *str, size_t size) const
250 {
251         snprintf(str, size, _("Edit keys for %s"),
252                  get_key_command_name(Command(subcmd)));
253         return str;
254 }
255
256 void
257 CommandKeysPage::Paint() const
258 {
259         lw.Paint(TextListRenderer(*this));
260 }
261
262 bool
263 CommandKeysPage::OnCommand(struct mpdclient &c, Command cmd)
264 {
265         if (cmd == Command::LIST_RANGE_SELECT)
266                 return false;
267
268         if (ListPage::OnCommand(c, cmd))
269                 return true;
270
271         switch(cmd) {
272         case Command::PLAY:
273                 if (lw.selected == subcmd_item_up()) {
274                         screen.OnCommand(c, Command::GO_PARENT_DIRECTORY);
275                 } else if (lw.selected == subcmd_item_add()) {
276                         AddKey();
277                 } else {
278                         /* just to be sure ;-) */
279                         assert(subcmd_item_is_key(lw.selected));
280                         OverwriteKey(subcmd_item_to_key_id(lw.selected));
281                 }
282                 return true;
283         case Command::DELETE:
284                 if (subcmd_item_is_key(lw.selected))
285                         DeleteKey(subcmd_item_to_key_id(lw.selected));
286
287                 return true;
288         case Command::ADD:
289                 AddKey();
290                 return true;
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);
296                 SetDirty();
297                 return true;
298
299         default:
300                 return false;
301         }
302
303         /* unreachable */
304         assert(0);
305         return false;
306 }
307
308 class CommandListPage final : public ListPage, ListText {
309         ScreenManager &screen;
310
311         KeyBindings *bindings;
312
313         /** the number of commands */
314         static constexpr size_t command_n_commands = size_t(Command::NONE);
315
316 public:
317         CommandListPage(ScreenManager &_screen, WINDOW *w, Size size)
318                 :ListPage(w, size), screen(_screen) {}
319
320         ~CommandListPage() override {
321                 delete bindings;
322         }
323
324         KeyBindings *GetBindings() {
325                 return bindings;
326         }
327
328         int GetSelectedCommand() const {
329                 return lw.selected < command_n_commands
330                         ? (int)lw.selected
331                         : -1;
332         }
333
334 private:
335         /**
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.
338          */
339         static constexpr unsigned command_item_apply() {
340                 return command_n_commands;
341         }
342
343         /** the position of the "apply and save" item */
344         static constexpr unsigned command_item_save() {
345                 return command_item_apply() + 1;
346         }
347
348         /** the number of items in the "command" view */
349         gcc_pure
350         unsigned command_length() const {
351                 return command_item_save() + 1;
352         }
353
354         /** The position of the up ("[..]") item */
355         static constexpr unsigned subcmd_item_up() {
356                 return 0;
357         }
358
359 public:
360         bool IsModified() const;
361
362         void Apply();
363         void Save();
364
365 public:
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;
371
372 private:
373         /* virtual methods from class ListText */
374         const char *GetListItemText(char *buffer, size_t size,
375                                     unsigned i) const override;
376 };
377
378 bool
379 CommandListPage::IsModified() const
380 {
381         const auto &orginal_bindings = GetGlobalKeyBindings();
382         constexpr size_t size = sizeof(orginal_bindings);
383
384         return memcmp(&orginal_bindings, &bindings, size) != 0;
385 }
386
387 void
388 CommandListPage::Apply()
389 {
390         if (IsModified()) {
391                 auto &orginal_bindings = GetGlobalKeyBindings();
392                 orginal_bindings = *bindings;
393                 screen_status_message(_("You have new key bindings"));
394         } else
395                 screen_status_message(_("Keybindings unchanged."));
396 }
397
398 void
399 CommandListPage::Save()
400 {
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"));
406                         screen_bell();
407                         return;
408                 }
409         } else
410                 filename = options.key_file;
411
412         FILE *f = fopen(filename.c_str(), "w");
413         if (f == nullptr) {
414                 screen_status_printf("%s: %s - %s", _("Error"),
415                                      filename.c_str(), strerror(errno));
416                 screen_bell();
417                 return;
418         }
419
420         if (GetGlobalKeyBindings().WriteToFile(f, KEYDEF_WRITE_HEADER))
421                 screen_status_printf(_("Wrote %s"), filename.c_str());
422         else
423                 screen_status_printf("%s: %s - %s", _("Error"),
424                                      filename.c_str(), strerror(errno));
425
426         fclose(f);
427 }
428
429 const char *
430 CommandListPage::GetListItemText(char *buffer, size_t size, unsigned idx) const
431 {
432         if (idx == command_item_apply())
433                 return _("===> Apply key bindings ");
434         if (idx == command_item_save())
435                 return _("===> Apply & Save key bindings  ");
436
437         assert(idx < command_n_commands);
438
439         /*
440          * Format the lines in two aligned columnes for the key name and
441          * the description, like this:
442          *
443          *      this-command - do this
444          *      that-one     - do that
445          */
446         const char *name = get_key_command_name(Command(idx));
447         size_t len = strlen(name);
448         strncpy(buffer, name, size);
449
450         if (len < get_cmds_max_name_width())
451                 memset(buffer + len, ' ', get_cmds_max_name_width() - len);
452
453         snprintf(buffer + get_cmds_max_name_width(),
454                  size - get_cmds_max_name_width(),
455                  " - %s", gettext(get_command_definitions()[idx].description));
456
457         return buffer;
458 }
459
460 void
461 CommandListPage::OnOpen(gcc_unused struct mpdclient &c)
462 {
463         if (bindings == nullptr)
464                 bindings = new KeyBindings(GetGlobalKeyBindings());
465
466         lw.SetLength(command_length());
467 }
468
469 const char *
470 CommandListPage::GetTitle(char *, size_t) const
471 {
472         return _("Edit key bindings");
473 }
474
475 void
476 CommandListPage::Paint() const
477 {
478         lw.Paint(TextListRenderer(*this));
479 }
480
481 bool
482 CommandListPage::OnCommand(struct mpdclient &c, Command cmd)
483 {
484         if (cmd == Command::LIST_RANGE_SELECT)
485                 return false;
486
487         if (ListPage::OnCommand(c, cmd))
488                 return true;
489
490         switch(cmd) {
491         case Command::PLAY:
492                 if (lw.selected == command_item_apply()) {
493                         Apply();
494                         return true;
495                 } else if (lw.selected == command_item_save()) {
496                         Apply();
497                         Save();
498                         return true;
499                 }
500
501                 break;
502
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);
508                 SetDirty();
509                 return true;
510
511         default:
512                 break;
513         }
514
515         return false;
516 }
517
518 class KeyDefPage final : public ProxyPage {
519         CommandListPage command_list_page;
520         CommandKeysPage command_keys_page;
521
522 public:
523         KeyDefPage(ScreenManager &screen, WINDOW *_w, Size size)
524                 :ProxyPage(_w),
525                  command_list_page(screen, _w, size),
526                  command_keys_page(screen, _w, size) {}
527
528 public:
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;
533 };
534
535 static Page *
536 keydef_init(ScreenManager &screen, WINDOW *w, Size size)
537 {
538         return new KeyDefPage(screen, w, size);
539 }
540
541 void
542 KeyDefPage::OnOpen(struct mpdclient &c)
543 {
544         ProxyPage::OnOpen(c);
545
546         if (GetCurrentPage() == nullptr)
547                 SetCurrentPage(c, &command_list_page);
548 }
549
550 void
551 KeyDefPage::OnClose()
552 {
553         if (command_list_page.IsModified())
554                 screen_status_message(_("Note: Did you forget to \'Apply\' your changes?"));
555
556         ProxyPage::OnClose();
557 }
558
559 bool
560 KeyDefPage::OnCommand(struct mpdclient &c, Command cmd)
561 {
562         if (ProxyPage::OnCommand(c, cmd))
563                 return true;
564
565         switch(cmd) {
566         case Command::PLAY:
567                 if (GetCurrentPage() == &command_list_page) {
568                         int s = command_list_page.GetSelectedCommand();
569                         if (s >= 0) {
570                                 command_keys_page.SetCommand(command_list_page.GetBindings(),
571                                                              s);
572                                 SetCurrentPage(c, &command_keys_page);
573                                 return true;
574                         }
575                 }
576
577                 break;
578
579         case Command::GO_PARENT_DIRECTORY:
580         case Command::GO_ROOT_DIRECTORY:
581                 if (GetCurrentPage() != &command_list_page) {
582                         SetCurrentPage(c, &command_list_page);
583                         return true;
584                 }
585
586                 break;
587
588         case Command::SAVE_PLAYLIST:
589                 command_list_page.Apply();
590                 command_list_page.Save();
591                 return true;
592
593         default:
594                 return false;
595         }
596
597         /* unreachable */
598         assert(0);
599         return false;
600 }
601
602 const PageMeta screen_keydef = {
603         "keydef",
604         N_("Keys"),
605         Command::SCREEN_KEYDEF,
606         keydef_init,
607 };