d768352854d06873caf7aeb8774cc951c7365969
1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2010 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 */
20 #include "screen_keydef.h"
21 #include "screen_interface.h"
22 #include "screen_status.h"
23 #include "screen_find.h"
24 #include "i18n.h"
25 #include "conf.h"
26 #include "screen.h"
27 #include "screen_utils.h"
29 #include <assert.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <glib.h>
34 static struct list_window *lw;
36 static command_definition_t *cmds = NULL;
38 /** the number of commands */
39 static unsigned command_n_commands = 0;
41 /**
42 * the position of the "apply" item. It's the same as command_n_commands,
43 * because array subscripts start at 0, while numbers of items start at 1.
44 */
45 #define command_item_apply (command_n_commands)
47 /** the position of the "apply and save" item */
48 #define command_item_save (command_item_apply + 1)
50 /** the number of items on the "command" view */
51 #define command_length (command_item_save + 1)
54 /**
55 * The command being edited, represented by a array subscript to @cmds, or -1,
56 * if no command is being edited
57 */
58 static int subcmd = -1;
60 /** The number of keys assigned to the current command */
61 static unsigned subcmd_n_keys = 0;
63 /** The position of the up ("[..]") item */
64 #define subcmd_item_up 0
66 /** The position of the "add a key" item */
67 #define subcmd_item_add (subcmd_n_keys + 1)
69 /** The number of items in the list_window, if there's a command being edited */
70 #define subcmd_length (subcmd_item_add + 1)
72 /** Check whether a given item is a key */
73 #define subcmd_item_is_key(i) \
74 ((i) > subcmd_item_up && (i) < subcmd_item_add)
76 /**
77 * Convert an item id (as in lw->selected) into a "key id", which is an array
78 * subscript to cmds[subcmd].keys.
79 */
80 #define subcmd_item_to_key_id(i) ((i) - 1)
83 static int
84 keybindings_changed(void)
85 {
86 command_definition_t *orginal_cmds = get_command_definitions();
87 size_t size = command_n_commands * sizeof(command_definition_t);
89 return memcmp(orginal_cmds, cmds, size);
90 }
92 static void
93 apply_keys(void)
94 {
95 if (keybindings_changed()) {
96 command_definition_t *orginal_cmds = get_command_definitions();
97 size_t size = command_n_commands * sizeof(command_definition_t);
99 memcpy(orginal_cmds, cmds, size);
100 screen_status_printf(_("You have new key bindings"));
101 } else
102 screen_status_printf(_("Keybindings unchanged."));
103 }
105 static int
106 save_keys(void)
107 {
108 FILE *f;
109 char *filename;
111 if (check_user_conf_dir()) {
112 screen_status_printf(_("Error: Unable to create directory ~/.ncmpc - %s"),
113 strerror(errno));
114 screen_bell();
115 return -1;
116 }
118 filename = get_user_key_binding_filename();
120 if ((f = fopen(filename,"w")) == NULL) {
121 screen_status_printf(_("Error: %s - %s"), filename, strerror(errno));
122 screen_bell();
123 g_free(filename);
124 return -1;
125 }
127 if (write_key_bindings(f, KEYDEF_WRITE_HEADER))
128 screen_status_printf(_("Error: %s - %s"), filename, strerror(errno));
129 else
130 screen_status_printf(_("Wrote %s"), filename);
132 g_free(filename);
133 return fclose(f);
134 }
136 /* TODO: rename to check_n_keys / subcmd_count_keys? */
137 static void
138 check_subcmd_length(void)
139 {
140 unsigned i;
142 /* this loops counts the continous valid keys at the start of the the keys
143 array, so make sure you don't have gaps */
144 for (i = 0; i < MAX_COMMAND_KEYS; i++)
145 if (cmds[subcmd].keys[i] == 0)
146 break;
147 subcmd_n_keys = i;
149 list_window_set_length(lw, subcmd_length);
150 }
152 static void
153 keydef_paint(void);
155 static void
156 keydef_repaint(void)
157 {
158 keydef_paint();
159 wrefresh(lw->w);
160 }
162 /** lw->start the last time switch_to_subcmd_mode() was called */
163 static unsigned saved_start = 0;
165 static void
166 switch_to_subcmd_mode(int cmd)
167 {
168 assert(subcmd == -1);
170 saved_start = lw->start;
172 subcmd = cmd;
173 list_window_reset(lw);
174 check_subcmd_length();
176 keydef_repaint();
177 }
179 static void
180 switch_to_command_mode(void)
181 {
182 assert(subcmd != -1);
184 list_window_set_length(lw, command_length);
185 list_window_set_cursor(lw, subcmd);
186 subcmd = -1;
188 lw->start = saved_start;
190 keydef_repaint();
191 }
193 /**
194 * Delete a key from a given command's definition
195 * @param cmd_index the command
196 * @param key_index the key (see below)
197 */
198 static void
199 delete_key(int cmd_index, int key_index)
200 {
201 /* shift the keys to close the gap that appeared */
202 int i = key_index+1;
203 while (i < MAX_COMMAND_KEYS && cmds[cmd_index].keys[i])
204 cmds[cmd_index].keys[key_index++] = cmds[cmd_index].keys[i++];
206 /* As key_index now holds the index of the last key slot that contained
207 a key, we use it to empty this slot, because this key has been copied
208 to the previous slot in the loop above */
209 cmds[cmd_index].keys[key_index] = 0;
211 cmds[cmd_index].flags |= COMMAND_KEY_MODIFIED;
212 check_subcmd_length();
214 screen_status_printf(_("Deleted"));
216 /* repaint */
217 keydef_repaint();
219 /* update key conflict flags */
220 check_key_bindings(cmds, NULL, 0);
221 }
223 /* assigns a new key to a key slot */
224 static void
225 overwrite_key(int cmd_index, int key_index)
226 {
227 int key;
228 char *buf;
229 command_t cmd;
231 assert(key_index < MAX_COMMAND_KEYS);
233 buf = g_strdup_printf(_("Enter new key for %s: "), cmds[cmd_index].name);
234 key = screen_getch(buf);
235 g_free(buf);
237 if (key==ERR) {
238 screen_status_printf(_("Aborted"));
239 return;
240 }
242 cmd = find_key_command(key, cmds);
243 if (cmd != CMD_NONE) {
244 screen_status_printf(_("Error: key %s is already used for %s"),
245 key2str(key), get_key_command_name(cmd));
246 screen_bell();
247 return;
248 }
250 cmds[cmd_index].keys[key_index] = key;
251 cmds[cmd_index].flags |= COMMAND_KEY_MODIFIED;
253 screen_status_printf(_("Assigned %s to %s"),
254 key2str(key),cmds[cmd_index].name);
255 check_subcmd_length();
257 /* repaint */
258 keydef_repaint();
260 /* update key conflict flags */
261 check_key_bindings(cmds, NULL, 0);
262 }
264 /* assign a new key to a new slot */
265 static void
266 add_key(int cmd_index)
267 {
268 if (subcmd_n_keys < MAX_COMMAND_KEYS)
269 overwrite_key(cmd_index, subcmd_n_keys);
270 }
272 static const char *
273 list_callback(unsigned idx, G_GNUC_UNUSED void *data)
274 {
275 static char buf[256];
277 if (subcmd == -1) {
278 if (idx == command_item_apply)
279 return _("===> Apply key bindings ");
280 if (idx == command_item_save)
281 return _("===> Apply & Save key bindings ");
283 assert(idx < (unsigned) command_n_commands);
285 /*
286 * Format the lines in two aligned columnes for the key name and
287 * the description, like this:
288 *
289 * this-command - do this
290 * that-one - do that
291 */
292 size_t len = strlen(cmds[idx].name);
293 strncpy(buf, cmds[idx].name, sizeof(buf));
295 if (len < get_cmds_max_name_width(cmds))
296 memset(buf + len, ' ', get_cmds_max_name_width(cmds) - len);
298 g_snprintf(buf + get_cmds_max_name_width(cmds),
299 sizeof(buf) - get_cmds_max_name_width(cmds),
300 " - %s", _(cmds[idx].description));
302 return buf;
303 } else {
304 if (idx == subcmd_item_up)
305 return "[..]";
307 if (idx == subcmd_item_add) {
308 g_snprintf(buf, sizeof(buf), "%d. %s",
309 idx, _("Add new key"));
310 return buf;
311 }
313 assert(subcmd_item_is_key(idx));
315 g_snprintf(buf, sizeof(buf),
316 "%d. %-20s (%d) ", idx,
317 key2str(cmds[subcmd].keys[subcmd_item_to_key_id(idx)]),
318 cmds[subcmd].keys[subcmd_item_to_key_id(idx)]);
319 return buf;
320 }
321 }
323 static void
324 keydef_init(WINDOW *w, int cols, int rows)
325 {
326 lw = list_window_init(w, cols, rows);
327 }
329 static void
330 keydef_resize(int cols, int rows)
331 {
332 list_window_resize(lw, cols, rows);
333 }
335 static void
336 keydef_exit(void)
337 {
338 list_window_free(lw);
339 if (cmds)
340 g_free(cmds);
341 cmds = NULL;
342 lw = NULL;
343 }
345 static void
346 keydef_open(G_GNUC_UNUSED struct mpdclient *c)
347 {
348 if (cmds == NULL) {
349 command_definition_t *current_cmds = get_command_definitions();
350 size_t cmds_size;
352 command_n_commands = 0;
353 while (current_cmds[command_n_commands].name)
354 command_n_commands++;
356 /* +1 for the terminator element */
357 cmds_size = (command_n_commands + 1) * sizeof(command_definition_t);
358 cmds = g_malloc0(cmds_size);
359 memcpy(cmds, current_cmds, cmds_size);
360 }
362 subcmd = -1;
363 list_window_set_length(lw, command_length);
364 }
366 static void
367 keydef_close(void)
368 {
369 if (cmds && !keybindings_changed()) {
370 g_free(cmds);
371 cmds = NULL;
372 } else
373 screen_status_printf(_("Note: Did you forget to \'Apply\' your changes?"));
374 }
376 static const char *
377 keydef_title(char *str, size_t size)
378 {
379 if (subcmd == -1)
380 return _("Edit key bindings");
382 g_snprintf(str, size, _("Edit keys for %s"), cmds[subcmd].name);
383 return str;
384 }
386 static void
387 keydef_paint(void)
388 {
389 list_window_paint(lw, list_callback, NULL);
390 }
392 static bool
393 keydef_cmd(G_GNUC_UNUSED struct mpdclient *c, command_t cmd)
394 {
395 if (cmd == CMD_LIST_RANGE_SELECT)
396 return false;
398 if (list_window_cmd(lw, cmd)) {
399 keydef_repaint();
400 return true;
401 }
403 switch(cmd) {
404 case CMD_PLAY:
405 if (subcmd == -1) {
406 if (lw->selected == command_item_apply) {
407 apply_keys();
408 } else if (lw->selected == command_item_save) {
409 apply_keys();
410 save_keys();
411 } else {
412 switch_to_subcmd_mode(lw->selected);
413 }
414 } else {
415 if (lw->selected == subcmd_item_up) {
416 switch_to_command_mode();
417 } else if (lw->selected == subcmd_item_add) {
418 add_key(subcmd);
419 } else {
420 /* just to be sure ;-) */
421 assert(subcmd_item_is_key(lw->selected));
422 overwrite_key(subcmd, subcmd_item_to_key_id(lw->selected));
423 }
424 }
425 return true;
426 case CMD_GO_PARENT_DIRECTORY:
427 case CMD_GO_ROOT_DIRECTORY:
428 if (subcmd != -1)
429 switch_to_command_mode();
430 return true;
431 case CMD_DELETE:
432 if (subcmd != -1 && subcmd_item_is_key(lw->selected))
433 delete_key(subcmd, subcmd_item_to_key_id(lw->selected));
435 return true;
436 case CMD_ADD:
437 if (subcmd != -1)
438 add_key(subcmd);
439 return true;
440 case CMD_SAVE_PLAYLIST:
441 apply_keys();
442 save_keys();
443 return true;
444 case CMD_LIST_FIND:
445 case CMD_LIST_RFIND:
446 case CMD_LIST_FIND_NEXT:
447 case CMD_LIST_RFIND_NEXT:
448 screen_find(lw, cmd, list_callback, NULL);
449 keydef_repaint();
450 return true;
452 default:
453 return false;
454 }
456 /* unreachable */
457 assert(0);
458 return false;
459 }
461 const struct screen_functions screen_keydef = {
462 .init = keydef_init,
463 .exit = keydef_exit,
464 .open = keydef_open,
465 .close = keydef_close,
466 .resize = keydef_resize,
467 .paint = keydef_paint,
468 .cmd = keydef_cmd,
469 .get_title = keydef_title,
470 };