1 /*
2 * Copyright (c) 2010, 2011 Ryan Flannery <ryan.flannery@gmail.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include "keybindings.h"
18
19
20 /* This table maps KeyActions to their string representations */
21 typedef struct {
22 KeyAction action;
23 char *name;
24 } KeyActionName;
25
26 const KeyActionName KeyActionNames[] = {
27 { scroll_up, "scroll_up" },
28 { scroll_down, "scroll_down" },
29 { scroll_up_page, "scroll_up_page" },
30 { scroll_down_page, "scroll_down_page" },
31 { scroll_up_halfpage, "scroll_up_halfpage" },
32 { scroll_down_halfpage, "scroll_down_halfpage" },
33 { scroll_up_wholepage, "scroll_up_wholepage" },
34 { scroll_down_wholepage, "scroll_down_wholepage" },
35 { scroll_left, "scroll_left" },
36 { scroll_right, "scroll_right" },
37 { scroll_leftmost, "scroll_leftmost" },
38 { scroll_rightmost, "scroll_rightmost" },
39 { jumpto_screen_top, "jumpto_screen_top" },
40 { jumpto_screen_middle, "jumpto_screen_middle" },
41 { jumpto_screen_bottom, "jumpto_screen_bottom" },
42 { jumpto_line, "jumpto_line" },
43 { jumpto_percent, "jumpto_percent" },
44 { search_forward, "search_forward" },
45 { search_backward, "search_backward" },
46 { find_next_forward, "find_next_forward" },
47 { find_next_backward, "find_next_backward" },
48 { visual, "visual" },
49 { cut, "cut" },
50 { yank, "yank" },
51 { paste_after, "paste_after" },
52 { paste_before, "paste_before" },
53 { undo, "undo" },
54 { redo, "redo" },
55 { go, "go" },
56 { quit, "quit" },
57 { redraw, "redraw" },
58 { command_mode, "command_mode" },
59 { shell, "shell" },
60 { switch_windows, "switch_window" },
61 { show_file_info, "show_file_info" },
62 { load_playlist, "load_playlist" },
63 { media_play, "media_play" },
64 { media_pause, "media_pause" },
65 { media_stop, "media_stop" },
66 { media_next, "media_next" },
67 { media_prev, "media_prev" },
68 { volume_increase, "volume_increase" },
69 { volume_decrease, "volume_decrease" },
70 { seek_forward_seconds, "seek_forward_seconds" },
71 { seek_backward_seconds, "seek_backward_seconds" },
72 { seek_forward_minutes, "seek_forward_minutes" },
73 { seek_backward_minutes, "seek_backward_minutes" },
74 { toggle_forward, "toggle_forward" },
75 { toggle_backward, "toggle_backward" }
76 };
77 const size_t KeyActionNamesSize = sizeof(KeyActionNames) / sizeof(KeyActionName);
78
79
80 /* This table maps KeyActions to their handler-functions (with arguments) */
81 typedef struct {
82 KeyAction action;
83 ActionHandler handler;
84 bool visual;
85 KbaArgs args;
86 } KeyActionHandler;
87
88 #define ARG_NOT_USED { .num = 0 }
89 const KeyActionHandler KeyActionHandlers[] = {
90 { scroll_up, kba_scroll_row, true, { .direction = UP }},
91 { scroll_down, kba_scroll_row, true, { .direction = DOWN }},
92 { scroll_up_page, kba_scroll_page, true, { .direction = UP, .amount = SINGLE }},
93 { scroll_down_page, kba_scroll_page, true, { .direction = DOWN, .amount = SINGLE }},
94 { scroll_up_halfpage, kba_scroll_page, true, { .direction = UP, .amount = HALF }},
95 { scroll_down_halfpage, kba_scroll_page, true, { .direction = DOWN, .amount = HALF }},
96 { scroll_up_wholepage, kba_scroll_page, true, { .direction = UP, .amount = WHOLE }},
97 { scroll_down_wholepage, kba_scroll_page, true, { .direction = DOWN, .amount = WHOLE }},
98 { scroll_left, kba_scroll_col, true, { .direction = LEFT, .amount = SINGLE }},
99 { scroll_right, kba_scroll_col, true, { .direction = RIGHT, .amount = SINGLE }},
100 { scroll_leftmost, kba_scroll_col, true, { .direction = LEFT, .amount = WHOLE }},
101 { scroll_rightmost, kba_scroll_col, true, { .direction = RIGHT, .amount = WHOLE }},
102 { jumpto_screen_top, kba_jumpto_screen, true, { .placement = TOP }},
103 { jumpto_screen_middle, kba_jumpto_screen, true, { .placement = MIDDLE }},
104 { jumpto_screen_bottom, kba_jumpto_screen, true, { .placement = BOTTOM }},
105 { jumpto_line, kba_jumpto_file, true, { .scale = NUMBER, .num = 'G' }},
106 { jumpto_percent, kba_jumpto_file, true, { .scale = PERCENT }},
107 { search_forward, kba_search, true, { .direction = FORWARDS }},
108 { search_backward, kba_search, true, { .direction = BACKWARDS }},
109 { find_next_forward, kba_search_find, true, { .direction = SAME }},
110 { find_next_backward, kba_search_find, true, { .direction = OPPOSITE }},
111 { visual, kba_visual, true, ARG_NOT_USED },
112 { cut, kba_cut, true, ARG_NOT_USED },
113 { yank, kba_yank, true, ARG_NOT_USED },
114 { paste_after, kba_paste, false, { .placement = AFTER }},
115 { paste_before, kba_paste, false, { .placement = BEFORE }},
116 { undo, kba_undo, false, ARG_NOT_USED },
117 { redo, kba_redo, false, ARG_NOT_USED },
118 { go, kba_go, true, ARG_NOT_USED },
119 { quit, kba_quit, false, ARG_NOT_USED },
120 { redraw, kba_redraw, false, ARG_NOT_USED },
121 { command_mode, kba_command_mode, false, ARG_NOT_USED },
122 { shell, kba_shell, false, ARG_NOT_USED },
123 { switch_windows, kba_switch_windows, false, ARG_NOT_USED },
124 { show_file_info, kba_show_file_info, false, ARG_NOT_USED },
125 { load_playlist, kba_load_playlist, false, ARG_NOT_USED },
126 { media_play, kba_play, false, ARG_NOT_USED },
127 { media_pause, kba_pause, false, ARG_NOT_USED },
128 { media_stop, kba_stop, false, ARG_NOT_USED },
129 { media_next, kba_play_next, false, ARG_NOT_USED },
130 { media_prev, kba_play_prev, false, ARG_NOT_USED },
131 { volume_increase, kba_volume, false, { .direction = FORWARDS }},
132 { volume_decrease, kba_volume, false, { .direction = BACKWARDS }},
133 { seek_forward_seconds, kba_seek, false, { .direction = FORWARDS, .scale = SECONDS, .num = 10 }},
134 { seek_backward_seconds, kba_seek, false, { .direction = BACKWARDS, .scale = SECONDS, .num = 10 }},
135 { seek_forward_minutes, kba_seek, false, { .direction = FORWARDS, .scale = MINUTES, .num = 1 }},
136 { seek_backward_minutes, kba_seek, false, { .direction = BACKWARDS, .scale = MINUTES, .num = 1 }},
137 { toggle_forward, kba_toggle, false, { .direction = FORWARDS }},
138 { toggle_backward, kba_toggle, false, { .direction = BACKWARDS }}
139 };
140 const size_t KeyActionHandlersSize = sizeof(KeyActionHandlers) / sizeof(KeyActionHandler);
141
142
143 /* This table contains the default keybindings */
144 typedef struct {
145 KeyCode key;
146 KeyAction action;
147 } KeyBinding;
148
149 #define MY_K_TAB 9
150 #define MY_K_ENTER 13
151 #define MY_K_ESCAPE 27
152 #define kb_CONTROL(x) ((x) - 'a' + 1)
153
154 const KeyBinding DefaultKeyBindings[] = {
155 { 'k', scroll_up },
156 { '-', scroll_up },
157 { KEY_UP, scroll_up },
158 { 'j', scroll_down },
159 { KEY_DOWN, scroll_down },
160 { kb_CONTROL('y'), scroll_up_page },
161 { kb_CONTROL('e'), scroll_down_page },
162 { kb_CONTROL('u'), scroll_up_halfpage },
163 { kb_CONTROL('d'), scroll_down_halfpage },
164 { kb_CONTROL('b'), scroll_up_wholepage },
165 { KEY_PPAGE, scroll_up_wholepage },
166 { kb_CONTROL('f'), scroll_down_wholepage },
167 { KEY_NPAGE, scroll_down_wholepage },
168 { 'h', scroll_left },
169 { KEY_LEFT, scroll_left },
170 { KEY_BACKSPACE, scroll_left },
171 { 'l', scroll_right },
172 { KEY_RIGHT, scroll_right },
173 { ' ', scroll_right },
174 { '^', scroll_leftmost },
175 { '0', scroll_leftmost },
176 { '|', scroll_leftmost },
177 { '$', scroll_rightmost },
178 { 'H', jumpto_screen_top },
179 { 'M', jumpto_screen_middle },
180 { 'L', jumpto_screen_bottom },
181 { 'G', jumpto_line },
182 { '%', jumpto_percent },
183 { '/', search_forward },
184 { '?', search_backward },
185 { 'n', find_next_forward },
186 { 'N', find_next_backward },
187 { 'v', visual },
188 { 'V', visual },
189 { 'd', cut },
190 { 'y', yank },
191 { 'p', paste_after },
192 { 'P', paste_before },
193 { 'u', undo },
194 { kb_CONTROL('r'), redo },
195 { 'g', go },
196 { kb_CONTROL('c'), quit },
197 { kb_CONTROL('/'), quit },
198 { kb_CONTROL('l'), redraw },
199 { ':', command_mode },
200 { '!', shell },
201 { MY_K_TAB, switch_windows },
202 { 'm', show_file_info },
203 { MY_K_ENTER, load_playlist },
204 { MY_K_ENTER, media_play },
205 { 'z', media_pause },
206 { 's', media_stop },
207 { 'f', seek_forward_seconds },
208 { ']', seek_forward_seconds },
209 { 'b', seek_backward_seconds },
210 { '[', seek_backward_seconds },
211 { 'F', seek_forward_minutes },
212 { '}', seek_forward_minutes },
213 { 'B', seek_backward_minutes },
214 { '{', seek_backward_minutes },
215 { '(', media_prev },
216 { ')', media_next },
217 { '<', volume_decrease },
218 { '>', volume_increase },
219 { 't', toggle_forward },
220 { 'T', toggle_backward }
221 };
222 const size_t DefaultKeyBindingsSize = sizeof(DefaultKeyBindings) / sizeof(KeyBinding);
223
224
225 /* Mapping of special keys to their values */
226 typedef struct {
227 char *str;
228 KeyCode code;
229 } SpecialKeyCode;
230 SpecialKeyCode SpecialKeys[] = {
231 { "BACKSPACE", KEY_BACKSPACE },
232 { "ENTER", MY_K_ENTER },
233 { "SPACE", ' ' },
234 { "TAB", MY_K_TAB },
235 { "UP", KEY_UP },
236 { "DOWN", KEY_DOWN },
237 { "LEFT", KEY_LEFT },
238 { "RIGHT", KEY_RIGHT },
239 { "PAGEUP", KEY_PPAGE },
240 { "PAGEDOWN", KEY_NPAGE }
241 };
242 const size_t SpecialKeysSize = sizeof(SpecialKeys) / sizeof(SpecialKeyCode);
243
244
245 /*
246 * This is the actual keybinding table mapping KeyCodes to actions. It is
247 * loaded at initial runtime (kb_init()), and modified thereafter, either via
248 * "bind" commands in the config file or issued during runtime.
249 */
250 #define KEYBINDINGS_CHUNK_SIZE 100
251 KeyBinding *KeyBindings;
252 size_t KeyBindingsSize;
253 size_t KeyBindingsCapacity;
254
255 void
kb_increase_capacity()256 kb_increase_capacity()
257 {
258 KeyBinding *new_buffer;
259 size_t nbytes;
260
261 KeyBindingsCapacity += KEYBINDINGS_CHUNK_SIZE;
262 nbytes = KeyBindingsCapacity * sizeof(KeyBinding);
263 if ((new_buffer = realloc(KeyBindings, nbytes)) == NULL)
264 err(1, "%s: failed to realloc(3) keybindings", __FUNCTION__);
265
266 KeyBindings = new_buffer;
267 }
268
269
270 /****************************************************************************
271 *
272 * Routines for initializing, free'ing, manipulating,
273 * and retrieving keybindings.
274 *
275 ****************************************************************************/
276
277 void
kb_init()278 kb_init()
279 {
280 size_t i;
281
282 /* setup empty buffer */
283 KeyBindingsSize = 0;
284 KeyBindingsCapacity = 0;
285 kb_increase_capacity();
286
287 /* install default bindings */
288 for (i = 0; i < DefaultKeyBindingsSize; i++)
289 kb_bind(DefaultKeyBindings[i].action, DefaultKeyBindings[i].key);
290 }
291
292 void
kb_free()293 kb_free()
294 {
295 free(KeyBindings);
296 KeyBindingsSize = 0;
297 }
298
299 void
kb_bind(KeyAction action,KeyCode key)300 kb_bind(KeyAction action, KeyCode key)
301 {
302 kb_unbind_key(key);
303
304 if (KeyBindingsSize == KeyBindingsCapacity)
305 kb_increase_capacity();
306
307 KeyBindings[KeyBindingsSize].action = action;
308 KeyBindings[KeyBindingsSize].key = key;
309 KeyBindingsSize++;
310 }
311
312 void
kb_unbind_action(KeyAction action)313 kb_unbind_action(KeyAction action)
314 {
315 size_t i, j;
316
317 for (i = 0; i < KeyBindingsSize; i++) {
318 if (KeyBindings[i].action == action) {
319 for (j = i; j < KeyBindingsSize - 1; j++)
320 KeyBindings[j] = KeyBindings[j + 1];
321
322 KeyBindingsSize--;
323 }
324 }
325 }
326
327 void
kb_unbind_key(KeyCode key)328 kb_unbind_key(KeyCode key)
329 {
330 size_t i, j;
331
332 for (i = 0; i < KeyBindingsSize; i++) {
333 if (KeyBindings[i].key == key) {
334 for (j = i; j < KeyBindingsSize - 1; j++)
335 KeyBindings[j] = KeyBindings[j + 1];
336
337 KeyBindingsSize--;
338 }
339 }
340 }
341
342 void
kb_unbind_all()343 kb_unbind_all()
344 {
345 KeyBindingsSize = 0;
346 }
347
348 bool
kb_str2action(const char * s,KeyAction * action)349 kb_str2action(const char *s, KeyAction *action)
350 {
351 size_t i;
352
353 for (i = 0; i < KeyActionNamesSize; i++) {
354 if (strcasecmp(KeyActionNames[i].name, s) == 0) {
355 *action = KeyActionNames[i].action;
356 return true;
357 }
358 }
359
360 return false;
361 }
362
363 KeyCode
kb_str2specialKey(char * s)364 kb_str2specialKey(char *s)
365 {
366 size_t i;
367
368 for (i = 0; i < SpecialKeysSize; i++) {
369 if (strcasecmp(SpecialKeys[i].str, s) == 0)
370 return SpecialKeys[i].code;
371 }
372
373 return -1;
374 }
375
376 KeyCode
kb_str2keycode(char * s)377 kb_str2keycode(char *s)
378 {
379 KeyCode val;
380
381 if (strlen(s) == 1)
382 val = s[0];
383 else
384 val = kb_str2specialKey(s);
385
386 return val;
387 }
388
389 KeyCode
kb_str2keycode2(char * control,char * key)390 kb_str2keycode2(char *control, char *key)
391 {
392 KeyCode val;
393
394 if (strcasecmp(control, "control") != 0)
395 return -1;
396
397 if ((val = kb_str2keycode(key)) > 0)
398 return kb_CONTROL(val);
399 else
400 return -1;
401 }
402
403 bool
kb_execute(KeyCode k)404 kb_execute(KeyCode k)
405 {
406 KeyAction action;
407 size_t i;
408 bool found;
409
410 /* visual mode and ESC pressed ?*/
411 if (visual_mode_start != -1 && k == MY_K_ESCAPE) {
412 visual_mode_start = -1;
413 return true;
414 }
415
416 /* Is the key bound? */
417 found = false;
418 action = -1;
419 for (i = 0; i < KeyBindingsSize; i++) {
420 if (KeyBindings[i].key == k) {
421 action = KeyBindings[i].action;
422 found = true;
423 }
424 }
425
426 if (!found)
427 return false;
428
429 /* Execute theaction handler. */
430 for (i = 0; i < KeyActionHandlersSize; i++) {
431 if (KeyActionHandlers[i].action == action) {
432 if (visual_mode_start == -1 || KeyActionHandlers[i].visual) {
433 ((KeyActionHandlers[i].handler)(KeyActionHandlers[i].args));
434 return true;
435 }
436 }
437 }
438
439 return false;
440 }
441
442 bool
kb_execute_by_name(const char * name)443 kb_execute_by_name(const char *name)
444 {
445 KeyAction a;
446 size_t x;
447
448 if (!kb_str2action(name, &a))
449 return false;
450
451 for(x = 0; x < KeyActionHandlersSize; x++) {
452 if(a == KeyActionHandlers[x].action) {
453 KeyActionHandlers[x].handler(KeyActionHandlers[x].args);
454 return true;
455 }
456 }
457 return false;
458 }
459
460
461 /*****************************************************************************
462 *
463 * Individual Keybinding Handlers
464 *
465 ****************************************************************************/
466
467 KbaArgs
get_dummy_args()468 get_dummy_args()
469 {
470 KbaArgs dummy = { .direction = -1, .scale = -1, .amount = -1,
471 .placement = -1, .num = -1 };
472
473 return dummy;
474 }
475
476 void
kba_scroll_row(KbaArgs a)477 kba_scroll_row(KbaArgs a)
478 {
479 int n = gnum_retrieve();
480
481 /* update current row */
482 switch (a.direction) {
483 case DOWN:
484 ui.active->crow += n;
485 break;
486 case UP:
487 ui.active->crow -= n;
488 break;
489 default:
490 errx(1, "%s: invalid direction", __FUNCTION__);
491 }
492
493 /* handle off-the-edge cases */
494 if (ui.active->crow < 0) {
495 swindow_scroll(ui.active, UP, -1 * ui.active->crow);
496 ui.active->crow = 0;
497 }
498
499 if (ui.active->voffset + ui.active->crow >= ui.active->nrows)
500 ui.active->crow = ui.active->nrows - ui.active->voffset - 1;
501
502 if (ui.active->crow >= ui.active->h) {
503 swindow_scroll(ui.active, DOWN, ui.active->crow - ui.active->h + 1);
504 ui.active->crow = ui.active->h - 1;
505 }
506
507 if (ui.active->crow < 0)
508 ui.active->crow = 0;
509
510 /* redraw */
511 redraw_active();
512 paint_status_bar();
513 }
514
515 void
kba_scroll_page(KbaArgs a)516 kba_scroll_page(KbaArgs a)
517 {
518 bool maintain_row_idx;
519 int voffset_original;
520 int max_row;
521 int diff;
522 int n;
523
524 voffset_original = ui.active->voffset;
525
526 /* determine max row number */
527 if (ui.active->voffset + ui.active->h >= ui.active->nrows)
528 max_row = ui.active->nrows - ui.active->voffset - 1;
529 else
530 max_row = ui.active->h - 1;
531
532 /* determine how many pages to scroll */
533 n = 1;
534 if (gnum_get() > 0) {
535 n = gnum_get();
536 gnum_clear();
537 }
538
539 /* determine how much crow should change */
540 switch (a.amount) {
541 case SINGLE:
542 diff = 1 * n;
543 maintain_row_idx = true;
544 break;
545 case HALF:
546 diff = (ui.active->h / 2) * n;
547 maintain_row_idx = false;
548 break;
549 case WHOLE:
550 diff = ui.active->h * n;
551 maintain_row_idx = false;
552 break;
553 default:
554 errx(1, "scroll_page: invalid amount");
555 }
556 swindow_scroll(ui.active, a.direction, diff);
557
558 /* handle updating the current row */
559 if (maintain_row_idx) {
560
561 if (a.direction == DOWN)
562 ui.active->crow -= diff;
563 else
564 ui.active->crow += diff;
565
566 /* handle off-the-edge cases */
567 if (ui.active->crow < 0)
568 ui.active->crow = 0;
569
570 if (ui.active->voffset + ui.active->crow >= ui.active->nrows)
571 ui.active->crow = ui.active->nrows - ui.active->voffset - 1;
572
573 if (ui.active->crow >= ui.active->h)
574 ui.active->crow = ui.active->h - 1;
575
576 if (ui.active->crow < 0)
577 ui.active->crow = 0;
578
579 } else {
580 /*
581 * We only move current row here if there was a change this time
582 * in the vertical offset
583 */
584
585 if (a.direction == DOWN
586 && ui.active->voffset == voffset_original) {
587 ui.active->crow += diff;
588 if (ui.active->crow > max_row)
589 ui.active->crow = max_row;
590 }
591
592 if (a.direction == UP
593 && ui.active->voffset == 0 && voffset_original == 0) {
594 ui.active->crow -= diff;
595 if (ui.active->crow < 0)
596 ui.active->crow = 0;
597 }
598 }
599
600 redraw_active();
601 paint_status_bar();
602 }
603
604 void
kba_scroll_col(KbaArgs a)605 kba_scroll_col(KbaArgs a)
606 {
607 int maxlen;
608 int maxhoff;
609 int n;
610 int i;
611
612 /* determine how many cols to scroll */
613 n = 1;
614 if (gnum_get() > 0) {
615 n = gnum_get();
616 gnum_clear();
617 }
618
619 /* determine maximum horizontal offset */
620 maxhoff = 0;
621 if (ui.active == ui.playlist) {
622 if (mi_display_getwidth() > ui.active->w)
623 maxhoff = mi_display_getwidth() - ui.active->w;
624 } else {
625 maxlen = 0;
626 for (i = 0; i < mdb.nplaylists; i++) {
627 if ((int) strlen(mdb.playlists[i]->name) > maxlen)
628 maxlen = strlen(mdb.playlists[i]->name);
629 }
630 if (maxlen > ui.active->w)
631 maxhoff = maxlen - ui.active->w;
632 }
633
634 /* scroll */
635 switch (a.amount) {
636 case SINGLE:
637 swindow_scroll(ui.active, a.direction, n);
638 if (ui.active->hoffset > maxhoff)
639 ui.active->hoffset = maxhoff;
640 break;
641 case WHOLE:
642 switch (a.direction) {
643 case LEFT:
644 ui.active->hoffset = 0;
645 break;
646 case RIGHT:
647 ui.active->hoffset = maxhoff;
648 break;
649 default:
650 errx(1, "scroll_col: invalid direction");
651 }
652 break;
653 default:
654 errx(1, "scroll_col: invalid amount");
655 }
656
657 /* redraw */
658 redraw_active();
659 paint_status_bar();
660 }
661
662 void
kba_jumpto_screen(KbaArgs a)663 kba_jumpto_screen(KbaArgs a)
664 {
665 int n;
666 int max_row;
667
668 /* get offset */
669 n = 0;
670 if (gnum_get() > 0) {
671 n = gnum_get();
672 gnum_clear();
673 }
674
675 /* determine max row number */
676 if (ui.active->voffset + ui.active->h >= ui.active->nrows)
677 max_row = ui.active->nrows - ui.active->voffset - 1;
678 else
679 max_row = ui.active->h - 1;
680
681 /* update current row */
682 switch (a.placement) {
683 case TOP:
684 ui.active->crow = n - 1;
685 break;
686 case MIDDLE:
687 ui.active->crow = max_row / 2;
688 break;
689 case BOTTOM:
690 ui.active->crow = max_row - n + 1;
691 break;
692 default:
693 errx(1, "jumpto_page: invalid location");
694 }
695
696 /* sanitize current row */
697 if (ui.active->crow < 0)
698 ui.active->crow = 0;
699
700 if (ui.active->crow >= ui.active->h)
701 ui.active->crow = ui.active->h - 1;
702
703 if (ui.active->crow > max_row)
704 ui.active->crow = max_row;
705
706 /* redraw */
707 redraw_active();
708 paint_status_bar();
709 }
710
711 void
kba_jumpto_file(KbaArgs a)712 kba_jumpto_file(KbaArgs a)
713 {
714 float pct;
715 int n, line, input;
716
717 /* determine line/percent to jump to */
718 n = -1;
719 if (gnum_get() > 0) {
720 n = gnum_get();
721 gnum_clear();
722 }
723
724 /* get line number to jump to */
725 switch (a.scale) {
726 case NUMBER:
727 switch (a.num) {
728 case 'g':
729 /* retrieve second 'g' (or cancel) and determine jump-point */
730 while ((input = getch()) && input != 'g') {
731 if (input != ERR) {
732 ungetch(input);
733 return;
734 }
735 }
736
737 if (n < 0)
738 line = 1;
739 else if (n >= ui.active->nrows)
740 line = ui.active->nrows;
741 else
742 line = n;
743
744 break;
745
746 case 'G':
747 /* determine jump-point */
748 if (n < 0 || n >= ui.active->nrows)
749 line = ui.active->nrows;
750 else
751 line = n;
752
753 break;
754
755 default:
756 errx(1, "jumpto_file: NUMBER type with no num!");
757 }
758
759 break;
760
761 case PERCENT:
762 if (n <= 0 || n > 100)
763 return;
764
765 pct = (float) n / 100;
766 line = pct * (float) ui.active->nrows;
767 break;
768
769 default:
770 errx(1, "jumpto_file: invalid scale");
771 }
772
773 /* jump */
774 if (line <= ui.active->voffset) {
775 swindow_scroll(ui.active, UP, ui.active->voffset - line + 1);
776 ui.active->crow = 0;
777 } else if (line > ui.active->voffset + ui.active->h) {
778 swindow_scroll(ui.active, DOWN, line - (ui.active->voffset + ui.active->h));
779 ui.active->crow = ui.active->h - 1;
780 } else {
781 ui.active->crow = line - ui.active->voffset - 1;
782 }
783
784 /* redraw */
785 redraw_active();
786
787 if (a.num != -1) /* XXX a damn dirty hack for now. see search_find */
788 paint_status_bar();
789 }
790
791 void
kba_go(KbaArgs a UNUSED)792 kba_go(KbaArgs a UNUSED)
793 {
794 /* NOTE: While I intend to support most of vim's g* commands, currently
795 * this only supports 'gg' ... simply because I use it.
796 */
797 KbaArgs args = get_dummy_args();
798
799 args.scale = NUMBER;
800 args.num = 'g';
801 kba_jumpto_file(args);
802 }
803
804 void
kba_search(KbaArgs a)805 kba_search(KbaArgs a)
806 {
807 const char *errmsg = NULL;
808 KbaArgs find_args;
809 char *search_phrase;
810 char *prompt = NULL;
811 char **argv = NULL;
812 int argc = 0;
813 int i;
814
815 /* determine prompt to use */
816 switch (a.direction) {
817 case FORWARDS:
818 prompt = "/";
819 break;
820 case BACKWARDS:
821 prompt = "?";
822 break;
823 default:
824 errx(1, "search: invalid direction");
825 }
826
827 /* get search phrase from user */
828 if (user_getstr(prompt, &search_phrase) != 0) {
829 wclear(ui.command);
830 wrefresh(ui.command);
831 return;
832 }
833
834 /* set the global query description and the search direction */
835 if (str2argv(search_phrase, &argc, &argv, &errmsg) != 0) {
836 free(search_phrase);
837 paint_error("parse error: %s in '%s'", errmsg, search_phrase);
838 return;
839 }
840
841 /* setup query */
842 mi_query_clear();
843 mi_query_setraw(search_phrase);
844 for (i = 0; i < argc; i++)
845 mi_query_add_token(argv[i]);
846
847 search_dir_set(a.direction);
848 argv_free(&argc, &argv);
849 free(search_phrase);
850
851 /* do the search */
852 find_args = get_dummy_args();
853 find_args.direction = SAME;
854 kba_search_find(find_args);
855 }
856
857 void
kba_search_find(KbaArgs a)858 kba_search_find(KbaArgs a)
859 {
860 KbaArgs foo;
861 bool matches;
862 char *msg;
863 int dir;
864 int start_idx;
865 int idx;
866 int c;
867
868 /* determine direction to do the search */
869 switch (a.direction) {
870 case SAME:
871 dir = search_dir_get();
872 break;
873
874 case OPPOSITE:
875 if (search_dir_get() == FORWARDS)
876 dir = BACKWARDS;
877 else
878 dir = FORWARDS;
879 break;
880
881 default:
882 errx(1, "search_find: invalid direction");
883 }
884
885 /* start looking from current row */
886 start_idx = ui.active->voffset + ui.active->crow;
887 msg = NULL;
888 for (c = 1; c < ui.active->nrows + 1; c++) {
889
890 /* get idx of record */
891 if (dir == FORWARDS)
892 idx = start_idx + c;
893 else
894 idx = start_idx - c;
895
896 /* normalize idx */
897 if (idx < 0) {
898 idx = ui.active->nrows + idx;
899 msg = "search hit TOP, continuing at BOTTOM";
900
901 } else if (idx >= ui.active->nrows) {
902 idx %= ui.active->nrows;
903 msg = "search hit BOTTOM, continuing at TOP";
904 }
905
906 /* check if record at idx matches */
907 if (ui.active == ui.library)
908 matches = str_match_query(mdb.playlists[idx]->name);
909 else
910 matches = mi_match(viewing_playlist->files[idx]);
911
912 /* found one, jump to it */
913 if (matches) {
914 if (msg != NULL)
915 paint_message(msg);
916
917 gnum_set(idx + 1);
918 foo = get_dummy_args();
919 foo.scale = NUMBER;
920 foo.num = 'G';
921 kba_jumpto_file(foo);
922 return;
923 }
924 }
925
926 paint_error("Pattern not found: %s", mi_query_getraw());
927 }
928
929
930 void
kba_visual(KbaArgs a UNUSED)931 kba_visual(KbaArgs a UNUSED)
932 {
933 if (ui.active == ui.library) {
934 paint_message("No visual mode in library window. Sorry.");
935 return;
936 }
937
938 if (visual_mode_start == -1)
939 visual_mode_start = ui.active->voffset + ui.active->crow;
940 else
941 visual_mode_start = -1;
942
943 paint_playlist();
944 }
945
946 /*
947 * TODO so much of the logic in cut() is similar with that of yank() above.
948 * combine the two, use an Args parameter to determine if the operation is
949 * a yank or a delete
950 */
951 void
kba_cut(KbaArgs a UNUSED)952 kba_cut(KbaArgs a UNUSED)
953 {
954 playlist *p;
955 char *warning;
956 bool got_target;
957 int start, end;
958 int response;
959 int input;
960 int n;
961
962 if (visual_mode_start != -1) {
963 start = visual_mode_start;
964 end = ui.active->voffset + ui.active->crow;
965 visual_mode_start = -1;
966 if (start > end) {
967 int tmp = end;
968 end = start;
969 start = tmp;
970 }
971 end++;
972 } else {
973 /* determine range */
974 n = 1;
975 if (gnum_get() > 0) {
976 n = gnum_get();
977 gnum_clear();
978 }
979
980 /* get range */
981 got_target = false;
982 start = 0;
983 end = 0;
984 while ((input = getch()) && !got_target) {
985 if (input == ERR)
986 continue;
987
988 switch (input) {
989 case 'd': /* delete next n lines */
990 start = ui.active->voffset + ui.active->crow;
991 end = start + n;
992 got_target = true;
993 break;
994
995 case 'G': /* delete to end of current playlist */
996 start = ui.active->voffset + ui.active->crow;
997 end = ui.active->nrows;
998 got_target = true;
999 break;
1000
1001 default:
1002 ungetch(input);
1003 return;
1004 }
1005 }
1006 }
1007
1008 if (start >= ui.active->nrows) {
1009 paint_message("nothing to delete here!");
1010 return;
1011 }
1012
1013 /* handle deleting of a whole playlist */
1014 if (ui.active == ui.library) {
1015 /* get playlist to delete */
1016 n = ui.active->voffset + ui.active->crow;
1017 p = mdb.playlists[n];
1018
1019 /*
1020 * XXX i simply do NOT want to be able to delete multiple playlists
1021 * while drunk.
1022 */
1023 if (end != start + 1) {
1024 paint_error("cannot delete multiple playlists");
1025 return;
1026 }
1027 if (p == mdb.library || p == mdb.filter_results) {
1028 paint_error("cannot delete pseudo-playlists like LIBRARY or FILTER");
1029 return;
1030 }
1031
1032 asprintf(&warning, "Are you sure you want to delete '%s'?", p->name);
1033 if (warning == NULL)
1034 err(1, "cut: asprintf(3) failed");
1035
1036 /* make sure user wants this */
1037 if (user_get_yesno(warning, &response) != 0) {
1038 paint_message("delete of '%s' cancelled", p->name);
1039 return;
1040 }
1041 if (response != 1) {
1042 paint_message("playlist '%s' not deleted", p->name);
1043 return;
1044 }
1045
1046 /* delete playlist and redraw library window */
1047 if (viewing_playlist == p) {
1048 viewing_playlist = mdb.library;
1049 ui.playlist->nrows = mdb.library->nfiles;
1050 ui.playlist->crow = 0;
1051 ui.playlist->voffset = 0;
1052 ui.playlist->hoffset = 0;
1053 paint_playlist();
1054 }
1055 ui.library->nrows--;
1056 if (ui.library->voffset + ui.library->crow >= ui.library->nrows)
1057 ui.library->crow = ui.library->nrows - ui.library->voffset - 1;
1058
1059 medialib_playlist_remove(n);
1060 paint_library();
1061 free(warning);
1062 return;
1063 }
1064
1065 /* return to handling of deleting files in a playlist... */
1066
1067 /* can't delete from library */
1068 if (viewing_playlist == mdb.library) {
1069 paint_error("cannot delete from library");
1070 return;
1071 }
1072
1073 /* sanitize start and end */
1074 if (end > ui.active->nrows)
1075 end = ui.active->nrows;
1076
1077 /* clear existing yank buffer and add new stuff */
1078 ybuffer_clear();
1079 for (n = start; n < end; n++)
1080 ybuffer_add(viewing_playlist->files[n]);
1081
1082 /* delete files */
1083 playlist_files_remove(viewing_playlist, start, end - start, true);
1084
1085 /* update ui appropriately */
1086 viewing_playlist->needs_saving = true;
1087 ui.active->nrows = viewing_playlist->nfiles;
1088 if (ui.active->voffset + ui.active->crow >= ui.active->nrows)
1089 ui.active->crow = ui.active->nrows - ui.active->voffset - 1;
1090
1091
1092 /* redraw */
1093 paint_playlist();
1094 paint_library();
1095 paint_message("%d fewer files.", end - start);
1096 }
1097
1098 void
kba_yank(KbaArgs a UNUSED)1099 kba_yank(KbaArgs a UNUSED)
1100 {
1101 bool got_target;
1102 int start, end;
1103 int input;
1104 int n;
1105
1106 if (ui.active == ui.library) {
1107 paint_error("cannot yank in library window");
1108 return;
1109 }
1110
1111 if (viewing_playlist->nfiles == 0) {
1112 paint_error("nothing to yank!");
1113 return;
1114 }
1115
1116 if (visual_mode_start != -1) {
1117 start = visual_mode_start;
1118 end = ui.active->voffset + ui.active->crow;
1119 visual_mode_start = -1;
1120 if (start > end) {
1121 int tmp = end;
1122 end = start;
1123 start = tmp;
1124 }
1125 end++;
1126 } else {
1127 /* determine range */
1128 n = 1;
1129 if (gnum_get() > 0) {
1130 n = gnum_get();
1131 gnum_clear();
1132 }
1133 /* get next input from user */
1134 got_target = false;
1135 start = 0;
1136 end = 0;
1137 while ((input = getch()) && !got_target) {
1138 if (input == ERR)
1139 continue;
1140
1141 switch (input) {
1142 case 'y': /* yank next n lines */
1143 start = ui.active->voffset + ui.active->crow;
1144 end = start + n;
1145 got_target = true;
1146 break;
1147
1148 case 'G': /* yank to end of current playlist */
1149 start = ui.active->voffset + ui.active->crow;
1150 end = ui.active->nrows;
1151 got_target = true;
1152 break;
1153
1154 /*
1155 * TODO handle other directions ( j/k/H/L/M/^u/^d/ etc. )
1156 * here. this will be ... tricky.
1157 * might want to re-organize other stuff?
1158 */
1159
1160 default:
1161 ungetch(input);
1162 return;
1163 }
1164 }
1165 }
1166
1167 /* sanitize start and end */
1168 if (end > ui.active->nrows)
1169 end = ui.active->nrows;
1170
1171 /* clear existing yank buffer and add new stuff */
1172 ybuffer_clear();
1173 for (n = start; n < end; n++)
1174 ybuffer_add(viewing_playlist->files[n]);
1175
1176 paint_playlist();
1177 /* notify user # of rows yanked */
1178 paint_message("Yanked %d files.", end - start);
1179 }
1180
1181 void
kba_paste(KbaArgs a)1182 kba_paste(KbaArgs a)
1183 {
1184 playlist *p;
1185 int start = 0;
1186 int i;
1187
1188 if (_yank_buffer.nfiles == 0) {
1189 paint_error("nothing to paste");
1190 return;
1191 }
1192
1193 /* determine the playlist we're pasting into */
1194 if (ui.active == ui.library) {
1195 i = ui.active->voffset + ui.active->crow;
1196 p = mdb.playlists[i];
1197 } else {
1198 p = viewing_playlist;
1199 }
1200
1201 /* can't alter library */
1202 if (p == mdb.library) {
1203 paint_error("Cannot alter %s psuedo-playlist", mdb.library->name);
1204 return;
1205 }
1206
1207 if (ui.active == ui.library) {
1208 /* figure out where to paste into playlist */
1209 switch (a.placement) {
1210 case BEFORE:
1211 start = 0;
1212 break;
1213 case AFTER:
1214 start = p->nfiles;
1215 break;
1216 default:
1217 errx(1, "paste: invalid placement [if]");
1218 }
1219
1220 } else {
1221 /* determine index in playlist to insert new files after */
1222 switch (a.placement) {
1223 case BEFORE:
1224 start = ui.active->voffset + ui.active->crow;
1225 break;
1226 case AFTER:
1227 start = ui.active->voffset + ui.active->crow + 1;
1228 if (start > p->nfiles) start = p->nfiles;
1229 break;
1230 default:
1231 errx(1, "paste: invalid placement [else]");
1232 }
1233 }
1234
1235 /* add files */
1236 playlist_files_add(p, _yank_buffer.files, start, _yank_buffer.nfiles, true);
1237
1238 if (p == viewing_playlist)
1239 ui.playlist->nrows = p->nfiles;
1240
1241 p->needs_saving = true;
1242
1243 /* redraw */
1244 paint_library();
1245 paint_playlist();
1246 if (ui.active == ui.library)
1247 paint_message("Pasted %d files to '%s'", _yank_buffer.nfiles, p->name);
1248 else
1249 paint_message("Pasted %d files.", _yank_buffer.nfiles);
1250 }
1251
1252
1253 void
kba_undo(KbaArgs a UNUSED)1254 kba_undo(KbaArgs a UNUSED)
1255 {
1256 if (ui.active == ui.library) {
1257 paint_message("Cannot undo in library window.");
1258 return;
1259 }
1260
1261 if (playlist_undo(viewing_playlist) != 0)
1262 paint_message("Nothing to undo.");
1263 else
1264 paint_message("Undo successfull.");
1265
1266 /* TODO more informative message like in vim */
1267
1268 ui.playlist->nrows = viewing_playlist->nfiles;
1269 if (ui.playlist->voffset + ui.playlist->crow >= ui.playlist->nrows)
1270 ui.playlist->crow = ui.playlist->nrows - ui.playlist->voffset - 1;
1271
1272 paint_playlist();
1273 }
1274
1275 void
kba_redo(KbaArgs a UNUSED)1276 kba_redo(KbaArgs a UNUSED)
1277 {
1278 if (ui.active == ui.library) {
1279 paint_message("Cannot redo in library window.");
1280 return;
1281 }
1282
1283 if (playlist_redo(viewing_playlist) != 0)
1284 paint_message("Nothing to redo.");
1285 else
1286 paint_message("Redo successfull.");
1287
1288 /* TODO */
1289
1290 ui.playlist->nrows = viewing_playlist->nfiles;
1291 if (ui.playlist->voffset + ui.playlist->crow >= ui.playlist->nrows)
1292 ui.playlist->crow = ui.playlist->nrows - ui.playlist->voffset - 1;
1293
1294 paint_playlist();
1295 }
1296
1297 void
kba_command_mode(KbaArgs a UNUSED)1298 kba_command_mode(KbaArgs a UNUSED)
1299 {
1300 char *cmd;
1301
1302 /* get command from user */
1303 if (user_getstr(":", &cmd) != 0 || strlen(cmd) == 0) {
1304 werase(ui.command);
1305 wrefresh(ui.command);
1306 return;
1307 }
1308
1309 /* check for '!' used for executing external commands */
1310 if (cmd[0] == '!') {
1311 execute_external_command(cmd + 1);
1312 free(cmd);
1313 return;
1314 }
1315
1316 cmd_execute(cmd);
1317 free(cmd);
1318 return;
1319 }
1320
1321 void
kba_shell(KbaArgs a UNUSED)1322 kba_shell(KbaArgs a UNUSED)
1323 {
1324 char *cmd;
1325
1326 /* get command from user */
1327 if (user_getstr("!", &cmd) != 0) {
1328 werase(ui.command);
1329 wrefresh(ui.command);
1330 return;
1331 }
1332
1333 execute_external_command(cmd);
1334 free(cmd);
1335 }
1336
1337 void
kba_quit(KbaArgs a UNUSED)1338 kba_quit(KbaArgs a UNUSED)
1339 {
1340 VSIG_QUIT = 1;
1341 }
1342
1343 void
kba_redraw(KbaArgs a UNUSED)1344 kba_redraw(KbaArgs a UNUSED)
1345 {
1346 ui_clear();
1347 paint_all();
1348 }
1349
1350 void
kba_switch_windows(KbaArgs a UNUSED)1351 kba_switch_windows(KbaArgs a UNUSED)
1352 {
1353 if (ui.active == ui.library) {
1354 ui.active = ui.playlist;
1355 if (ui.lhide)
1356 ui_hide_library();
1357 } else {
1358 ui.active = ui.library;
1359 if (ui.lhide)
1360 ui_unhide_library();
1361 }
1362
1363 paint_all();
1364 paint_status_bar();
1365 }
1366
1367 void
kba_show_file_info(KbaArgs a UNUSED)1368 kba_show_file_info(KbaArgs a UNUSED)
1369 {
1370 int idx;
1371
1372 if (ui.active == ui.library)
1373 return;
1374
1375 if (ui.active->crow >= ui.active->nrows) {
1376 paint_message("no file here");
1377 return;
1378 }
1379
1380 if (showing_file_info)
1381 paint_playlist();
1382 else {
1383 /* get file index and show */
1384 idx = ui.active->voffset + ui.active->crow;
1385 paint_playlist_file_info(viewing_playlist->files[idx]);
1386 }
1387 }
1388
1389 void
kba_load_playlist(KbaArgs a UNUSED)1390 kba_load_playlist(KbaArgs a UNUSED)
1391 {
1392 int idx;
1393
1394 if (ui.active == ui.library) {
1395 /* load playlist & switch focus */
1396 idx = ui.library->voffset + ui.library->crow;
1397 viewing_playlist = mdb.playlists[idx];
1398 ui.playlist->nrows = mdb.playlists[idx]->nfiles;
1399 ui.playlist->crow = 0;
1400 ui.playlist->voffset = 0;
1401 ui.playlist->hoffset = 0;
1402
1403 paint_playlist();
1404 kba_switch_windows(get_dummy_args());
1405 } else {
1406 /* play song */
1407 if (ui.active->crow >= ui.active->nrows) {
1408 paint_message("no file here");
1409 return;
1410 }
1411 player_set_queue(viewing_playlist, ui.active->voffset + ui.active->crow);
1412 playing_playlist = viewing_playlist;
1413 player_play();
1414 }
1415 }
1416
1417 void
kba_play(KbaArgs a UNUSED)1418 kba_play(KbaArgs a UNUSED)
1419 {
1420 int idx;
1421
1422 if (ui.active == ui.library) {
1423 /* load playlist & switch focus */
1424 idx = ui.library->voffset + ui.library->crow;
1425 viewing_playlist = mdb.playlists[idx];
1426 ui.playlist->nrows = mdb.playlists[idx]->nfiles;
1427 ui.playlist->crow = 0;
1428 ui.playlist->voffset = 0;
1429 ui.playlist->hoffset = 0;
1430
1431 paint_playlist();
1432 kba_switch_windows(get_dummy_args());
1433 } else {
1434 /* play song */
1435 if (ui.active->crow >= ui.active->nrows) {
1436 paint_message("no file here");
1437 return;
1438 }
1439 player_set_queue(viewing_playlist, ui.active->voffset + ui.active->crow);
1440 playing_playlist = viewing_playlist;
1441 player_play();
1442 }
1443 }
1444
1445 void
kba_pause(KbaArgs a UNUSED)1446 kba_pause(KbaArgs a UNUSED)
1447 {
1448 player_pause();
1449 }
1450
1451 void
kba_stop(KbaArgs a UNUSED)1452 kba_stop(KbaArgs a UNUSED)
1453 {
1454 player_stop();
1455 playing_playlist = NULL;
1456 }
1457
1458 void
kba_play_next(KbaArgs a UNUSED)1459 kba_play_next(KbaArgs a UNUSED)
1460 {
1461 int n = 1;
1462
1463 /* is there a multiplier? */
1464 if (gnum_get() > 0) {
1465 n = gnum_get();
1466 gnum_clear();
1467 }
1468
1469 player_skip_song(n);
1470 }
1471
1472 void
kba_play_prev(KbaArgs a UNUSED)1473 kba_play_prev(KbaArgs a UNUSED)
1474 {
1475 int n = 1;
1476
1477 /* is there a multiplier? */
1478 if (gnum_get() > 0) {
1479 n = gnum_get();
1480 gnum_clear();
1481 }
1482
1483 player_skip_song(n * -1);
1484 }
1485
1486 void
kba_volume(KbaArgs a)1487 kba_volume(KbaArgs a)
1488 {
1489 float pcnt;
1490
1491 if (gnum_get() > 0)
1492 pcnt = gnum_retrieve();
1493 else
1494 pcnt = 1;
1495
1496 switch (a.direction) {
1497 case FORWARDS:
1498 break;
1499 case BACKWARDS:
1500 pcnt *= -1;
1501 break;
1502 default:
1503 errx(1, "kba_volume: invalid direction");
1504 }
1505
1506 player_volume_step(pcnt);
1507 }
1508
1509 void
kba_seek(KbaArgs a)1510 kba_seek(KbaArgs a)
1511 {
1512 int n, secs;
1513
1514 /* determine number of seconds to seek */
1515 switch (a.scale) {
1516 case SECONDS:
1517 secs = a.num;
1518 break;
1519 case MINUTES:
1520 secs = a.num * 60;
1521 break;
1522 default:
1523 errx(1, "seek_playback: invalid scale");
1524 }
1525
1526 /* adjust for direction */
1527 switch (a.direction) {
1528 case FORWARDS:
1529 /* no change */
1530 break;
1531 case BACKWARDS:
1532 secs *= -1;
1533 break;
1534 default:
1535 errx(1, "seek_playback: invalid direction");
1536 }
1537
1538 /* is there a multiplier? */
1539 n = 1;
1540 if (gnum_get() > 0) {
1541 n = gnum_get();
1542 gnum_clear();
1543 }
1544
1545 /* apply n & seek */
1546 player_seek(secs * n);
1547 }
1548
1549 void
kba_toggle(KbaArgs a)1550 kba_toggle(KbaArgs a)
1551 {
1552 toggle_list *t;
1553 char *cmd;
1554 bool got_register;
1555 int n, input, registr;
1556
1557 /* is there a multiplier? */
1558 n = 1;
1559 if (gnum_get() > 0) {
1560 n = gnum_get();
1561 gnum_clear();
1562 }
1563
1564 /* get the register */
1565 got_register = false;
1566 registr = -1;
1567 while ((input = getch()) && !got_register) {
1568 if (input == ERR)
1569 continue;
1570
1571 if (('a' <= input && input <= 'z')
1572 || ('A' <= input && input <= 'Z')) {
1573 got_register = true;
1574 registr = input;
1575 }
1576 }
1577
1578 /* get the command to execute */
1579 if ((t = toggle_get(registr)) == NULL) {
1580 paint_error("No toggle list in register %c (%i).", registr, registr);
1581 return;
1582 }
1583
1584 /* update index */
1585 n %= t->size;
1586 switch (a.direction) {
1587 case FORWARDS:
1588 t->index += n;
1589 t->index %= t->size;
1590 break;
1591 case BACKWARDS:
1592 if (n <= (int)t->index) {
1593 t->index -= n;
1594 t->index %= t->size;
1595 } else {
1596 t->index = t->size - (n - t->index);
1597 }
1598 break;
1599 default:
1600 errx(1, "%s: invalid direction", __FUNCTION__);
1601 }
1602
1603 /* execute */
1604 cmd = t->commands[t->index];
1605 cmd_execute(cmd);
1606 }
1607
1608
1609 /*****************************************************************************
1610 *
1611 * Routines for Working with 'gnum'
1612 *
1613 ****************************************************************************/
1614
1615 int _global_input_num = 0;
1616
gnum_clear()1617 void gnum_clear()
1618 { _global_input_num = 0; }
1619
gnum_get()1620 int gnum_get()
1621 { return _global_input_num; }
1622
gnum_set(int x)1623 void gnum_set(int x)
1624 { _global_input_num = x; }
1625
gnum_add(int x)1626 void gnum_add(int x)
1627 {
1628 _global_input_num = _global_input_num * 10 + x;
1629 }
1630
1631 int
gnum_retrieve()1632 gnum_retrieve()
1633 {
1634 int n = 1;
1635 if (gnum_get() > 0) {
1636 n = gnum_get();
1637 gnum_clear();
1638 }
1639 return n;
1640 }
1641
1642
1643 /*****************************************************************************
1644 *
1645 * Routines for Working with Search Direction
1646 *
1647 ****************************************************************************/
1648
1649 Direction _global_search_dir = FORWARDS;
1650
search_dir_get()1651 Direction search_dir_get()
1652 { return _global_search_dir; }
1653
search_dir_set(Direction dir)1654 void search_dir_set(Direction dir)
1655 { _global_search_dir = dir; }
1656
1657
1658 /*****************************************************************************
1659 *
1660 * Routines for Working with Copy/Cut/Paste Buffer
1661 *
1662 ****************************************************************************/
1663
1664 yank_buffer _yank_buffer;
1665
1666 void
ybuffer_init()1667 ybuffer_init()
1668 {
1669 _yank_buffer.files = calloc(YANK_BUFFER_CHUNK_SIZE, sizeof(meta_info*));
1670 if (_yank_buffer.files == NULL)
1671 err(1, "ybuffer_init: calloc(3) failed");
1672
1673 _yank_buffer.capacity = YANK_BUFFER_CHUNK_SIZE;
1674 _yank_buffer.nfiles = 0;
1675 }
1676
1677 void
ybuffer_clear()1678 ybuffer_clear()
1679 {
1680 _yank_buffer.nfiles = 0;
1681 }
1682
1683 void
ybuffer_free()1684 ybuffer_free()
1685 {
1686 free(_yank_buffer.files);
1687 _yank_buffer.capacity = 0;
1688 _yank_buffer.nfiles = 0;
1689 }
1690
1691 void
ybuffer_add(meta_info * f)1692 ybuffer_add(meta_info *f)
1693 {
1694 meta_info **new_buff;
1695 int new_capacity;
1696
1697 /* do we need to realloc()? */
1698 if (_yank_buffer.nfiles == _yank_buffer.capacity) {
1699 _yank_buffer.capacity += YANK_BUFFER_CHUNK_SIZE;
1700 new_capacity = _yank_buffer.capacity * sizeof(meta_info*);
1701 if ((new_buff = realloc(_yank_buffer.files, new_capacity)) == NULL)
1702 err(1, "ybuffer_add: realloc(3) failed [%i]", new_capacity);
1703
1704 _yank_buffer.files = new_buff;
1705 }
1706
1707 /* add the file */
1708 _yank_buffer.files[ _yank_buffer.nfiles++ ] = f;
1709 }
1710
1711
1712 /*****************************************************************************
1713 *
1714 * Misc. Handy Routines
1715 *
1716 ****************************************************************************/
1717
1718 void
redraw_active()1719 redraw_active()
1720 {
1721 if (ui.active == ui.library)
1722 paint_library();
1723 else
1724 paint_playlist();
1725 }
1726
1727 /*
1728 * Given string input from user (argv[0]) and a command name, check if the
1729 * input matches the command name, taking into acount '!' weirdness and
1730 * abbreviations.
1731 */
1732 bool
match_command_name(const char * input,const char * cmd)1733 match_command_name(const char *input, const char *cmd)
1734 {
1735 bool found;
1736 char *icopy;
1737
1738 if (input == NULL || strlen(input) == 0)
1739 return false;
1740
1741 if (strcmp(input, cmd) == 0)
1742 return true;
1743
1744 /* check for '!' weirdness and abbreviations */
1745
1746 if ((icopy = strdup(input)) == NULL)
1747 err(1, "match_command_name: strdup(3) failed");
1748
1749 /* remove '!' from input, if present */
1750 if (strstr(icopy, "!") != NULL)
1751 *strstr(icopy, "!") = '\0';
1752
1753 /* now check against command & abbreviation */
1754 if (strstr(cmd, icopy) == cmd)
1755 found = true;
1756 else
1757 found = false;
1758
1759 free(icopy);
1760 return found;
1761 }
1762
1763 void
execute_external_command(const char * cmd)1764 execute_external_command(const char *cmd)
1765 {
1766 int input;
1767
1768 def_prog_mode();
1769 endwin();
1770
1771 system(cmd);
1772 printf("\nPress ENTER or type command to continue");
1773 fflush(stdout);
1774 raw();
1775 while(!VSIG_QUIT) {
1776 if ((input = getch()) && input != ERR) {
1777 if (input != '\r')
1778 ungetch(input);
1779 break;
1780 }
1781 }
1782 reset_prog_mode();
1783 paint_all();
1784 }
1785
1786