Code

7bdee1a4e67bc4fc46b2dbab8c22dac4c586f29b
[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->start = 0;
68 }
70 void
71 list_window_check_selected(struct list_window *lw, unsigned length)
72 {
73         if (lw->start + lw->rows > length) {
74                 if (length > lw->rows)
75                         lw->start = length - lw->rows;
76                 else
77                         lw->start = 0;
78         }
80         if (lw->selected < lw->start)
81                 lw->selected = lw->start;
83         if (length == 0)
84                 lw->selected = 0;
85         else if (lw->selected >= length)
86                 lw->selected = length - 1;
88         if(lw->range_selection)
89         {
90                 if (length == 0) {
91                         lw->selected_start = 0;
92                         lw->selected_end = 0;
93                         lw->range_base = 0;
94                 } else {
95                         if (lw->selected_start >= length)
96                                 lw->selected_start = length - 1;
97                         if (lw->selected_end >= length)
98                                 lw->selected_end = length - 1;
99                         if (lw->range_base >= length)
100                                 lw->range_base = length - 1;
101                 }
103                 if(lw->range_base > lw->selected_end)
104                           lw->selected_end = lw->selected;
105                 if(lw->range_base < lw->selected_start)
106                           lw->selected_start = lw->selected;
107         }
108         else
109         {
110                 lw->selected_start = lw->selected;
111                 lw->selected_end = lw->selected;
112         }
115 void
116 list_window_center(struct list_window *lw, unsigned rows, unsigned n)
118         if (n > lw->rows / 2)
119                 lw->start = n - lw->rows / 2;
120         else
121                 lw->start = 0;
123         if (lw->start + lw->rows > rows) {
124                 if (lw->rows < rows)
125                         lw->start = rows - lw->rows;
126                 else
127                         lw->start = 0;
128         }
131 void
132 list_window_set_selected(struct list_window *lw, unsigned n)
134         lw->selected = n;
135         if(lw->range_selection)
136         {
137                 if(n >= lw->range_base)
138                 {
139                         lw->selected_end = n;
140                         lw->selected_start = lw->range_base;
141                 }
142                 if(n <= lw->range_base)
143                 {
144                         lw->selected_start = n;
145                         lw->selected_end = lw->range_base;
146                 }
147         }
148         else
149         {
150                 lw->selected_start = n;
151                 lw->selected_end = n;
152         }
155 static void
156 list_window_next(struct list_window *lw, unsigned length)
158         if (lw->selected + 1 < length)
159                 list_window_set_selected(lw, lw->selected + 1);
160         else if (options.list_wrap)
161                 list_window_set_selected(lw, 0);
164 static void
165 list_window_previous(struct list_window *lw, unsigned length)
167         if (lw->selected > 0)
168                 list_window_set_selected(lw, lw->selected - 1);
169         else if (options.list_wrap)
170                 list_window_set_selected(lw, length-1);
173 static void
174 list_window_top(struct list_window *lw)
176         if (lw->start == 0)
177                 list_window_set_selected(lw, lw->start);
178         else
179                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
180                         list_window_set_selected(lw, lw->start + lw->rows / 2);
181                 else
182                         list_window_set_selected(lw, lw->start + options.scroll_offset);
185 static void
186 list_window_middle(struct list_window *lw, unsigned length)
188         if (length >= lw->rows)
189                 list_window_set_selected(lw, lw->start + lw->rows / 2);
190         else
191                 list_window_set_selected(lw, length / 2);
194 static void
195 list_window_bottom(struct list_window *lw, unsigned length)
197         if (length >= lw->rows)
198                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
199                         list_window_set_selected(lw, lw->start + lw->rows / 2);
200                 else
201                         if (lw->start + lw->rows == length)
202                                 list_window_set_selected(lw, length - 1);
203                         else
204                                 list_window_set_selected(lw, lw->start + lw->rows - 1 - options.scroll_offset);
205         else
206                 list_window_set_selected(lw, length - 1);
209 static void
210 list_window_first(struct list_window *lw)
212         list_window_set_selected(lw, 0);
215 static void
216 list_window_last(struct list_window *lw, unsigned length)
218         if (length > 0)
219                 list_window_set_selected(lw, length - 1);
220         else
221                 list_window_set_selected(lw, 0);
224 static void
225 list_window_next_page(struct list_window *lw, unsigned length)
227         if (lw->rows < 2)
228                 return;
229         if (lw->selected + lw->rows < length)
230                 list_window_set_selected(lw, lw->selected + lw->rows - 1);
231         else
232                 list_window_last(lw, length);
235 static void
236 list_window_previous_page(struct list_window *lw)
238         if (lw->rows < 2)
239                 return;
240         if (lw->selected > lw->rows - 1)
241                 list_window_set_selected(lw, lw->selected - lw->rows + 1);
242         else
243                 list_window_first(lw);
246 static void
247 list_window_scroll_up(struct list_window *lw, unsigned n)
249         if (lw->start > 0) {
250                 if (n > lw->start)
251                         lw->start = 0;
252                 else
253                         lw->start -= n;
254                 if (lw->selected > lw->start + lw->rows - 1 - options.scroll_offset) {
255                         lw->selected = lw->start + lw->rows - 1 - options.scroll_offset;
256                         if (lw->range_selection) {
257                                 if (lw->selected < lw->range_base) {
258                                         lw->selected_start = lw->selected;
259                                         lw->selected_end = lw->range_base;
260                                 } else {
261                                         lw->selected_end = lw->selected;
262                                 }
263                         } else {
264                                 lw->selected_start = lw->selected;
265                                 lw->selected_end = lw->selected;
266                         }
267                 }
268         }
271 static void
272 list_window_scroll_down(struct list_window *lw, unsigned length, unsigned n)
274         if (lw->start + lw->rows < length)
275         {
276                 if ( lw->start + lw->rows + n > length - 1)
277                         lw->start = length - lw->rows;
278                 else
279                         lw->start += n;
280                 if (lw->selected < lw->start + options.scroll_offset) {
281                         lw->selected = lw->start + options.scroll_offset;
282                         if (lw->range_selection) {
283                                 if (lw->selected > lw->range_base) {
284                                         lw->selected_end = lw->selected;
285                                         lw->selected_start = lw->range_base;
286                                 } else {
287                                         lw->selected_start = lw->selected;
288                                 }
289                         } else {
290                                 lw->selected_start = lw->selected;
291                                 lw->selected_end = lw->selected;
292                         }
293                 }
294         }
297 void
298 list_window_paint(struct list_window *lw,
299                   list_window_callback_fn_t callback,
300                   void *callback_data)
302         unsigned i;
303         bool fill = options.wide_cursor;
304         bool show_cursor = !lw->hide_cursor;
305         bool highlight = false;
307         if (show_cursor) {
308                 int start = lw->start;
309                 if ((unsigned) options.scroll_offset * 2 >= lw->rows)
310                         // Center if the offset is more than half the screen
311                         start = lw->selected - lw->rows / 2;
312                 else
313                 {
314                         if (lw->selected < lw->start + options.scroll_offset)
315                                 start = lw->selected - options.scroll_offset;
317                         if (lw->selected >= lw->start + lw->rows - options.scroll_offset)
318                         {
319                                 start = lw->selected - lw->rows + 1 + options.scroll_offset;
320                         }
321                 }
322                 if (start < 0)
323                         lw->start = 0;
324                 else
325                 {
326                         while ( start > 0 && callback(start + lw->rows - 1, &highlight, callback_data) == NULL)
327                                 start--;
328                         lw->start = start;
329                 }
330         }
332         for (i = 0; i < lw->rows; i++) {
333                 const char *label;
334                 highlight = false;
336                 label = callback(lw->start + i, &highlight, callback_data);
337                 wmove(lw->w, i, 0);
339                 if (label) {
340                         bool selected = (lw->start + i >= lw->selected_start && lw->start + i <= lw->selected_end);
341                         unsigned len = utf8_width(label);
343                         if (highlight)
344                                 colors_use(lw->w, COLOR_LIST_BOLD);
345                         else
346                                 colors_use(lw->w, COLOR_LIST);
348                         if (show_cursor && selected)
349                                 wattron(lw->w, A_REVERSE);
351                         //waddnstr(lw->w, label, lw->cols);
352                         waddstr(lw->w, label);
353                         if (fill && len < lw->cols)
354                                 whline(lw->w,  ' ', lw->cols-len);
356                         if (selected)
357                                 wattroff(lw->w, A_REVERSE);
359                         if (!fill && len < lw->cols)
360                                 wclrtoeol(lw->w);
361                 } else
362                         wclrtoeol(lw->w);
363         }
366 bool
367 list_window_find(struct list_window *lw,
368                  list_window_callback_fn_t callback,
369                  void *callback_data,
370                  const char *str,
371                  bool wrap,
372                  bool bell_on_wrap)
374         bool h;
375         unsigned i = lw->selected + 1;
376         const char *label;
378         do {
379                 while ((label = callback(i,&h,callback_data))) {
380                         if (str && label && match_line(label, str)) {
381                                 lw->selected = i;
382                                 if(!lw->range_selection || i > lw->selected_end)
383                                           lw->selected_end = i;
384                                 if(!lw->range_selection || i < lw->selected_start)
385                                           lw->selected_start = i;
386                                 return true;
387                         }
388                         if (wrap && i == lw->selected)
389                                 return false;
390                         i++;
391                 }
392                 if (wrap) {
393                         if (i == 0) /* empty list */
394                                 return 1;
395                         i=0; /* first item */
396                         if (bell_on_wrap) {
397                                 screen_bell();
398                         }
399                 }
400         } while (wrap);
402         return false;
405 bool
406 list_window_rfind(struct list_window *lw,
407                   list_window_callback_fn_t callback,
408                   void *callback_data,
409                   const char *str,
410                   bool wrap,
411                   bool bell_on_wrap,
412                   unsigned rows)
414         bool h;
415         int i = lw->selected - 1;
416         const char *label;
418         if (rows == 0)
419                 return false;
421         do {
422                 while (i >= 0 && (label = callback(i,&h,callback_data))) {
423                         if( str && label && match_line(label, str) ) {
424                                 lw->selected = i;
425                                 if(!lw->range_selection || i > (int)lw->selected_end)
426                                           lw->selected_end = i;
427                                 if(!lw->range_selection || i < (int)lw->selected_start)
428                                           lw->selected_start = i;
429                                 return true;
430                         }
431                         if (wrap && i == (int)lw->selected)
432                                 return false;
433                         i--;
434                 }
435                 if (wrap) {
436                         i = rows - 1; /* last item */
437                         if (bell_on_wrap) {
438                                 screen_bell();
439                         }
440                 }
441         } while (wrap);
443         return false;
446 bool
447 list_window_jump(struct list_window *lw,
448                  list_window_callback_fn_t callback,
449                  void *callback_data,
450                  const char *str)
452         bool h;
453         unsigned i = 0;
454         const char *label;
456         while ((label = callback(i,&h,callback_data))) {
457                 if (label && label[0] == '[')
458                         label++;
459 #ifndef NCMPC_MINI
460                 if (str && label &&
461                                 ((options.jump_prefix_only && g_ascii_strncasecmp(label, str, strlen(str)) == 0) ||
462                                  (!options.jump_prefix_only && match_line(label, str))) )
463 #else
464                 if (str && label && g_ascii_strncasecmp(label, str, strlen(str)) == 0)
465 #endif
466                 {
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                 i++;
475         }
476         return false;
479 /* perform basic list window commands (movement) */
480 bool
481 list_window_cmd(struct list_window *lw, unsigned rows, command_t cmd)
483         switch (cmd) {
484         case CMD_LIST_PREVIOUS:
485                 list_window_previous(lw, rows);
486                 break;
487         case CMD_LIST_NEXT:
488                 list_window_next(lw, rows);
489                 break;
490         case CMD_LIST_TOP:
491                 list_window_top(lw);
492                 break;
493         case CMD_LIST_MIDDLE:
494                 list_window_middle(lw,rows);
495                 break;
496         case CMD_LIST_BOTTOM:
497                 list_window_bottom(lw,rows);
498                 break;
499         case CMD_LIST_FIRST:
500                 list_window_first(lw);
501                 break;
502         case CMD_LIST_LAST:
503                 list_window_last(lw, rows);
504                 break;
505         case CMD_LIST_NEXT_PAGE:
506                 list_window_next_page(lw, rows);
507                 break;
508         case CMD_LIST_PREVIOUS_PAGE:
509                 list_window_previous_page(lw);
510                 break;
511         case CMD_LIST_RANGE_SELECT:
512                 if(lw->range_selection)
513                 {
514                         screen_status_printf(_("Range selection disabled"));
515                         lw->range_selection = false;
516                         list_window_set_selected(lw, lw->selected);
517                 }
518                 else
519                 {
520                         screen_status_printf(_("Range selection enabled"));
521                         lw->range_base = lw->selected;
522                         lw->range_selection = true;
523                 }
524                 break;
525         case CMD_LIST_SCROLL_UP_LINE:
526                 list_window_scroll_up(lw, 1);
527                 break;
528         case CMD_LIST_SCROLL_DOWN_LINE:
529                 list_window_scroll_down(lw, rows, 1);
530                 break;
531         case CMD_LIST_SCROLL_UP_HALF:
532                 list_window_scroll_up(lw, (lw->rows - 1) / 2);
533                 break;
534         case CMD_LIST_SCROLL_DOWN_HALF:
535                 list_window_scroll_down(lw, rows, (lw->rows - 1) / 2);
536                 break;
537         default:
538                 return false;
539         }
541         return true;
544 bool
545 list_window_scroll_cmd(struct list_window *lw, unsigned rows, command_t cmd)
547         switch (cmd) {
548         case CMD_LIST_SCROLL_UP_LINE:
549         case CMD_LIST_PREVIOUS:
550                 if (lw->start > 0)
551                         lw->start--;
552                 break;
554         case CMD_LIST_SCROLL_DOWN_LINE:
555         case CMD_LIST_NEXT:
556                 if (lw->start + lw->rows < rows)
557                         lw->start++;
558                 break;
560         case CMD_LIST_FIRST:
561                 lw->start = 0;
562                 break;
564         case CMD_LIST_LAST:
565                 if (rows > lw->rows)
566                         lw->start = rows - lw->rows;
567                 else
568                         lw->start = 0;
569                 break;
571         case CMD_LIST_NEXT_PAGE:
572                 lw->start += lw->rows - 1;
573                 if (lw->start + lw->rows > rows) {
574                         if (rows > lw->rows)
575                                 lw->start = rows - lw->rows;
576                         else
577                                 lw->start = 0;
578                 }
579                 break;
581         case CMD_LIST_PREVIOUS_PAGE:
582                 if (lw->start > lw->rows)
583                         lw->start -= lw->rows;
584                 else
585                         lw->start = 0;
586                 break;
588         case CMD_LIST_SCROLL_UP_HALF:
589                 if (lw->start > (lw->rows - 1) / 2)
590                         lw->start -= (lw->rows - 1) / 2;
591                 else
592                         lw->start = 0;
593                 break;
595         case CMD_LIST_SCROLL_DOWN_HALF:
596                 lw->start += (lw->rows - 1) / 2;
597                 if (lw->start + lw->rows > rows) {
598                         if (rows > lw->rows)
599                                 lw->start = rows - lw->rows;
600                         else
601                                 lw->start = 0;
602                 }
603                 break;
605         default:
606                 return false;
607         }
609         return true;
612 #ifdef HAVE_GETMOUSE
613 bool
614 list_window_mouse(struct list_window *lw, unsigned rows,
615                   unsigned long bstate, int y)
617         assert(lw != NULL);
619         /* if the even occurred above the list window move up */
620         if (y < 0) {
621                 if (bstate & BUTTON3_CLICKED)
622                         list_window_first(lw);
623                 else
624                         list_window_previous_page(lw);
625                 return true;
626         }
628         /* if the even occurred below the list window move down */
629         if ((unsigned)y >= rows) {
630                 if (bstate & BUTTON3_CLICKED)
631                         list_window_last(lw, rows);
632                 else
633                         list_window_next_page(lw, rows);
634                 return true;
635         }
637         return false;
639 #endif