Code

list_window: don't invoke callback out-of-range
[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 void
70 list_window_check_selected(struct list_window *lw)
71 {
72         if (lw->start + lw->rows > lw->length) {
73                 if (lw->length > lw->rows)
74                         lw->start = lw->length - lw->rows;
75                 else
76                         lw->start = 0;
77         }
79         if (lw->selected < lw->start)
80                 lw->selected = lw->start;
82         if (lw->length == 0)
83                 lw->selected = 0;
84         else if (lw->selected >= lw->length)
85                 lw->selected = lw->length - 1;
87         if(lw->range_selection)
88         {
89                 if (lw->length == 0) {
90                         lw->selected_start = 0;
91                         lw->selected_end = 0;
92                         lw->range_base = 0;
93                 } else {
94                         if (lw->selected_start >= lw->length)
95                                 lw->selected_start = lw->length - 1;
96                         if (lw->selected_end >= lw->length)
97                                 lw->selected_end = lw->length - 1;
98                         if (lw->range_base >= lw->length)
99                                 lw->range_base = lw->length - 1;
100                 }
102                 if(lw->range_base > lw->selected_end)
103                           lw->selected_end = lw->selected;
104                 if(lw->range_base < lw->selected_start)
105                           lw->selected_start = lw->selected;
106         }
107         else
108         {
109                 lw->selected_start = lw->selected;
110                 lw->selected_end = lw->selected;
111         }
114 void
115 list_window_set_length(struct list_window *lw, unsigned length)
117         lw->length = length;
119         list_window_check_selected(lw);
122 void
123 list_window_center(struct list_window *lw, unsigned n)
125         if (n > lw->rows / 2)
126                 lw->start = n - lw->rows / 2;
127         else
128                 lw->start = 0;
130         if (lw->start + lw->rows > lw->length) {
131                 if (lw->rows < lw->length)
132                         lw->start = lw->length - lw->rows;
133                 else
134                         lw->start = 0;
135         }
138 void
139 list_window_set_cursor(struct list_window *lw, unsigned i)
141         lw->range_selection = false;
142         lw->selected = i;
143         lw->selected_start = i;
144         lw->selected_end = i;
146         list_window_check_selected(lw);
149 void
150 list_window_move_cursor(struct list_window *lw, unsigned n)
152         lw->selected = n;
153         if(lw->range_selection)
154         {
155                 if(n >= lw->range_base)
156                 {
157                         lw->selected_end = n;
158                         lw->selected_start = lw->range_base;
159                 }
160                 if(n <= lw->range_base)
161                 {
162                         lw->selected_start = n;
163                         lw->selected_end = lw->range_base;
164                 }
165         }
166         else
167         {
168                 lw->selected_start = n;
169                 lw->selected_end = n;
170         }
173 void
174 list_window_fetch_cursor(struct list_window *lw)
176         if (lw->selected < lw->start + options.scroll_offset) {
177                 if (lw->start > 0)
178                         lw->selected = lw->start + options.scroll_offset;
179                 if (lw->range_selection) {
180                         if (lw->selected > lw->range_base) {
181                                 lw->selected_end = lw->selected;
182                                 lw->selected_start = lw->range_base;
183                         } else {
184                                 lw->selected_start = lw->selected;
185                         }
186                 } else {
187                         lw->selected_start = lw->selected;
188                         lw->selected_end = lw->selected;
189                 }
190         } else if (lw->selected > lw->start + lw->rows - 1 - options.scroll_offset) {
191                 if (lw->start + lw->rows < lw->length)
192                         lw->selected = lw->start + lw->rows - 1 - options.scroll_offset;
193                 if (lw->range_selection) {
194                         if (lw->selected < lw->range_base) {
195                                 lw->selected_start = lw->selected;
196                                 lw->selected_end = lw->range_base;
197                         } else {
198                                 lw->selected_end = lw->selected;
199                         }
200                 } else {
201                         lw->selected_start = lw->selected;
202                         lw->selected_end = lw->selected;
203                 }
204         }
207 static void
208 list_window_next(struct list_window *lw)
210         if (lw->selected + 1 < lw->length)
211                 list_window_move_cursor(lw, lw->selected + 1);
212         else if (options.list_wrap)
213                 list_window_move_cursor(lw, 0);
216 static void
217 list_window_previous(struct list_window *lw)
219         if (lw->selected > 0)
220                 list_window_move_cursor(lw, lw->selected - 1);
221         else if (options.list_wrap)
222                 list_window_move_cursor(lw, lw->length - 1);
225 static void
226 list_window_top(struct list_window *lw)
228         if (lw->start == 0)
229                 list_window_move_cursor(lw, lw->start);
230         else
231                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
232                         list_window_move_cursor(lw, lw->start + lw->rows / 2);
233                 else
234                         list_window_move_cursor(lw, lw->start + options.scroll_offset);
237 static void
238 list_window_middle(struct list_window *lw)
240         if (lw->length >= lw->rows)
241                 list_window_move_cursor(lw, lw->start + lw->rows / 2);
242         else
243                 list_window_move_cursor(lw, lw->length / 2);
246 static void
247 list_window_bottom(struct list_window *lw)
249         if (lw->length >= lw->rows)
250                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
251                         list_window_move_cursor(lw, lw->start + lw->rows / 2);
252                 else
253                         if (lw->start + lw->rows == lw->length)
254                                 list_window_move_cursor(lw, lw->length - 1);
255                         else
256                                 list_window_move_cursor(lw, lw->start + lw->rows - 1 - options.scroll_offset);
257         else
258                 list_window_move_cursor(lw, lw->length - 1);
261 static void
262 list_window_first(struct list_window *lw)
264         list_window_move_cursor(lw, 0);
267 static void
268 list_window_last(struct list_window *lw)
270         if (lw->length > 0)
271                 list_window_move_cursor(lw, lw->length - 1);
272         else
273                 list_window_move_cursor(lw, 0);
276 static void
277 list_window_next_page(struct list_window *lw)
279         if (lw->rows < 2)
280                 return;
281         if (lw->selected + lw->rows < lw->length)
282                 list_window_move_cursor(lw, lw->selected + lw->rows - 1);
283         else
284                 list_window_last(lw);
287 static void
288 list_window_previous_page(struct list_window *lw)
290         if (lw->rows < 2)
291                 return;
292         if (lw->selected > lw->rows - 1)
293                 list_window_move_cursor(lw, lw->selected - lw->rows + 1);
294         else
295                 list_window_first(lw);
298 static void
299 list_window_scroll_up(struct list_window *lw, unsigned n)
301         if (lw->start > 0) {
302                 if (n > lw->start)
303                         lw->start = 0;
304                 else
305                         lw->start -= n;
307                 list_window_fetch_cursor(lw);
308         }
311 static void
312 list_window_scroll_down(struct list_window *lw, unsigned n)
314         if (lw->start + lw->rows < lw->length)
315         {
316                 if ( lw->start + lw->rows + n > lw->length - 1)
317                         lw->start = lw->length - lw->rows;
318                 else
319                         lw->start += n;
321                 list_window_fetch_cursor(lw);
322         }
325 static void
326 list_window_paint_row(WINDOW *w, unsigned y, unsigned width,
327                       bool selected, bool highlight,
328                       const char *text, const char *second_column)
330         unsigned text_width = utf8_width(text);
331         unsigned second_column_width;
333 #ifdef NCMPC_MINI
334         second_column = NULL;
335 #endif /* NCMPC_MINI */
337         if (second_column != NULL) {
338                 second_column_width = utf8_width(second_column) + 1;
339                 if (second_column_width < width)
340                         width -= second_column_width;
341                 else
342                         second_column_width = 0;
343         } else
344                 second_column_width = 0;
346         if (highlight)
347                 colors_use(w, COLOR_LIST_BOLD);
348         else
349                 colors_use(w, COLOR_LIST);
351         if (selected)
352                 wattron(w, A_REVERSE);
354         waddstr(w, text);
355         if (options.wide_cursor && text_width < width)
356                 whline(w, ' ', width - text_width);
358         if (second_column_width > 0) {
359                 wmove(w, y, width);
360                 waddch(w, ' ');
361                 waddstr(w, second_column);
362         }
364         if (selected)
365                 wattroff(w, A_REVERSE);
367         if (!options.wide_cursor && text_width < width) {
368                 if (second_column_width == 0)
369                         /* the cursor is at the end of the text; clear
370                            the rest of this row */
371                         wclrtoeol(w);
372                 else
373                         /* there's a second column: clear the space
374                            between the first and the second column */
375                         mvwhline(w, y, text_width, ' ', width - text_width);
376         }
379 void
380 list_window_paint(struct list_window *lw,
381                   list_window_callback_fn_t callback,
382                   void *callback_data)
384         unsigned i;
385         bool show_cursor = !lw->hide_cursor;
386         bool highlight = false;
388         if (show_cursor) {
389                 int start = lw->start;
390                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
391                         // Center if the offset is more than half the screen
392                         start = lw->selected - lw->rows / 2;
393                 else
394                 {
395                         if (lw->selected < lw->start + options.scroll_offset)
396                                 start = lw->selected - options.scroll_offset;
398                         if (lw->selected >= lw->start + lw->rows - options.scroll_offset)
399                         {
400                                 start = lw->selected - lw->rows + 1 + options.scroll_offset;
401                         }
402                 }
404                 if (start + lw->rows > lw->length)
405                         start = lw->length - lw->rows;
407                 if (start < 0 || lw->length == 0)
408                         start = 0;
410                 lw->start = start;
411         }
413         show_cursor = show_cursor &&
414                 (!options.hardware_cursor || lw->range_selection);
416         for (i = 0; i < lw->rows; i++) {
417                 const char *label;
418                 char *second_column = NULL;
419                 highlight = false;
421                 wmove(lw->w, i, 0);
423                 if (lw->start + i >= lw->length) {
424                         wclrtobot(lw->w);
425                         break;
426                 }
428                 label = callback(lw->start + i, &highlight, &second_column, callback_data);
429                 assert(label != NULL);
431                 list_window_paint_row(lw->w, i, lw->cols,
432                                       show_cursor &&
433                                       lw->start + i >= lw->selected_start &&
434                                       lw->start + i <= lw->selected_end,
435                                       highlight,
436                                       label, second_column);
438                 if (second_column != NULL)
439                         g_free(second_column);
440         }
442         if (options.hardware_cursor && lw->selected >= lw->start &&
443             lw->selected < lw->start + lw->rows) {
444                 curs_set(1);
445                 wmove(lw->w, lw->selected - lw->start, 0);
446         }
449 bool
450 list_window_find(struct list_window *lw,
451                  list_window_callback_fn_t callback,
452                  void *callback_data,
453                  const char *str,
454                  bool wrap,
455                  bool bell_on_wrap)
457         bool h;
458         unsigned i = lw->selected + 1;
459         const char *label;
461         do {
462                 while (i < lw->length) {
463                         label = callback(i, &h, NULL, callback_data);
464                         assert(label != NULL);
466                         if (str && label && match_line(label, str)) {
467                                 lw->selected = i;
468                                 if(!lw->range_selection || i > lw->selected_end)
469                                           lw->selected_end = i;
470                                 if(!lw->range_selection || i < lw->selected_start)
471                                           lw->selected_start = i;
472                                 return true;
473                         }
474                         if (wrap && i == lw->selected)
475                                 return false;
476                         i++;
477                 }
478                 if (wrap) {
479                         if (i == 0) /* empty list */
480                                 return 1;
481                         i=0; /* first item */
482                         if (bell_on_wrap) {
483                                 screen_bell();
484                         }
485                 }
486         } while (wrap);
488         return false;
491 bool
492 list_window_rfind(struct list_window *lw,
493                   list_window_callback_fn_t callback,
494                   void *callback_data,
495                   const char *str,
496                   bool wrap,
497                   bool bell_on_wrap)
499         bool h;
500         int i = lw->selected - 1;
501         const char *label;
503         if (lw->length == 0)
504                 return false;
506         do {
507                 while (i >= 0) {
508                         label = callback(i, &h, NULL, callback_data);
509                         assert(label != NULL);
511                         if( str && label && match_line(label, str) ) {
512                                 lw->selected = i;
513                                 if(!lw->range_selection || i > (int)lw->selected_end)
514                                           lw->selected_end = i;
515                                 if(!lw->range_selection || i < (int)lw->selected_start)
516                                           lw->selected_start = i;
517                                 return true;
518                         }
519                         if (wrap && i == (int)lw->selected)
520                                 return false;
521                         i--;
522                 }
523                 if (wrap) {
524                         i = lw->length - 1; /* last item */
525                         if (bell_on_wrap) {
526                                 screen_bell();
527                         }
528                 }
529         } while (wrap);
531         return false;
534 bool
535 list_window_jump(struct list_window *lw,
536                  list_window_callback_fn_t callback,
537                  void *callback_data,
538                  const char *str)
540         bool h;
541         const char *label;
543         for (unsigned i = 0; i < lw->length; ++i) {
544                 label = callback(i, &h, NULL, callback_data);
545                 assert(label != NULL);
547                 if (label[0] == '[')
548                         label++;
549 #ifndef NCMPC_MINI
550                 if (str && label &&
551                                 ((options.jump_prefix_only && g_ascii_strncasecmp(label, str, strlen(str)) == 0) ||
552                                  (!options.jump_prefix_only && match_line(label, str))) )
553 #else
554                 if (str && label && g_ascii_strncasecmp(label, str, strlen(str)) == 0)
555 #endif
556                 {
557                         lw->selected = i;
558                         if(!lw->range_selection || i > lw->selected_end)
559                                 lw->selected_end = i;
560                         if(!lw->range_selection || i < lw->selected_start)
561                                 lw->selected_start = i;
562                         return true;
563                 }
564         }
565         return false;
568 /* perform basic list window commands (movement) */
569 bool
570 list_window_cmd(struct list_window *lw, command_t cmd)
572         switch (cmd) {
573         case CMD_LIST_PREVIOUS:
574                 list_window_previous(lw);
575                 break;
576         case CMD_LIST_NEXT:
577                 list_window_next(lw);
578                 break;
579         case CMD_LIST_TOP:
580                 list_window_top(lw);
581                 break;
582         case CMD_LIST_MIDDLE:
583                 list_window_middle(lw);
584                 break;
585         case CMD_LIST_BOTTOM:
586                 list_window_bottom(lw);
587                 break;
588         case CMD_LIST_FIRST:
589                 list_window_first(lw);
590                 break;
591         case CMD_LIST_LAST:
592                 list_window_last(lw);
593                 break;
594         case CMD_LIST_NEXT_PAGE:
595                 list_window_next_page(lw);
596                 break;
597         case CMD_LIST_PREVIOUS_PAGE:
598                 list_window_previous_page(lw);
599                 break;
600         case CMD_LIST_RANGE_SELECT:
601                 if(lw->range_selection)
602                 {
603                         screen_status_printf(_("Range selection disabled"));
604                         list_window_set_cursor(lw, lw->selected);
605                 }
606                 else
607                 {
608                         screen_status_printf(_("Range selection enabled"));
609                         lw->range_base = lw->selected;
610                         lw->range_selection = true;
611                 }
612                 break;
613         case CMD_LIST_SCROLL_UP_LINE:
614                 list_window_scroll_up(lw, 1);
615                 break;
616         case CMD_LIST_SCROLL_DOWN_LINE:
617                 list_window_scroll_down(lw, 1);
618                 break;
619         case CMD_LIST_SCROLL_UP_HALF:
620                 list_window_scroll_up(lw, (lw->rows - 1) / 2);
621                 break;
622         case CMD_LIST_SCROLL_DOWN_HALF:
623                 list_window_scroll_down(lw, (lw->rows - 1) / 2);
624                 break;
625         default:
626                 return false;
627         }
629         return true;
632 bool
633 list_window_scroll_cmd(struct list_window *lw, command_t cmd)
635         switch (cmd) {
636         case CMD_LIST_SCROLL_UP_LINE:
637         case CMD_LIST_PREVIOUS:
638                 if (lw->start > 0)
639                         lw->start--;
640                 break;
642         case CMD_LIST_SCROLL_DOWN_LINE:
643         case CMD_LIST_NEXT:
644                 if (lw->start + lw->rows < lw->length)
645                         lw->start++;
646                 break;
648         case CMD_LIST_FIRST:
649                 lw->start = 0;
650                 break;
652         case CMD_LIST_LAST:
653                 if (lw->length > lw->rows)
654                         lw->start = lw->length - lw->rows;
655                 else
656                         lw->start = 0;
657                 break;
659         case CMD_LIST_NEXT_PAGE:
660                 lw->start += lw->rows - 1;
661                 if (lw->start + lw->rows > lw->length) {
662                         if (lw->length > lw->rows)
663                                 lw->start = lw->length - lw->rows;
664                         else
665                                 lw->start = 0;
666                 }
667                 break;
669         case CMD_LIST_PREVIOUS_PAGE:
670                 if (lw->start > lw->rows)
671                         lw->start -= lw->rows;
672                 else
673                         lw->start = 0;
674                 break;
676         case CMD_LIST_SCROLL_UP_HALF:
677                 if (lw->start > (lw->rows - 1) / 2)
678                         lw->start -= (lw->rows - 1) / 2;
679                 else
680                         lw->start = 0;
681                 break;
683         case CMD_LIST_SCROLL_DOWN_HALF:
684                 lw->start += (lw->rows - 1) / 2;
685                 if (lw->start + lw->rows > lw->length) {
686                         if (lw->length > lw->rows)
687                                 lw->start = lw->length - lw->rows;
688                         else
689                                 lw->start = 0;
690                 }
691                 break;
693         default:
694                 return false;
695         }
697         return true;
700 #ifdef HAVE_GETMOUSE
701 bool
702 list_window_mouse(struct list_window *lw, unsigned long bstate, int y)
704         assert(lw != NULL);
706         /* if the even occurred above the list window move up */
707         if (y < 0) {
708                 if (bstate & BUTTON3_CLICKED)
709                         list_window_first(lw);
710                 else
711                         list_window_previous_page(lw);
712                 return true;
713         }
715         /* if the even occurred below the list window move down */
716         if ((unsigned)y >= lw->length) {
717                 if (bstate & BUTTON3_CLICKED)
718                         list_window_last(lw);
719                 else
720                         list_window_next_page(lw);
721                 return true;
722         }
724         return false;
726 #endif