1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2009 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.
18 */
20 #include "list_window.h"
21 #include "config.h"
22 #include "options.h"
23 #include "charset.h"
24 #include "match.h"
25 #include "command.h"
26 #include "colors.h"
27 #include "screen_message.h"
28 #include "i18n.h"
30 #include <assert.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <string.h>
35 extern void screen_bell(void);
37 struct list_window *
38 list_window_init(WINDOW *w, unsigned width, unsigned height)
39 {
40 struct list_window *lw;
42 lw = g_malloc0(sizeof(*lw));
43 lw->w = w;
44 lw->cols = width;
45 lw->rows = height;
46 lw->range_selection = false;
47 return lw;
48 }
50 void
51 list_window_free(struct list_window *lw)
52 {
53 assert(lw != NULL);
55 g_free(lw);
56 }
58 void
59 list_window_reset(struct list_window *lw)
60 {
61 lw->selected = 0;
62 lw->selected_start = 0;
63 lw->selected_end = 0;
64 lw->range_selection = false;
65 lw->range_base = 0;
66 lw->start = 0;
67 }
69 static unsigned
70 list_window_validate_index(const struct list_window *lw, unsigned i)
71 {
72 if (lw->length == 0)
73 return 0;
74 else if (i >= lw->length)
75 return lw->length - 1;
76 else
77 return i;
78 }
80 static void
81 list_window_check_selected(struct list_window *lw)
82 {
83 lw->selected = list_window_validate_index(lw, lw->selected);
85 if(lw->range_selection)
86 {
87 lw->selected_start =
88 list_window_validate_index(lw, lw->selected_start);
89 lw->selected_end =
90 list_window_validate_index(lw, lw->selected_end);
91 lw->range_base =
92 list_window_validate_index(lw, lw->range_base);
94 if(lw->range_base > lw->selected_end)
95 lw->selected_end = lw->selected;
96 if(lw->range_base < lw->selected_start)
97 lw->selected_start = lw->selected;
98 }
99 else
100 {
101 lw->selected_start = lw->selected;
102 lw->selected_end = lw->selected;
103 }
104 }
106 /**
107 * Scroll after the cursor was moved, the list was changed or the
108 * window was resized.
109 */
110 static void
111 list_window_check_origin(struct list_window *lw)
112 {
113 int start = lw->start;
115 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
116 // Center if the offset is more than half the screen
117 start = lw->selected - lw->rows / 2;
118 else {
119 if (lw->selected < lw->start + options.scroll_offset)
120 start = lw->selected - options.scroll_offset;
122 if (lw->selected >= lw->start + lw->rows - options.scroll_offset)
123 start = lw->selected - lw->rows + 1 + options.scroll_offset;
124 }
126 if (start + lw->rows > lw->length)
127 start = lw->length - lw->rows;
129 if (start < 0 || lw->length == 0)
130 start = 0;
132 lw->start = start;
133 }
135 void
136 list_window_resize(struct list_window *lw, unsigned width, unsigned height)
137 {
138 lw->cols = width;
139 lw->rows = height;
141 list_window_check_origin(lw);
142 }
144 void
145 list_window_set_length(struct list_window *lw, unsigned length)
146 {
147 lw->length = length;
149 list_window_check_selected(lw);
150 list_window_check_origin(lw);
151 }
153 void
154 list_window_center(struct list_window *lw, unsigned n)
155 {
156 if (n > lw->rows / 2)
157 lw->start = n - lw->rows / 2;
158 else
159 lw->start = 0;
161 if (lw->start + lw->rows > lw->length) {
162 if (lw->rows < lw->length)
163 lw->start = lw->length - lw->rows;
164 else
165 lw->start = 0;
166 }
167 }
169 void
170 list_window_set_cursor(struct list_window *lw, unsigned i)
171 {
172 lw->range_selection = false;
173 lw->selected = i;
174 lw->selected_start = i;
175 lw->selected_end = i;
177 list_window_check_selected(lw);
178 list_window_check_origin(lw);
179 }
181 void
182 list_window_move_cursor(struct list_window *lw, unsigned n)
183 {
184 lw->selected = n;
185 if(lw->range_selection)
186 {
187 if(n >= lw->range_base)
188 {
189 lw->selected_end = n;
190 lw->selected_start = lw->range_base;
191 }
192 if(n <= lw->range_base)
193 {
194 lw->selected_start = n;
195 lw->selected_end = lw->range_base;
196 }
197 }
198 else
199 {
200 lw->selected_start = n;
201 lw->selected_end = n;
202 }
204 list_window_check_selected(lw);
205 list_window_check_origin(lw);
206 }
208 void
209 list_window_fetch_cursor(struct list_window *lw)
210 {
211 if (lw->selected < lw->start + options.scroll_offset) {
212 if (lw->start > 0)
213 lw->selected = lw->start + options.scroll_offset;
214 if (lw->range_selection) {
215 if (lw->selected > lw->range_base) {
216 lw->selected_end = lw->selected;
217 lw->selected_start = lw->range_base;
218 } else {
219 lw->selected_start = lw->selected;
220 }
221 } else {
222 lw->selected_start = lw->selected;
223 lw->selected_end = lw->selected;
224 }
225 } else if (lw->selected > lw->start + lw->rows - 1 - options.scroll_offset) {
226 if (lw->start + lw->rows < lw->length)
227 lw->selected = lw->start + lw->rows - 1 - options.scroll_offset;
228 if (lw->range_selection) {
229 if (lw->selected < lw->range_base) {
230 lw->selected_start = lw->selected;
231 lw->selected_end = lw->range_base;
232 } else {
233 lw->selected_end = lw->selected;
234 }
235 } else {
236 lw->selected_start = lw->selected;
237 lw->selected_end = lw->selected;
238 }
239 }
240 }
242 static void
243 list_window_next(struct list_window *lw)
244 {
245 if (lw->selected + 1 < lw->length)
246 list_window_move_cursor(lw, lw->selected + 1);
247 else if (options.list_wrap)
248 list_window_move_cursor(lw, 0);
249 }
251 static void
252 list_window_previous(struct list_window *lw)
253 {
254 if (lw->selected > 0)
255 list_window_move_cursor(lw, lw->selected - 1);
256 else if (options.list_wrap)
257 list_window_move_cursor(lw, lw->length - 1);
258 }
260 static void
261 list_window_top(struct list_window *lw)
262 {
263 if (lw->start == 0)
264 list_window_move_cursor(lw, lw->start);
265 else
266 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
267 list_window_move_cursor(lw, lw->start + lw->rows / 2);
268 else
269 list_window_move_cursor(lw, lw->start + options.scroll_offset);
270 }
272 static void
273 list_window_middle(struct list_window *lw)
274 {
275 if (lw->length >= lw->rows)
276 list_window_move_cursor(lw, lw->start + lw->rows / 2);
277 else
278 list_window_move_cursor(lw, lw->length / 2);
279 }
281 static void
282 list_window_bottom(struct list_window *lw)
283 {
284 if (lw->length >= lw->rows)
285 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
286 list_window_move_cursor(lw, lw->start + lw->rows / 2);
287 else
288 if (lw->start + lw->rows == lw->length)
289 list_window_move_cursor(lw, lw->length - 1);
290 else
291 list_window_move_cursor(lw, lw->start + lw->rows - 1 - options.scroll_offset);
292 else
293 list_window_move_cursor(lw, lw->length - 1);
294 }
296 static void
297 list_window_first(struct list_window *lw)
298 {
299 list_window_move_cursor(lw, 0);
300 }
302 static void
303 list_window_last(struct list_window *lw)
304 {
305 if (lw->length > 0)
306 list_window_move_cursor(lw, lw->length - 1);
307 else
308 list_window_move_cursor(lw, 0);
309 }
311 static void
312 list_window_next_page(struct list_window *lw)
313 {
314 if (lw->rows < 2)
315 return;
316 if (lw->selected + lw->rows < lw->length)
317 list_window_move_cursor(lw, lw->selected + lw->rows - 1);
318 else
319 list_window_last(lw);
320 }
322 static void
323 list_window_previous_page(struct list_window *lw)
324 {
325 if (lw->rows < 2)
326 return;
327 if (lw->selected > lw->rows - 1)
328 list_window_move_cursor(lw, lw->selected - lw->rows + 1);
329 else
330 list_window_first(lw);
331 }
333 static void
334 list_window_scroll_up(struct list_window *lw, unsigned n)
335 {
336 if (lw->start > 0) {
337 if (n > lw->start)
338 lw->start = 0;
339 else
340 lw->start -= n;
342 list_window_fetch_cursor(lw);
343 }
344 }
346 static void
347 list_window_scroll_down(struct list_window *lw, unsigned n)
348 {
349 if (lw->start + lw->rows < lw->length)
350 {
351 if ( lw->start + lw->rows + n > lw->length - 1)
352 lw->start = lw->length - lw->rows;
353 else
354 lw->start += n;
356 list_window_fetch_cursor(lw);
357 }
358 }
360 static void
361 list_window_paint_row(WINDOW *w, unsigned y, unsigned width,
362 bool selected, bool highlight,
363 const char *text, const char *second_column)
364 {
365 unsigned text_width = utf8_width(text);
366 unsigned second_column_width;
368 #ifdef NCMPC_MINI
369 second_column = NULL;
370 highlight = false;
371 #endif /* NCMPC_MINI */
373 if (second_column != NULL) {
374 second_column_width = utf8_width(second_column) + 1;
375 if (second_column_width < width)
376 width -= second_column_width;
377 else
378 second_column_width = 0;
379 } else
380 second_column_width = 0;
382 if (highlight)
383 colors_use(w, COLOR_LIST_BOLD);
384 else
385 colors_use(w, COLOR_LIST);
387 if (selected)
388 wattron(w, A_REVERSE);
390 waddstr(w, text);
391 if (options.wide_cursor && text_width < width)
392 whline(w, ' ', width - text_width);
394 if (second_column_width > 0) {
395 wmove(w, y, width);
396 waddch(w, ' ');
397 waddstr(w, second_column);
398 }
400 if (selected)
401 wattroff(w, A_REVERSE);
403 if (!options.wide_cursor && text_width < width) {
404 if (second_column_width == 0)
405 /* the cursor is at the end of the text; clear
406 the rest of this row */
407 wclrtoeol(w);
408 else
409 /* there's a second column: clear the space
410 between the first and the second column */
411 mvwhline(w, y, text_width, ' ', width - text_width);
412 }
413 }
415 void
416 list_window_paint(const struct list_window *lw,
417 list_window_callback_fn_t callback,
418 void *callback_data)
419 {
420 unsigned i;
421 bool show_cursor = !lw->hide_cursor;
423 show_cursor = show_cursor &&
424 (!options.hardware_cursor || lw->range_selection);
426 for (i = 0; i < lw->rows; i++) {
427 const char *label;
428 bool highlight = false;
429 char *second_column = NULL;
431 wmove(lw->w, i, 0);
433 if (lw->start + i >= lw->length) {
434 wclrtobot(lw->w);
435 break;
436 }
438 label = callback(lw->start + i, &highlight, &second_column, callback_data);
439 assert(label != NULL);
441 #ifdef NCMPC_MINI
442 highlight = false;
443 second_column = NULL;
444 #endif /* NCMPC_MINI */
446 list_window_paint_row(lw->w, i, lw->cols,
447 show_cursor &&
448 lw->start + i >= lw->selected_start &&
449 lw->start + i <= lw->selected_end,
450 highlight,
451 label, second_column);
453 if (second_column != NULL)
454 g_free(second_column);
455 }
457 if (options.hardware_cursor && lw->selected >= lw->start &&
458 lw->selected < lw->start + lw->rows) {
459 curs_set(1);
460 wmove(lw->w, lw->selected - lw->start, 0);
461 }
462 }
464 bool
465 list_window_find(struct list_window *lw,
466 list_window_callback_fn_t callback,
467 void *callback_data,
468 const char *str,
469 bool wrap,
470 bool bell_on_wrap)
471 {
472 bool h;
473 unsigned i = lw->selected + 1;
474 const char *label;
476 assert(str != NULL);
478 do {
479 while (i < lw->length) {
480 label = callback(i, &h, NULL, callback_data);
481 assert(label != NULL);
483 if (match_line(label, str)) {
484 list_window_move_cursor(lw, i);
485 return true;
486 }
487 if (wrap && i == lw->selected)
488 return false;
489 i++;
490 }
491 if (wrap) {
492 if (i == 0) /* empty list */
493 return 1;
494 i=0; /* first item */
495 if (bell_on_wrap) {
496 screen_bell();
497 }
498 }
499 } while (wrap);
501 return false;
502 }
504 bool
505 list_window_rfind(struct list_window *lw,
506 list_window_callback_fn_t callback,
507 void *callback_data,
508 const char *str,
509 bool wrap,
510 bool bell_on_wrap)
511 {
512 bool h;
513 int i = lw->selected - 1;
514 const char *label;
516 assert(str != NULL);
518 if (lw->length == 0)
519 return false;
521 do {
522 while (i >= 0) {
523 label = callback(i, &h, NULL, callback_data);
524 assert(label != NULL);
526 if (match_line(label, str)) {
527 list_window_move_cursor(lw, i);
528 return true;
529 }
530 if (wrap && i == (int)lw->selected)
531 return false;
532 i--;
533 }
534 if (wrap) {
535 i = lw->length - 1; /* last item */
536 if (bell_on_wrap) {
537 screen_bell();
538 }
539 }
540 } while (wrap);
542 return false;
543 }
545 static bool
546 jump_match(const char *haystack, const char *needle)
547 {
548 #ifdef NCMPC_MINI
549 bool jump_prefix_only = true;
550 #else
551 bool jump_prefix_only = options.jump_prefix_only;
552 #endif
554 assert(haystack != NULL);
555 assert(needle != NULL);
557 return jump_prefix_only
558 ? g_ascii_strncasecmp(haystack, needle, strlen(needle)) == 0
559 : match_line(haystack, needle);
560 }
562 bool
563 list_window_jump(struct list_window *lw,
564 list_window_callback_fn_t callback,
565 void *callback_data,
566 const char *str)
567 {
568 bool h;
569 const char *label;
571 assert(str != NULL);
573 for (unsigned i = 0; i < lw->length; ++i) {
574 label = callback(i, &h, NULL, callback_data);
575 assert(label != NULL);
577 if (label[0] == '[')
578 label++;
580 if (jump_match(label, str)) {
581 list_window_move_cursor(lw, i);
582 return true;
583 }
584 }
585 return false;
586 }
588 /* perform basic list window commands (movement) */
589 bool
590 list_window_cmd(struct list_window *lw, command_t cmd)
591 {
592 switch (cmd) {
593 case CMD_LIST_PREVIOUS:
594 list_window_previous(lw);
595 break;
596 case CMD_LIST_NEXT:
597 list_window_next(lw);
598 break;
599 case CMD_LIST_TOP:
600 list_window_top(lw);
601 break;
602 case CMD_LIST_MIDDLE:
603 list_window_middle(lw);
604 break;
605 case CMD_LIST_BOTTOM:
606 list_window_bottom(lw);
607 break;
608 case CMD_LIST_FIRST:
609 list_window_first(lw);
610 break;
611 case CMD_LIST_LAST:
612 list_window_last(lw);
613 break;
614 case CMD_LIST_NEXT_PAGE:
615 list_window_next_page(lw);
616 break;
617 case CMD_LIST_PREVIOUS_PAGE:
618 list_window_previous_page(lw);
619 break;
620 case CMD_LIST_RANGE_SELECT:
621 if(lw->range_selection)
622 {
623 screen_status_printf(_("Range selection disabled"));
624 list_window_set_cursor(lw, lw->selected);
625 }
626 else
627 {
628 screen_status_printf(_("Range selection enabled"));
629 lw->range_base = lw->selected;
630 lw->range_selection = true;
631 }
632 break;
633 case CMD_LIST_SCROLL_UP_LINE:
634 list_window_scroll_up(lw, 1);
635 break;
636 case CMD_LIST_SCROLL_DOWN_LINE:
637 list_window_scroll_down(lw, 1);
638 break;
639 case CMD_LIST_SCROLL_UP_HALF:
640 list_window_scroll_up(lw, (lw->rows - 1) / 2);
641 break;
642 case CMD_LIST_SCROLL_DOWN_HALF:
643 list_window_scroll_down(lw, (lw->rows - 1) / 2);
644 break;
645 default:
646 return false;
647 }
649 return true;
650 }
652 bool
653 list_window_scroll_cmd(struct list_window *lw, command_t cmd)
654 {
655 switch (cmd) {
656 case CMD_LIST_SCROLL_UP_LINE:
657 case CMD_LIST_PREVIOUS:
658 if (lw->start > 0)
659 lw->start--;
660 break;
662 case CMD_LIST_SCROLL_DOWN_LINE:
663 case CMD_LIST_NEXT:
664 if (lw->start + lw->rows < lw->length)
665 lw->start++;
666 break;
668 case CMD_LIST_FIRST:
669 lw->start = 0;
670 break;
672 case CMD_LIST_LAST:
673 if (lw->length > lw->rows)
674 lw->start = lw->length - lw->rows;
675 else
676 lw->start = 0;
677 break;
679 case CMD_LIST_NEXT_PAGE:
680 lw->start += lw->rows - 1;
681 if (lw->start + lw->rows > lw->length) {
682 if (lw->length > lw->rows)
683 lw->start = lw->length - lw->rows;
684 else
685 lw->start = 0;
686 }
687 break;
689 case CMD_LIST_PREVIOUS_PAGE:
690 if (lw->start > lw->rows)
691 lw->start -= lw->rows;
692 else
693 lw->start = 0;
694 break;
696 case CMD_LIST_SCROLL_UP_HALF:
697 if (lw->start > (lw->rows - 1) / 2)
698 lw->start -= (lw->rows - 1) / 2;
699 else
700 lw->start = 0;
701 break;
703 case CMD_LIST_SCROLL_DOWN_HALF:
704 lw->start += (lw->rows - 1) / 2;
705 if (lw->start + lw->rows > lw->length) {
706 if (lw->length > lw->rows)
707 lw->start = lw->length - lw->rows;
708 else
709 lw->start = 0;
710 }
711 break;
713 default:
714 return false;
715 }
717 return true;
718 }
720 #ifdef HAVE_GETMOUSE
721 bool
722 list_window_mouse(struct list_window *lw, unsigned long bstate, int y)
723 {
724 assert(lw != NULL);
726 /* if the even occurred above the list window move up */
727 if (y < 0) {
728 if (bstate & BUTTON3_CLICKED)
729 list_window_first(lw);
730 else
731 list_window_previous_page(lw);
732 return true;
733 }
735 /* if the even occurred below the list window move down */
736 if ((unsigned)y >= lw->length) {
737 if (bstate & BUTTON3_CLICKED)
738 list_window_last(lw);
739 else
740 list_window_next_page(lw);
741 return true;
742 }
744 return false;
745 }
746 #endif