Code

list_window: moved checks to list_window_validate_index()
[ncmpc.git] / src / list_window.c
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         }
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)
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;
135 void
136 list_window_resize(struct list_window *lw, unsigned width, unsigned height)
138         lw->cols = width;
139         lw->rows = height;
141         list_window_check_origin(lw);
144 void
145 list_window_set_length(struct list_window *lw, unsigned length)
147         lw->length = length;
149         list_window_check_selected(lw);
150         list_window_check_origin(lw);
153 void
154 list_window_center(struct list_window *lw, unsigned n)
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         }
169 void
170 list_window_set_cursor(struct list_window *lw, unsigned i)
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);
181 void
182 list_window_move_cursor(struct list_window *lw, unsigned n)
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);
208 void
209 list_window_fetch_cursor(struct list_window *lw)
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         }
242 static void
243 list_window_next(struct list_window *lw)
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);
251 static void
252 list_window_previous(struct list_window *lw)
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);
260 static void
261 list_window_top(struct list_window *lw)
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);
272 static void
273 list_window_middle(struct list_window *lw)
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);
281 static void
282 list_window_bottom(struct list_window *lw)
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);
296 static void
297 list_window_first(struct list_window *lw)
299         list_window_move_cursor(lw, 0);
302 static void
303 list_window_last(struct list_window *lw)
305         if (lw->length > 0)
306                 list_window_move_cursor(lw, lw->length - 1);
307         else
308                 list_window_move_cursor(lw, 0);
311 static void
312 list_window_next_page(struct list_window *lw)
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);
322 static void
323 list_window_previous_page(struct list_window *lw)
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);
333 static void
334 list_window_scroll_up(struct list_window *lw, unsigned n)
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         }
346 static void
347 list_window_scroll_down(struct list_window *lw, unsigned n)
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         }
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)
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         }
415 void
416 list_window_paint(const struct list_window *lw,
417                   list_window_callback_fn_t callback,
418                   void *callback_data)
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         }
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)
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;
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)
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;
545 static bool
546 jump_match(const char *haystack, const char *needle)
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);
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)
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;
588 /* perform basic list window commands (movement) */
589 bool
590 list_window_cmd(struct list_window *lw, command_t cmd)
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;
652 bool
653 list_window_scroll_cmd(struct list_window *lw, command_t cmd)
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;
720 #ifdef HAVE_GETMOUSE
721 bool
722 list_window_mouse(struct list_window *lw, unsigned long bstate, int y)
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;
746 #endif