Code

list_window: removed scrolling from list_window_check_selected()
[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->length == 0)
73                 lw->selected = 0;
74         else if (lw->selected >= lw->length)
75                 lw->selected = lw->length - 1;
77         if(lw->range_selection)
78         {
79                 if (lw->length == 0) {
80                         lw->selected_start = 0;
81                         lw->selected_end = 0;
82                         lw->range_base = 0;
83                 } else {
84                         if (lw->selected_start >= lw->length)
85                                 lw->selected_start = lw->length - 1;
86                         if (lw->selected_end >= lw->length)
87                                 lw->selected_end = lw->length - 1;
88                         if (lw->range_base >= lw->length)
89                                 lw->range_base = lw->length - 1;
90                 }
92                 if(lw->range_base > lw->selected_end)
93                           lw->selected_end = lw->selected;
94                 if(lw->range_base < lw->selected_start)
95                           lw->selected_start = lw->selected;
96         }
97         else
98         {
99                 lw->selected_start = lw->selected;
100                 lw->selected_end = lw->selected;
101         }
104 /**
105  * Scroll after the cursor was moved, the list was changed or the
106  * window was resized.
107  */
108 static void
109 list_window_check_origin(struct list_window *lw)
111         int start = lw->start;
113         if ((unsigned) options.scroll_offset * 2 >= lw->rows)
114                 // Center if the offset is more than half the screen
115                 start = lw->selected - lw->rows / 2;
116         else {
117                 if (lw->selected < lw->start + options.scroll_offset)
118                         start = lw->selected - options.scroll_offset;
120                 if (lw->selected >= lw->start + lw->rows - options.scroll_offset)
121                         start = lw->selected - lw->rows + 1 + options.scroll_offset;
122         }
124         if (start + lw->rows > lw->length)
125                 start = lw->length - lw->rows;
127         if (start < 0 || lw->length == 0)
128                 start = 0;
130         lw->start = start;
133 void
134 list_window_resize(struct list_window *lw, unsigned width, unsigned height)
136         lw->cols = width;
137         lw->rows = height;
139         list_window_check_origin(lw);
142 void
143 list_window_set_length(struct list_window *lw, unsigned length)
145         lw->length = length;
147         list_window_check_selected(lw);
148         list_window_check_origin(lw);
151 void
152 list_window_center(struct list_window *lw, unsigned n)
154         if (n > lw->rows / 2)
155                 lw->start = n - lw->rows / 2;
156         else
157                 lw->start = 0;
159         if (lw->start + lw->rows > lw->length) {
160                 if (lw->rows < lw->length)
161                         lw->start = lw->length - lw->rows;
162                 else
163                         lw->start = 0;
164         }
167 void
168 list_window_set_cursor(struct list_window *lw, unsigned i)
170         lw->range_selection = false;
171         lw->selected = i;
172         lw->selected_start = i;
173         lw->selected_end = i;
175         list_window_check_selected(lw);
176         list_window_check_origin(lw);
179 void
180 list_window_move_cursor(struct list_window *lw, unsigned n)
182         lw->selected = n;
183         if(lw->range_selection)
184         {
185                 if(n >= lw->range_base)
186                 {
187                         lw->selected_end = n;
188                         lw->selected_start = lw->range_base;
189                 }
190                 if(n <= lw->range_base)
191                 {
192                         lw->selected_start = n;
193                         lw->selected_end = lw->range_base;
194                 }
195         }
196         else
197         {
198                 lw->selected_start = n;
199                 lw->selected_end = n;
200         }
202         list_window_check_selected(lw);
203         list_window_check_origin(lw);
206 void
207 list_window_fetch_cursor(struct list_window *lw)
209         if (lw->selected < lw->start + options.scroll_offset) {
210                 if (lw->start > 0)
211                         lw->selected = lw->start + options.scroll_offset;
212                 if (lw->range_selection) {
213                         if (lw->selected > lw->range_base) {
214                                 lw->selected_end = lw->selected;
215                                 lw->selected_start = lw->range_base;
216                         } else {
217                                 lw->selected_start = lw->selected;
218                         }
219                 } else {
220                         lw->selected_start = lw->selected;
221                         lw->selected_end = lw->selected;
222                 }
223         } else if (lw->selected > lw->start + lw->rows - 1 - options.scroll_offset) {
224                 if (lw->start + lw->rows < lw->length)
225                         lw->selected = lw->start + lw->rows - 1 - options.scroll_offset;
226                 if (lw->range_selection) {
227                         if (lw->selected < lw->range_base) {
228                                 lw->selected_start = lw->selected;
229                                 lw->selected_end = lw->range_base;
230                         } else {
231                                 lw->selected_end = lw->selected;
232                         }
233                 } else {
234                         lw->selected_start = lw->selected;
235                         lw->selected_end = lw->selected;
236                 }
237         }
240 static void
241 list_window_next(struct list_window *lw)
243         if (lw->selected + 1 < lw->length)
244                 list_window_move_cursor(lw, lw->selected + 1);
245         else if (options.list_wrap)
246                 list_window_move_cursor(lw, 0);
249 static void
250 list_window_previous(struct list_window *lw)
252         if (lw->selected > 0)
253                 list_window_move_cursor(lw, lw->selected - 1);
254         else if (options.list_wrap)
255                 list_window_move_cursor(lw, lw->length - 1);
258 static void
259 list_window_top(struct list_window *lw)
261         if (lw->start == 0)
262                 list_window_move_cursor(lw, lw->start);
263         else
264                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
265                         list_window_move_cursor(lw, lw->start + lw->rows / 2);
266                 else
267                         list_window_move_cursor(lw, lw->start + options.scroll_offset);
270 static void
271 list_window_middle(struct list_window *lw)
273         if (lw->length >= lw->rows)
274                 list_window_move_cursor(lw, lw->start + lw->rows / 2);
275         else
276                 list_window_move_cursor(lw, lw->length / 2);
279 static void
280 list_window_bottom(struct list_window *lw)
282         if (lw->length >= lw->rows)
283                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
284                         list_window_move_cursor(lw, lw->start + lw->rows / 2);
285                 else
286                         if (lw->start + lw->rows == lw->length)
287                                 list_window_move_cursor(lw, lw->length - 1);
288                         else
289                                 list_window_move_cursor(lw, lw->start + lw->rows - 1 - options.scroll_offset);
290         else
291                 list_window_move_cursor(lw, lw->length - 1);
294 static void
295 list_window_first(struct list_window *lw)
297         list_window_move_cursor(lw, 0);
300 static void
301 list_window_last(struct list_window *lw)
303         if (lw->length > 0)
304                 list_window_move_cursor(lw, lw->length - 1);
305         else
306                 list_window_move_cursor(lw, 0);
309 static void
310 list_window_next_page(struct list_window *lw)
312         if (lw->rows < 2)
313                 return;
314         if (lw->selected + lw->rows < lw->length)
315                 list_window_move_cursor(lw, lw->selected + lw->rows - 1);
316         else
317                 list_window_last(lw);
320 static void
321 list_window_previous_page(struct list_window *lw)
323         if (lw->rows < 2)
324                 return;
325         if (lw->selected > lw->rows - 1)
326                 list_window_move_cursor(lw, lw->selected - lw->rows + 1);
327         else
328                 list_window_first(lw);
331 static void
332 list_window_scroll_up(struct list_window *lw, unsigned n)
334         if (lw->start > 0) {
335                 if (n > lw->start)
336                         lw->start = 0;
337                 else
338                         lw->start -= n;
340                 list_window_fetch_cursor(lw);
341         }
344 static void
345 list_window_scroll_down(struct list_window *lw, unsigned n)
347         if (lw->start + lw->rows < lw->length)
348         {
349                 if ( lw->start + lw->rows + n > lw->length - 1)
350                         lw->start = lw->length - lw->rows;
351                 else
352                         lw->start += n;
354                 list_window_fetch_cursor(lw);
355         }
358 static void
359 list_window_paint_row(WINDOW *w, unsigned y, unsigned width,
360                       bool selected, bool highlight,
361                       const char *text, const char *second_column)
363         unsigned text_width = utf8_width(text);
364         unsigned second_column_width;
366 #ifdef NCMPC_MINI
367         second_column = NULL;
368         highlight = false;
369 #endif /* NCMPC_MINI */
371         if (second_column != NULL) {
372                 second_column_width = utf8_width(second_column) + 1;
373                 if (second_column_width < width)
374                         width -= second_column_width;
375                 else
376                         second_column_width = 0;
377         } else
378                 second_column_width = 0;
380         if (highlight)
381                 colors_use(w, COLOR_LIST_BOLD);
382         else
383                 colors_use(w, COLOR_LIST);
385         if (selected)
386                 wattron(w, A_REVERSE);
388         waddstr(w, text);
389         if (options.wide_cursor && text_width < width)
390                 whline(w, ' ', width - text_width);
392         if (second_column_width > 0) {
393                 wmove(w, y, width);
394                 waddch(w, ' ');
395                 waddstr(w, second_column);
396         }
398         if (selected)
399                 wattroff(w, A_REVERSE);
401         if (!options.wide_cursor && text_width < width) {
402                 if (second_column_width == 0)
403                         /* the cursor is at the end of the text; clear
404                            the rest of this row */
405                         wclrtoeol(w);
406                 else
407                         /* there's a second column: clear the space
408                            between the first and the second column */
409                         mvwhline(w, y, text_width, ' ', width - text_width);
410         }
413 void
414 list_window_paint(const struct list_window *lw,
415                   list_window_callback_fn_t callback,
416                   void *callback_data)
418         unsigned i;
419         bool show_cursor = !lw->hide_cursor;
421         show_cursor = show_cursor &&
422                 (!options.hardware_cursor || lw->range_selection);
424         for (i = 0; i < lw->rows; i++) {
425                 const char *label;
426                 bool highlight = false;
427                 char *second_column = NULL;
429                 wmove(lw->w, i, 0);
431                 if (lw->start + i >= lw->length) {
432                         wclrtobot(lw->w);
433                         break;
434                 }
436                 label = callback(lw->start + i, &highlight, &second_column, callback_data);
437                 assert(label != NULL);
439 #ifdef NCMPC_MINI
440                 highlight = false;
441                 second_column = NULL;
442 #endif /* NCMPC_MINI */
444                 list_window_paint_row(lw->w, i, lw->cols,
445                                       show_cursor &&
446                                       lw->start + i >= lw->selected_start &&
447                                       lw->start + i <= lw->selected_end,
448                                       highlight,
449                                       label, second_column);
451                 if (second_column != NULL)
452                         g_free(second_column);
453         }
455         if (options.hardware_cursor && lw->selected >= lw->start &&
456             lw->selected < lw->start + lw->rows) {
457                 curs_set(1);
458                 wmove(lw->w, lw->selected - lw->start, 0);
459         }
462 bool
463 list_window_find(struct list_window *lw,
464                  list_window_callback_fn_t callback,
465                  void *callback_data,
466                  const char *str,
467                  bool wrap,
468                  bool bell_on_wrap)
470         bool h;
471         unsigned i = lw->selected + 1;
472         const char *label;
474         assert(str != NULL);
476         do {
477                 while (i < lw->length) {
478                         label = callback(i, &h, NULL, callback_data);
479                         assert(label != NULL);
481                         if (match_line(label, str)) {
482                                 list_window_move_cursor(lw, i);
483                                 return true;
484                         }
485                         if (wrap && i == lw->selected)
486                                 return false;
487                         i++;
488                 }
489                 if (wrap) {
490                         if (i == 0) /* empty list */
491                                 return 1;
492                         i=0; /* first item */
493                         if (bell_on_wrap) {
494                                 screen_bell();
495                         }
496                 }
497         } while (wrap);
499         return false;
502 bool
503 list_window_rfind(struct list_window *lw,
504                   list_window_callback_fn_t callback,
505                   void *callback_data,
506                   const char *str,
507                   bool wrap,
508                   bool bell_on_wrap)
510         bool h;
511         int i = lw->selected - 1;
512         const char *label;
514         assert(str != NULL);
516         if (lw->length == 0)
517                 return false;
519         do {
520                 while (i >= 0) {
521                         label = callback(i, &h, NULL, callback_data);
522                         assert(label != NULL);
524                         if (match_line(label, str)) {
525                                 list_window_move_cursor(lw, i);
526                                 return true;
527                         }
528                         if (wrap && i == (int)lw->selected)
529                                 return false;
530                         i--;
531                 }
532                 if (wrap) {
533                         i = lw->length - 1; /* last item */
534                         if (bell_on_wrap) {
535                                 screen_bell();
536                         }
537                 }
538         } while (wrap);
540         return false;
543 static bool
544 jump_match(const char *haystack, const char *needle)
546 #ifdef NCMPC_MINI
547         bool jump_prefix_only = true;
548 #else
549         bool jump_prefix_only = options.jump_prefix_only;
550 #endif
552         assert(haystack != NULL);
553         assert(needle != NULL);
555         return jump_prefix_only
556                 ? g_ascii_strncasecmp(haystack, needle, strlen(needle)) == 0
557                 : match_line(haystack, needle);
560 bool
561 list_window_jump(struct list_window *lw,
562                  list_window_callback_fn_t callback,
563                  void *callback_data,
564                  const char *str)
566         bool h;
567         const char *label;
569         assert(str != NULL);
571         for (unsigned i = 0; i < lw->length; ++i) {
572                 label = callback(i, &h, NULL, callback_data);
573                 assert(label != NULL);
575                 if (label[0] == '[')
576                         label++;
578                 if (jump_match(label, str)) {
579                         list_window_move_cursor(lw, i);
580                         return true;
581                 }
582         }
583         return false;
586 /* perform basic list window commands (movement) */
587 bool
588 list_window_cmd(struct list_window *lw, command_t cmd)
590         switch (cmd) {
591         case CMD_LIST_PREVIOUS:
592                 list_window_previous(lw);
593                 break;
594         case CMD_LIST_NEXT:
595                 list_window_next(lw);
596                 break;
597         case CMD_LIST_TOP:
598                 list_window_top(lw);
599                 break;
600         case CMD_LIST_MIDDLE:
601                 list_window_middle(lw);
602                 break;
603         case CMD_LIST_BOTTOM:
604                 list_window_bottom(lw);
605                 break;
606         case CMD_LIST_FIRST:
607                 list_window_first(lw);
608                 break;
609         case CMD_LIST_LAST:
610                 list_window_last(lw);
611                 break;
612         case CMD_LIST_NEXT_PAGE:
613                 list_window_next_page(lw);
614                 break;
615         case CMD_LIST_PREVIOUS_PAGE:
616                 list_window_previous_page(lw);
617                 break;
618         case CMD_LIST_RANGE_SELECT:
619                 if(lw->range_selection)
620                 {
621                         screen_status_printf(_("Range selection disabled"));
622                         list_window_set_cursor(lw, lw->selected);
623                 }
624                 else
625                 {
626                         screen_status_printf(_("Range selection enabled"));
627                         lw->range_base = lw->selected;
628                         lw->range_selection = true;
629                 }
630                 break;
631         case CMD_LIST_SCROLL_UP_LINE:
632                 list_window_scroll_up(lw, 1);
633                 break;
634         case CMD_LIST_SCROLL_DOWN_LINE:
635                 list_window_scroll_down(lw, 1);
636                 break;
637         case CMD_LIST_SCROLL_UP_HALF:
638                 list_window_scroll_up(lw, (lw->rows - 1) / 2);
639                 break;
640         case CMD_LIST_SCROLL_DOWN_HALF:
641                 list_window_scroll_down(lw, (lw->rows - 1) / 2);
642                 break;
643         default:
644                 return false;
645         }
647         return true;
650 bool
651 list_window_scroll_cmd(struct list_window *lw, command_t cmd)
653         switch (cmd) {
654         case CMD_LIST_SCROLL_UP_LINE:
655         case CMD_LIST_PREVIOUS:
656                 if (lw->start > 0)
657                         lw->start--;
658                 break;
660         case CMD_LIST_SCROLL_DOWN_LINE:
661         case CMD_LIST_NEXT:
662                 if (lw->start + lw->rows < lw->length)
663                         lw->start++;
664                 break;
666         case CMD_LIST_FIRST:
667                 lw->start = 0;
668                 break;
670         case CMD_LIST_LAST:
671                 if (lw->length > lw->rows)
672                         lw->start = lw->length - lw->rows;
673                 else
674                         lw->start = 0;
675                 break;
677         case CMD_LIST_NEXT_PAGE:
678                 lw->start += lw->rows - 1;
679                 if (lw->start + lw->rows > lw->length) {
680                         if (lw->length > lw->rows)
681                                 lw->start = lw->length - lw->rows;
682                         else
683                                 lw->start = 0;
684                 }
685                 break;
687         case CMD_LIST_PREVIOUS_PAGE:
688                 if (lw->start > lw->rows)
689                         lw->start -= lw->rows;
690                 else
691                         lw->start = 0;
692                 break;
694         case CMD_LIST_SCROLL_UP_HALF:
695                 if (lw->start > (lw->rows - 1) / 2)
696                         lw->start -= (lw->rows - 1) / 2;
697                 else
698                         lw->start = 0;
699                 break;
701         case CMD_LIST_SCROLL_DOWN_HALF:
702                 lw->start += (lw->rows - 1) / 2;
703                 if (lw->start + lw->rows > lw->length) {
704                         if (lw->length > lw->rows)
705                                 lw->start = lw->length - lw->rows;
706                         else
707                                 lw->start = 0;
708                 }
709                 break;
711         default:
712                 return false;
713         }
715         return true;
718 #ifdef HAVE_GETMOUSE
719 bool
720 list_window_mouse(struct list_window *lw, unsigned long bstate, int y)
722         assert(lw != NULL);
724         /* if the even occurred above the list window move up */
725         if (y < 0) {
726                 if (bstate & BUTTON3_CLICKED)
727                         list_window_first(lw);
728                 else
729                         list_window_previous_page(lw);
730                 return true;
731         }
733         /* if the even occurred below the list window move down */
734         if ((unsigned)y >= lw->length) {
735                 if (bstate & BUTTON3_CLICKED)
736                         list_window_last(lw);
737                 else
738                         list_window_next_page(lw);
739                 return true;
740         }
742         return false;
744 #endif