Code

list_window: removed redundant range_base checks
[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);
93         }
94         else
95         {
96                 lw->selected_start = lw->selected;
97                 lw->selected_end = lw->selected;
98         }
99 }
101 /**
102  * Scroll after the cursor was moved, the list was changed or the
103  * window was resized.
104  */
105 static void
106 list_window_check_origin(struct list_window *lw)
108         int start = lw->start;
110         if ((unsigned) options.scroll_offset * 2 >= lw->rows)
111                 // Center if the offset is more than half the screen
112                 start = lw->selected - lw->rows / 2;
113         else {
114                 if (lw->selected < lw->start + options.scroll_offset)
115                         start = lw->selected - options.scroll_offset;
117                 if (lw->selected >= lw->start + lw->rows - options.scroll_offset)
118                         start = lw->selected - lw->rows + 1 + options.scroll_offset;
119         }
121         if (start + lw->rows > lw->length)
122                 start = lw->length - lw->rows;
124         if (start < 0 || lw->length == 0)
125                 start = 0;
127         lw->start = start;
130 void
131 list_window_resize(struct list_window *lw, unsigned width, unsigned height)
133         lw->cols = width;
134         lw->rows = height;
136         list_window_check_origin(lw);
139 void
140 list_window_set_length(struct list_window *lw, unsigned length)
142         lw->length = length;
144         list_window_check_selected(lw);
145         list_window_check_origin(lw);
148 void
149 list_window_center(struct list_window *lw, unsigned n)
151         if (n > lw->rows / 2)
152                 lw->start = n - lw->rows / 2;
153         else
154                 lw->start = 0;
156         if (lw->start + lw->rows > lw->length) {
157                 if (lw->rows < lw->length)
158                         lw->start = lw->length - lw->rows;
159                 else
160                         lw->start = 0;
161         }
164 void
165 list_window_set_cursor(struct list_window *lw, unsigned i)
167         lw->range_selection = false;
168         lw->selected = i;
169         lw->selected_start = i;
170         lw->selected_end = i;
172         list_window_check_selected(lw);
173         list_window_check_origin(lw);
176 void
177 list_window_move_cursor(struct list_window *lw, unsigned n)
179         lw->selected = n;
180         if(lw->range_selection)
181         {
182                 if(n >= lw->range_base)
183                 {
184                         lw->selected_end = n;
185                         lw->selected_start = lw->range_base;
186                 }
187                 if(n <= lw->range_base)
188                 {
189                         lw->selected_start = n;
190                         lw->selected_end = lw->range_base;
191                 }
192         }
193         else
194         {
195                 lw->selected_start = n;
196                 lw->selected_end = n;
197         }
199         list_window_check_selected(lw);
200         list_window_check_origin(lw);
203 void
204 list_window_fetch_cursor(struct list_window *lw)
206         if (lw->selected < lw->start + options.scroll_offset) {
207                 if (lw->start > 0)
208                         lw->selected = lw->start + options.scroll_offset;
209                 if (lw->range_selection) {
210                         if (lw->selected > lw->range_base) {
211                                 lw->selected_end = lw->selected;
212                                 lw->selected_start = lw->range_base;
213                         } else {
214                                 lw->selected_start = lw->selected;
215                         }
216                 } else {
217                         lw->selected_start = lw->selected;
218                         lw->selected_end = lw->selected;
219                 }
220         } else if (lw->selected > lw->start + lw->rows - 1 - options.scroll_offset) {
221                 if (lw->start + lw->rows < lw->length)
222                         lw->selected = lw->start + lw->rows - 1 - options.scroll_offset;
223                 if (lw->range_selection) {
224                         if (lw->selected < lw->range_base) {
225                                 lw->selected_start = lw->selected;
226                                 lw->selected_end = lw->range_base;
227                         } else {
228                                 lw->selected_end = lw->selected;
229                         }
230                 } else {
231                         lw->selected_start = lw->selected;
232                         lw->selected_end = lw->selected;
233                 }
234         }
237 static void
238 list_window_next(struct list_window *lw)
240         if (lw->selected + 1 < lw->length)
241                 list_window_move_cursor(lw, lw->selected + 1);
242         else if (options.list_wrap)
243                 list_window_move_cursor(lw, 0);
246 static void
247 list_window_previous(struct list_window *lw)
249         if (lw->selected > 0)
250                 list_window_move_cursor(lw, lw->selected - 1);
251         else if (options.list_wrap)
252                 list_window_move_cursor(lw, lw->length - 1);
255 static void
256 list_window_top(struct list_window *lw)
258         if (lw->start == 0)
259                 list_window_move_cursor(lw, lw->start);
260         else
261                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
262                         list_window_move_cursor(lw, lw->start + lw->rows / 2);
263                 else
264                         list_window_move_cursor(lw, lw->start + options.scroll_offset);
267 static void
268 list_window_middle(struct list_window *lw)
270         if (lw->length >= lw->rows)
271                 list_window_move_cursor(lw, lw->start + lw->rows / 2);
272         else
273                 list_window_move_cursor(lw, lw->length / 2);
276 static void
277 list_window_bottom(struct list_window *lw)
279         if (lw->length >= lw->rows)
280                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
281                         list_window_move_cursor(lw, lw->start + lw->rows / 2);
282                 else
283                         if (lw->start + lw->rows == lw->length)
284                                 list_window_move_cursor(lw, lw->length - 1);
285                         else
286                                 list_window_move_cursor(lw, lw->start + lw->rows - 1 - options.scroll_offset);
287         else
288                 list_window_move_cursor(lw, lw->length - 1);
291 static void
292 list_window_first(struct list_window *lw)
294         list_window_move_cursor(lw, 0);
297 static void
298 list_window_last(struct list_window *lw)
300         if (lw->length > 0)
301                 list_window_move_cursor(lw, lw->length - 1);
302         else
303                 list_window_move_cursor(lw, 0);
306 static void
307 list_window_next_page(struct list_window *lw)
309         if (lw->rows < 2)
310                 return;
311         if (lw->selected + lw->rows < lw->length)
312                 list_window_move_cursor(lw, lw->selected + lw->rows - 1);
313         else
314                 list_window_last(lw);
317 static void
318 list_window_previous_page(struct list_window *lw)
320         if (lw->rows < 2)
321                 return;
322         if (lw->selected > lw->rows - 1)
323                 list_window_move_cursor(lw, lw->selected - lw->rows + 1);
324         else
325                 list_window_first(lw);
328 static void
329 list_window_scroll_up(struct list_window *lw, unsigned n)
331         if (lw->start > 0) {
332                 if (n > lw->start)
333                         lw->start = 0;
334                 else
335                         lw->start -= n;
337                 list_window_fetch_cursor(lw);
338         }
341 static void
342 list_window_scroll_down(struct list_window *lw, unsigned n)
344         if (lw->start + lw->rows < lw->length)
345         {
346                 if ( lw->start + lw->rows + n > lw->length - 1)
347                         lw->start = lw->length - lw->rows;
348                 else
349                         lw->start += n;
351                 list_window_fetch_cursor(lw);
352         }
355 static void
356 list_window_paint_row(WINDOW *w, unsigned y, unsigned width,
357                       bool selected, bool highlight,
358                       const char *text, const char *second_column)
360         unsigned text_width = utf8_width(text);
361         unsigned second_column_width;
363 #ifdef NCMPC_MINI
364         second_column = NULL;
365         highlight = false;
366 #endif /* NCMPC_MINI */
368         if (second_column != NULL) {
369                 second_column_width = utf8_width(second_column) + 1;
370                 if (second_column_width < width)
371                         width -= second_column_width;
372                 else
373                         second_column_width = 0;
374         } else
375                 second_column_width = 0;
377         if (highlight)
378                 colors_use(w, COLOR_LIST_BOLD);
379         else
380                 colors_use(w, COLOR_LIST);
382         if (selected)
383                 wattron(w, A_REVERSE);
385         waddstr(w, text);
386         if (options.wide_cursor && text_width < width)
387                 whline(w, ' ', width - text_width);
389         if (second_column_width > 0) {
390                 wmove(w, y, width);
391                 waddch(w, ' ');
392                 waddstr(w, second_column);
393         }
395         if (selected)
396                 wattroff(w, A_REVERSE);
398         if (!options.wide_cursor && text_width < width) {
399                 if (second_column_width == 0)
400                         /* the cursor is at the end of the text; clear
401                            the rest of this row */
402                         wclrtoeol(w);
403                 else
404                         /* there's a second column: clear the space
405                            between the first and the second column */
406                         mvwhline(w, y, text_width, ' ', width - text_width);
407         }
410 void
411 list_window_paint(const struct list_window *lw,
412                   list_window_callback_fn_t callback,
413                   void *callback_data)
415         unsigned i;
416         bool show_cursor = !lw->hide_cursor;
418         show_cursor = show_cursor &&
419                 (!options.hardware_cursor || lw->range_selection);
421         for (i = 0; i < lw->rows; i++) {
422                 const char *label;
423                 bool highlight = false;
424                 char *second_column = NULL;
426                 wmove(lw->w, i, 0);
428                 if (lw->start + i >= lw->length) {
429                         wclrtobot(lw->w);
430                         break;
431                 }
433                 label = callback(lw->start + i, &highlight, &second_column, callback_data);
434                 assert(label != NULL);
436 #ifdef NCMPC_MINI
437                 highlight = false;
438                 second_column = NULL;
439 #endif /* NCMPC_MINI */
441                 list_window_paint_row(lw->w, i, lw->cols,
442                                       show_cursor &&
443                                       lw->start + i >= lw->selected_start &&
444                                       lw->start + i <= lw->selected_end,
445                                       highlight,
446                                       label, second_column);
448                 if (second_column != NULL)
449                         g_free(second_column);
450         }
452         if (options.hardware_cursor && lw->selected >= lw->start &&
453             lw->selected < lw->start + lw->rows) {
454                 curs_set(1);
455                 wmove(lw->w, lw->selected - lw->start, 0);
456         }
459 bool
460 list_window_find(struct list_window *lw,
461                  list_window_callback_fn_t callback,
462                  void *callback_data,
463                  const char *str,
464                  bool wrap,
465                  bool bell_on_wrap)
467         bool h;
468         unsigned i = lw->selected + 1;
469         const char *label;
471         assert(str != NULL);
473         do {
474                 while (i < lw->length) {
475                         label = callback(i, &h, NULL, callback_data);
476                         assert(label != NULL);
478                         if (match_line(label, str)) {
479                                 list_window_move_cursor(lw, i);
480                                 return true;
481                         }
482                         if (wrap && i == lw->selected)
483                                 return false;
484                         i++;
485                 }
486                 if (wrap) {
487                         if (i == 0) /* empty list */
488                                 return 1;
489                         i=0; /* first item */
490                         if (bell_on_wrap) {
491                                 screen_bell();
492                         }
493                 }
494         } while (wrap);
496         return false;
499 bool
500 list_window_rfind(struct list_window *lw,
501                   list_window_callback_fn_t callback,
502                   void *callback_data,
503                   const char *str,
504                   bool wrap,
505                   bool bell_on_wrap)
507         bool h;
508         int i = lw->selected - 1;
509         const char *label;
511         assert(str != NULL);
513         if (lw->length == 0)
514                 return false;
516         do {
517                 while (i >= 0) {
518                         label = callback(i, &h, NULL, callback_data);
519                         assert(label != NULL);
521                         if (match_line(label, str)) {
522                                 list_window_move_cursor(lw, i);
523                                 return true;
524                         }
525                         if (wrap && i == (int)lw->selected)
526                                 return false;
527                         i--;
528                 }
529                 if (wrap) {
530                         i = lw->length - 1; /* last item */
531                         if (bell_on_wrap) {
532                                 screen_bell();
533                         }
534                 }
535         } while (wrap);
537         return false;
540 static bool
541 jump_match(const char *haystack, const char *needle)
543 #ifdef NCMPC_MINI
544         bool jump_prefix_only = true;
545 #else
546         bool jump_prefix_only = options.jump_prefix_only;
547 #endif
549         assert(haystack != NULL);
550         assert(needle != NULL);
552         return jump_prefix_only
553                 ? g_ascii_strncasecmp(haystack, needle, strlen(needle)) == 0
554                 : match_line(haystack, needle);
557 bool
558 list_window_jump(struct list_window *lw,
559                  list_window_callback_fn_t callback,
560                  void *callback_data,
561                  const char *str)
563         bool h;
564         const char *label;
566         assert(str != NULL);
568         for (unsigned i = 0; i < lw->length; ++i) {
569                 label = callback(i, &h, NULL, callback_data);
570                 assert(label != NULL);
572                 if (label[0] == '[')
573                         label++;
575                 if (jump_match(label, str)) {
576                         list_window_move_cursor(lw, i);
577                         return true;
578                 }
579         }
580         return false;
583 /* perform basic list window commands (movement) */
584 bool
585 list_window_cmd(struct list_window *lw, command_t cmd)
587         switch (cmd) {
588         case CMD_LIST_PREVIOUS:
589                 list_window_previous(lw);
590                 break;
591         case CMD_LIST_NEXT:
592                 list_window_next(lw);
593                 break;
594         case CMD_LIST_TOP:
595                 list_window_top(lw);
596                 break;
597         case CMD_LIST_MIDDLE:
598                 list_window_middle(lw);
599                 break;
600         case CMD_LIST_BOTTOM:
601                 list_window_bottom(lw);
602                 break;
603         case CMD_LIST_FIRST:
604                 list_window_first(lw);
605                 break;
606         case CMD_LIST_LAST:
607                 list_window_last(lw);
608                 break;
609         case CMD_LIST_NEXT_PAGE:
610                 list_window_next_page(lw);
611                 break;
612         case CMD_LIST_PREVIOUS_PAGE:
613                 list_window_previous_page(lw);
614                 break;
615         case CMD_LIST_RANGE_SELECT:
616                 if(lw->range_selection)
617                 {
618                         screen_status_printf(_("Range selection disabled"));
619                         list_window_set_cursor(lw, lw->selected);
620                 }
621                 else
622                 {
623                         screen_status_printf(_("Range selection enabled"));
624                         lw->range_base = lw->selected;
625                         lw->range_selection = true;
626                 }
627                 break;
628         case CMD_LIST_SCROLL_UP_LINE:
629                 list_window_scroll_up(lw, 1);
630                 break;
631         case CMD_LIST_SCROLL_DOWN_LINE:
632                 list_window_scroll_down(lw, 1);
633                 break;
634         case CMD_LIST_SCROLL_UP_HALF:
635                 list_window_scroll_up(lw, (lw->rows - 1) / 2);
636                 break;
637         case CMD_LIST_SCROLL_DOWN_HALF:
638                 list_window_scroll_down(lw, (lw->rows - 1) / 2);
639                 break;
640         default:
641                 return false;
642         }
644         return true;
647 bool
648 list_window_scroll_cmd(struct list_window *lw, command_t cmd)
650         switch (cmd) {
651         case CMD_LIST_SCROLL_UP_LINE:
652         case CMD_LIST_PREVIOUS:
653                 if (lw->start > 0)
654                         lw->start--;
655                 break;
657         case CMD_LIST_SCROLL_DOWN_LINE:
658         case CMD_LIST_NEXT:
659                 if (lw->start + lw->rows < lw->length)
660                         lw->start++;
661                 break;
663         case CMD_LIST_FIRST:
664                 lw->start = 0;
665                 break;
667         case CMD_LIST_LAST:
668                 if (lw->length > lw->rows)
669                         lw->start = lw->length - lw->rows;
670                 else
671                         lw->start = 0;
672                 break;
674         case CMD_LIST_NEXT_PAGE:
675                 lw->start += lw->rows - 1;
676                 if (lw->start + lw->rows > lw->length) {
677                         if (lw->length > lw->rows)
678                                 lw->start = lw->length - lw->rows;
679                         else
680                                 lw->start = 0;
681                 }
682                 break;
684         case CMD_LIST_PREVIOUS_PAGE:
685                 if (lw->start > lw->rows)
686                         lw->start -= lw->rows;
687                 else
688                         lw->start = 0;
689                 break;
691         case CMD_LIST_SCROLL_UP_HALF:
692                 if (lw->start > (lw->rows - 1) / 2)
693                         lw->start -= (lw->rows - 1) / 2;
694                 else
695                         lw->start = 0;
696                 break;
698         case CMD_LIST_SCROLL_DOWN_HALF:
699                 lw->start += (lw->rows - 1) / 2;
700                 if (lw->start + lw->rows > lw->length) {
701                         if (lw->length > lw->rows)
702                                 lw->start = lw->length - lw->rows;
703                         else
704                                 lw->start = 0;
705                 }
706                 break;
708         default:
709                 return false;
710         }
712         return true;
715 #ifdef HAVE_GETMOUSE
716 bool
717 list_window_mouse(struct list_window *lw, unsigned long bstate, int y)
719         assert(lw != NULL);
721         /* if the even occurred above the list window move up */
722         if (y < 0) {
723                 if (bstate & BUTTON3_CLICKED)
724                         list_window_first(lw);
725                 else
726                         list_window_previous_page(lw);
727                 return true;
728         }
730         /* if the even occurred below the list window move down */
731         if ((unsigned)y >= lw->length) {
732                 if (bstate & BUTTON3_CLICKED)
733                         list_window_last(lw);
734                 else
735                         list_window_next_page(lw);
736                 return true;
737         }
739         return false;
741 #endif