Code

added scroll-offset option; fixed scrolling bug
[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.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(list_window_t));
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         if (lw) {
54                 memset(lw, 0, sizeof(list_window_t));
55                 g_free(lw);
56         }
57 }
59 void
60 list_window_reset(struct list_window *lw)
61 {
62         lw->selected = 0;
63         lw->selected_start = 0;
64         lw->selected_end = 0;
65         lw->range_selection = false;
66         lw->range_base = 0;
67         lw->xoffset = 0;
68         lw->start = 0;
69 }
71 void
72 list_window_check_selected(struct list_window *lw, unsigned length)
73 {
74         if (lw->start + lw->rows > length) {
75                 if (length > lw->rows)
76                         lw->start = length - lw->rows;
77                 else
78                         lw->start = 0;
79         }
81         if (lw->selected < lw->start)
82                 lw->selected = lw->start;
84         if (length == 0)
85                 lw->selected = 0;
86         else if (lw->selected >= length)
87                 lw->selected = length - 1;
89         if(lw->range_selection)
90         {
91                 if(lw->range_base > lw->selected_end)
92                           lw->selected_end = lw->selected;
93                 if(lw->range_base < lw->selected_start)
94                           lw->selected_start = lw->selected;
95         }
96         else
97         {
98                 lw->selected_start = lw->selected;
99                 lw->selected_end = lw->selected;
100         }
103 void
104 list_window_center(struct list_window *lw, unsigned rows, unsigned n)
106         if (n > lw->rows / 2)
107                 lw->start = n - lw->rows / 2;
108         else
109                 lw->start = 0;
111         if (lw->start + lw->rows > rows) {
112                 if (lw->rows < rows)
113                         lw->start = rows - lw->rows;
114                 else
115                         lw->start = 0;
116         }
119 void
120 list_window_set_selected(struct list_window *lw, unsigned n)
122         lw->selected = n;
123         if(lw->range_selection)
124         {
125                 if(n >= lw->range_base)
126                 {
127                         lw->selected_end = n;
128                         lw->selected_start = lw->range_base;
129                 }
130                 if(n <= lw->range_base)
131                 {
132                         lw->selected_start = n;
133                         lw->selected_end = lw->range_base;
134                 }
135         }
136         else
137         {
138                 lw->selected_start = n;
139                 lw->selected_end = n;
140         }
143 static void
144 list_window_next(struct list_window *lw, unsigned length)
146         if (lw->selected + 1 < length)
147                 list_window_set_selected(lw, lw->selected + 1);
148         else if (options.list_wrap)
149                 list_window_set_selected(lw, 0);
152 static void
153 list_window_previous(struct list_window *lw, unsigned length)
155         if (lw->selected > 0)
156                 list_window_set_selected(lw, lw->selected - 1);
157         else if (options.list_wrap)
158                 list_window_set_selected(lw, length-1);
161 static void
162 list_window_top(struct list_window *lw)
164         if (lw->start == 0)
165                 list_window_set_selected(lw, lw->start);
166         else
167                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
168                         list_window_set_selected(lw, lw->start + lw->rows / 2);
169                 else
170                         list_window_set_selected(lw, lw->start + options.scroll_offset);
173 static void
174 list_window_middle(struct list_window *lw, unsigned length)
176         if (length >= lw->rows)
177                 list_window_set_selected(lw, lw->start + lw->rows / 2);
178         else
179                 list_window_set_selected(lw, length / 2);
182 static void
183 list_window_bottom(struct list_window *lw, unsigned length)
185         if (length >= lw->rows)
186                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
187                         list_window_set_selected(lw, lw->start + lw->rows / 2);
188                 else
189                         if (lw->start + lw->rows == length)
190                                 list_window_set_selected(lw, length - 1);
191                         else
192                                 list_window_set_selected(lw, lw->start + lw->rows - 1 - options.scroll_offset);
193         else
194                 list_window_set_selected(lw, length - 1);
197 static void
198 list_window_first(struct list_window *lw)
200         lw->xoffset = 0;
201         list_window_set_selected(lw, 0);
204 static void
205 list_window_last(struct list_window *lw, unsigned length)
207         lw->xoffset = 0;
208         if (length > 0)
209                 list_window_set_selected(lw, length - 1);
210         else
211                 list_window_set_selected(lw, 0);
214 static void
215 list_window_next_page(struct list_window *lw, unsigned length)
217         if (lw->rows < 2)
218                 return;
219         if (lw->selected + lw->rows < length)
220                 list_window_set_selected(lw, lw->selected + lw->rows - 1);
221         else
222                 list_window_last(lw, length);
225 static void
226 list_window_previous_page(struct list_window *lw)
228         if (lw->rows < 2)
229                 return;
230         if (lw->selected > lw->rows - 1)
231                 list_window_set_selected(lw, lw->selected - lw->rows + 1);
232         else
233                 list_window_first(lw);
236 static void
237 list_window_scroll_up(struct list_window *lw, unsigned n)
239         if (lw->start > 0) {
240                 if (n > lw->start)
241                         lw->start = 0;
242                 else
243                         lw->start -= n;
244                 if (lw->selected > lw->start + lw->rows - 1 - options.scroll_offset) {
245                         lw->selected = lw->start + lw->rows - 1 - options.scroll_offset;
246                         if (lw->range_selection) {
247                                 if (lw->selected < lw->range_base) {
248                                         lw->selected_start = lw->selected;
249                                         lw->selected_end = lw->range_base;
250                                 } else {
251                                         lw->selected_end = lw->selected;
252                                 }
253                         } else {
254                                 lw->selected_start = lw->selected;
255                                 lw->selected_end = lw->selected;
256                         }
257                 }
258         }
261 static void
262 list_window_scroll_down(struct list_window *lw, unsigned length, unsigned n)
264         if (lw->start + lw->rows < length)
265         {
266                 if ( lw->start + lw->rows + n > length - 1)
267                         lw->start = length - lw->rows;
268                 else
269                         lw->start += n;
270                 if (lw->selected < lw->start + options.scroll_offset) {
271                         lw->selected = lw->start + options.scroll_offset;
272                         if (lw->range_selection) {
273                                 if (lw->selected > lw->range_base) {
274                                         lw->selected_end = lw->selected;
275                                         lw->selected_start = lw->range_base;
276                                 } else {
277                                         lw->selected_start = lw->selected;
278                                 }
279                         } else {
280                                 lw->selected_start = lw->selected;
281                                 lw->selected_end = lw->selected;
282                         }
283                 }
284         }
287 void
288 list_window_paint(struct list_window *lw,
289                   list_window_callback_fn_t callback,
290                   void *callback_data)
292         unsigned i;
293         bool fill = options.wide_cursor;
294         bool show_cursor = !lw->hide_cursor;
295         bool highlight = false;
297         if (show_cursor) {
298                 int start = lw->start;
299                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
300                         // Center if the offset is more than half the screen
301                         start = lw->selected - lw->rows / 2;
302                 else
303                 {
304                         if (lw->selected < lw->start + options.scroll_offset)
305                                 start = lw->selected - options.scroll_offset;
307                         if (lw->selected >= lw->start + lw->rows - options.scroll_offset)
308                         {
309                                 start = lw->selected - lw->rows + 1 + options.scroll_offset;
310                         }
311                 }
312                 if (start < 0)
313                         lw->start = 0;
314                 else
315                 {
316                         while ( start > 0 && callback(start + lw->rows - 1, &highlight, callback_data) == NULL)
317                                 start--;
318                         lw->start = start;
319                 }
320         }
322         for (i = 0; i < lw->rows; i++) {
323                 const char *label;
324                 highlight = false;
326                 label = callback(lw->start + i, &highlight, callback_data);
327                 wmove(lw->w, i, 0);
329                 if (label) {
330                         bool selected = (lw->start + i >= lw->selected_start && lw->start + i <= lw->selected_end);
331                         unsigned len = utf8_width(label);
333                         if (highlight)
334                                 colors_use(lw->w, COLOR_LIST_BOLD);
335                         else
336                                 colors_use(lw->w, COLOR_LIST);
338                         if (show_cursor && selected)
339                                 wattron(lw->w, A_REVERSE);
341                         //waddnstr(lw->w, label, lw->cols);
342                         waddstr(lw->w, label);
343                         if (fill && len < lw->cols)
344                                 whline(lw->w,  ' ', lw->cols-len);
346                         if (selected)
347                                 wattroff(lw->w, A_REVERSE);
349                         if (!fill && len < lw->cols)
350                                 wclrtoeol(lw->w);
351                 } else
352                         wclrtoeol(lw->w);
353         }
356 bool
357 list_window_find(struct list_window *lw,
358                  list_window_callback_fn_t callback,
359                  void *callback_data,
360                  const char *str,
361                  bool wrap,
362                  bool bell_on_wrap)
364         bool h;
365         unsigned i = lw->selected + 1;
366         const char *label;
368         do {
369                 while ((label = callback(i,&h,callback_data))) {
370                         if (str && label && match_line(label, str)) {
371                                 lw->selected = i;
372                                 if(!lw->range_selection || i > lw->selected_end)
373                                           lw->selected_end = i;
374                                 if(!lw->range_selection || i < lw->selected_start)
375                                           lw->selected_start = i;
376                                 return true;
377                         }
378                         if (wrap && i == lw->selected)
379                                 return false;
380                         i++;
381                 }
382                 if (wrap) {
383                         if (i == 0) /* empty list */
384                                 return 1;
385                         i=0; /* first item */
386                         if (bell_on_wrap) {
387                                 screen_bell();
388                         }
389                 }
390         } while (wrap);
392         return false;
395 bool
396 list_window_rfind(struct list_window *lw,
397                   list_window_callback_fn_t callback,
398                   void *callback_data,
399                   const char *str,
400                   bool wrap,
401                   bool bell_on_wrap,
402                   unsigned rows)
404         bool h;
405         int i = lw->selected - 1;
406         const char *label;
408         if (rows == 0)
409                 return false;
411         do {
412                 while (i >= 0 && (label = callback(i,&h,callback_data))) {
413                         if( str && label && match_line(label, str) ) {
414                                 lw->selected = i;
415                                 if(!lw->range_selection || i > (int)lw->selected_end)
416                                           lw->selected_end = i;
417                                 if(!lw->range_selection || i < (int)lw->selected_start)
418                                           lw->selected_start = i;
419                                 return true;
420                         }
421                         if (wrap && i == (int)lw->selected)
422                                 return false;
423                         i--;
424                 }
425                 if (wrap) {
426                         i = rows - 1; /* last item */
427                         if (bell_on_wrap) {
428                                 screen_bell();
429                         }
430                 }
431         } while (wrap);
433         return false;
436 bool
437 list_window_jump(struct list_window *lw,
438                  list_window_callback_fn_t callback,
439                  void *callback_data,
440                  const char *str)
442         bool h;
443         unsigned i = 0;
444         const char *label;
446         while ((label = callback(i,&h,callback_data))) {
447                 if (label && label[0] == '[')
448                         label++;
449 #ifndef NCMPC_MINI
450                 if (str && label &&
451                                 ((options.jump_prefix_only && g_ascii_strncasecmp(label, str, strlen(str)) == 0) ||
452                                  (!options.jump_prefix_only && match_line(label, str))) )
453 #else
454                 if (str && label && g_ascii_strncasecmp(label, str, strlen(str)) == 0)
455 #endif
456                 {
457                         lw->selected = i;
458                         if(!lw->range_selection || i > lw->selected_end)
459                                 lw->selected_end = i;
460                         if(!lw->range_selection || i < lw->selected_start)
461                                 lw->selected_start = i;
462                         return true;
463                 }
464                 i++;
465         }
466         return false;
469 /* perform basic list window commands (movement) */
470 bool
471 list_window_cmd(struct list_window *lw, unsigned rows, command_t cmd)
473         switch (cmd) {
474         case CMD_LIST_PREVIOUS:
475                 list_window_previous(lw, rows);
476                 break;
477         case CMD_LIST_NEXT:
478                 list_window_next(lw, rows);
479                 break;
480         case CMD_LIST_TOP:
481                 list_window_top(lw);
482                 break;
483         case CMD_LIST_MIDDLE:
484                 list_window_middle(lw,rows);
485                 break;
486         case CMD_LIST_BOTTOM:
487                 list_window_bottom(lw,rows);
488                 break;
489         case CMD_LIST_FIRST:
490                 list_window_first(lw);
491                 break;
492         case CMD_LIST_LAST:
493                 list_window_last(lw, rows);
494                 break;
495         case CMD_LIST_NEXT_PAGE:
496                 list_window_next_page(lw, rows);
497                 break;
498         case CMD_LIST_PREVIOUS_PAGE:
499                 list_window_previous_page(lw);
500                 break;
501         case CMD_LIST_RANGE_SELECT:
502                 if(lw->range_selection)
503                 {
504                         screen_status_printf(_("Range selection disabled"));
505                         lw->range_selection = false;
506                         list_window_set_selected(lw, lw->selected);
507                 }
508                 else
509                 {
510                         screen_status_printf(_("Range selection enabled"));
511                         lw->range_base = lw->selected;
512                         lw->range_selection = true;
513                 }
514                 break;
515         case CMD_LIST_SCROLL_UP_LINE:
516                 list_window_scroll_up(lw, 1);
517                 break;
518         case CMD_LIST_SCROLL_DOWN_LINE:
519                 list_window_scroll_down(lw, rows, 1);
520                 break;
521         case CMD_LIST_SCROLL_UP_HALF:
522                 list_window_scroll_up(lw, (lw->rows - 1) / 2);
523                 break;
524         case CMD_LIST_SCROLL_DOWN_HALF:
525                 list_window_scroll_down(lw, rows, (lw->rows - 1) / 2);
526                 break;
527         default:
528                 return false;
529         }
531         return true;
534 bool
535 list_window_scroll_cmd(struct list_window *lw, unsigned rows, command_t cmd)
537         switch (cmd) {
538         case CMD_LIST_SCROLL_UP_LINE:
539         case CMD_LIST_PREVIOUS:
540                 if (lw->start > 0)
541                         lw->start--;
542                 break;
544         case CMD_LIST_SCROLL_DOWN_LINE:
545         case CMD_LIST_NEXT:
546                 if (lw->start + lw->rows < rows)
547                         lw->start++;
548                 break;
550         case CMD_LIST_FIRST:
551                 lw->start = 0;
552                 break;
554         case CMD_LIST_LAST:
555                 if (rows > lw->rows)
556                         lw->start = rows - lw->rows;
557                 else
558                         lw->start = 0;
559                 break;
561         case CMD_LIST_NEXT_PAGE:
562                 lw->start += lw->rows - 1;
563                 if (lw->start + lw->rows > rows) {
564                         if (rows > lw->rows)
565                                 lw->start = rows - lw->rows;
566                         else
567                                 lw->start = 0;
568                 }
569                 break;
571         case CMD_LIST_PREVIOUS_PAGE:
572                 if (lw->start > lw->rows)
573                         lw->start -= lw->rows;
574                 else
575                         lw->start = 0;
576                 break;
578         case CMD_LIST_SCROLL_UP_HALF:
579                 if (lw->start > (lw->rows - 1) / 2)
580                         lw->start -= (lw->rows - 1) / 2;
581                 else
582                         lw->start = 0;
583                 break;
585         case CMD_LIST_SCROLL_DOWN_HALF:
586                 lw->start += (lw->rows - 1) / 2;
587                 if (lw->start + lw->rows > rows) {
588                         if (rows > lw->rows)
589                                 lw->start = rows - lw->rows;
590                         else
591                                 lw->start = 0;
592                 }
593                 break;
595         default:
596                 return false;
597         }
599         return true;
602 #ifdef HAVE_GETMOUSE
603 bool
604 list_window_mouse(struct list_window *lw, unsigned rows,
605                   unsigned long bstate, int y)
607         assert(lw != NULL);
609         /* if the even occurred above the list window move up */
610         if (y < 0) {
611                 if (bstate & BUTTON3_CLICKED)
612                         list_window_first(lw);
613                 else
614                         list_window_previous_page(lw);
615                 return true;
616         }
618         /* if the even occurred below the list window move down */
619         if ((unsigned)y >= rows) {
620                 if (bstate & BUTTON3_CLICKED)
621                         list_window_last(lw, rows);
622                 else
623                         list_window_next_page(lw, rows);
624                 return true;
625         }
627         return false;
629 #endif