Code

9421db3744e9b472a43fcbf8e1a72b1c48baa29b
[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, NULL, callback_data) == NULL)
327                                 start--;
328                         lw->start = start;
329                 }
330         }
332         for (i = 0; i < lw->rows; i++) {
333                 const char *label;
334                 char *second_column = NULL;
335                 highlight = false;
337                 label = callback(lw->start + i, &highlight, &second_column, callback_data);
338                 wmove(lw->w, i, 0);
340                 if (label) {
341                         bool selected = (lw->start + i >= lw->selected_start && lw->start + i <= lw->selected_end);
342                         unsigned len = utf8_width(label);
344                         if (highlight)
345                                 colors_use(lw->w, COLOR_LIST_BOLD);
346                         else
347                                 colors_use(lw->w, COLOR_LIST);
349                         if (show_cursor && selected)
350                                 wattron(lw->w, A_REVERSE);
352                         //waddnstr(lw->w, label, lw->cols);
353                         waddstr(lw->w, label);
354                         if (fill && len < lw->cols)
355                                 whline(lw->w,  ' ', lw->cols-len);
357                         if(second_column)
358                         {
359                                 unsigned sc_len = utf8_width(second_column) + 1;
360                                 if(lw->cols > sc_len)
361                                 {
362                                         wmove(lw->w, i, lw->cols - sc_len);
363                                         waddstr(lw->w, " ");
364                                         wmove(lw->w, i, lw->cols - sc_len + 1);
365                                         waddstr(lw->w, second_column);
366                                 }
367                                 g_free(second_column);
368                         }
370                         if (selected)
371                                 wattroff(lw->w, A_REVERSE);
373                         if (!fill && len < lw->cols)
374                                 wclrtoeol(lw->w);
375                 } else
376                         wclrtoeol(lw->w);
377         }
380 bool
381 list_window_find(struct list_window *lw,
382                  list_window_callback_fn_t callback,
383                  void *callback_data,
384                  const char *str,
385                  bool wrap,
386                  bool bell_on_wrap)
388         bool h;
389         unsigned i = lw->selected + 1;
390         const char *label;
392         do {
393                 while ((label = callback(i,&h,NULL,callback_data))) {
394                         if (str && label && match_line(label, str)) {
395                                 lw->selected = i;
396                                 if(!lw->range_selection || i > lw->selected_end)
397                                           lw->selected_end = i;
398                                 if(!lw->range_selection || i < lw->selected_start)
399                                           lw->selected_start = i;
400                                 return true;
401                         }
402                         if (wrap && i == lw->selected)
403                                 return false;
404                         i++;
405                 }
406                 if (wrap) {
407                         if (i == 0) /* empty list */
408                                 return 1;
409                         i=0; /* first item */
410                         if (bell_on_wrap) {
411                                 screen_bell();
412                         }
413                 }
414         } while (wrap);
416         return false;
419 bool
420 list_window_rfind(struct list_window *lw,
421                   list_window_callback_fn_t callback,
422                   void *callback_data,
423                   const char *str,
424                   bool wrap,
425                   bool bell_on_wrap,
426                   unsigned rows)
428         bool h;
429         int i = lw->selected - 1;
430         const char *label;
432         if (rows == 0)
433                 return false;
435         do {
436                 while (i >= 0 && (label = callback(i,&h,NULL,callback_data))) {
437                         if( str && label && match_line(label, str) ) {
438                                 lw->selected = i;
439                                 if(!lw->range_selection || i > (int)lw->selected_end)
440                                           lw->selected_end = i;
441                                 if(!lw->range_selection || i < (int)lw->selected_start)
442                                           lw->selected_start = i;
443                                 return true;
444                         }
445                         if (wrap && i == (int)lw->selected)
446                                 return false;
447                         i--;
448                 }
449                 if (wrap) {
450                         i = rows - 1; /* last item */
451                         if (bell_on_wrap) {
452                                 screen_bell();
453                         }
454                 }
455         } while (wrap);
457         return false;
460 bool
461 list_window_jump(struct list_window *lw,
462                  list_window_callback_fn_t callback,
463                  void *callback_data,
464                  const char *str)
466         bool h;
467         unsigned i = 0;
468         const char *label;
470         while ((label = callback(i,&h,NULL,callback_data))) {
471                 if (label && label[0] == '[')
472                         label++;
473 #ifndef NCMPC_MINI
474                 if (str && label &&
475                                 ((options.jump_prefix_only && g_ascii_strncasecmp(label, str, strlen(str)) == 0) ||
476                                  (!options.jump_prefix_only && match_line(label, str))) )
477 #else
478                 if (str && label && g_ascii_strncasecmp(label, str, strlen(str)) == 0)
479 #endif
480                 {
481                         lw->selected = i;
482                         if(!lw->range_selection || i > lw->selected_end)
483                                 lw->selected_end = i;
484                         if(!lw->range_selection || i < lw->selected_start)
485                                 lw->selected_start = i;
486                         return true;
487                 }
488                 i++;
489         }
490         return false;
493 /* perform basic list window commands (movement) */
494 bool
495 list_window_cmd(struct list_window *lw, unsigned rows, command_t cmd)
497         switch (cmd) {
498         case CMD_LIST_PREVIOUS:
499                 list_window_previous(lw, rows);
500                 break;
501         case CMD_LIST_NEXT:
502                 list_window_next(lw, rows);
503                 break;
504         case CMD_LIST_TOP:
505                 list_window_top(lw);
506                 break;
507         case CMD_LIST_MIDDLE:
508                 list_window_middle(lw,rows);
509                 break;
510         case CMD_LIST_BOTTOM:
511                 list_window_bottom(lw,rows);
512                 break;
513         case CMD_LIST_FIRST:
514                 list_window_first(lw);
515                 break;
516         case CMD_LIST_LAST:
517                 list_window_last(lw, rows);
518                 break;
519         case CMD_LIST_NEXT_PAGE:
520                 list_window_next_page(lw, rows);
521                 break;
522         case CMD_LIST_PREVIOUS_PAGE:
523                 list_window_previous_page(lw);
524                 break;
525         case CMD_LIST_RANGE_SELECT:
526                 if(lw->range_selection)
527                 {
528                         screen_status_printf(_("Range selection disabled"));
529                         lw->range_selection = false;
530                         list_window_set_selected(lw, lw->selected);
531                 }
532                 else
533                 {
534                         screen_status_printf(_("Range selection enabled"));
535                         lw->range_base = lw->selected;
536                         lw->range_selection = true;
537                 }
538                 break;
539         case CMD_LIST_SCROLL_UP_LINE:
540                 list_window_scroll_up(lw, 1);
541                 break;
542         case CMD_LIST_SCROLL_DOWN_LINE:
543                 list_window_scroll_down(lw, rows, 1);
544                 break;
545         case CMD_LIST_SCROLL_UP_HALF:
546                 list_window_scroll_up(lw, (lw->rows - 1) / 2);
547                 break;
548         case CMD_LIST_SCROLL_DOWN_HALF:
549                 list_window_scroll_down(lw, rows, (lw->rows - 1) / 2);
550                 break;
551         default:
552                 return false;
553         }
555         return true;
558 bool
559 list_window_scroll_cmd(struct list_window *lw, unsigned rows, command_t cmd)
561         switch (cmd) {
562         case CMD_LIST_SCROLL_UP_LINE:
563         case CMD_LIST_PREVIOUS:
564                 if (lw->start > 0)
565                         lw->start--;
566                 break;
568         case CMD_LIST_SCROLL_DOWN_LINE:
569         case CMD_LIST_NEXT:
570                 if (lw->start + lw->rows < rows)
571                         lw->start++;
572                 break;
574         case CMD_LIST_FIRST:
575                 lw->start = 0;
576                 break;
578         case CMD_LIST_LAST:
579                 if (rows > lw->rows)
580                         lw->start = rows - lw->rows;
581                 else
582                         lw->start = 0;
583                 break;
585         case CMD_LIST_NEXT_PAGE:
586                 lw->start += lw->rows - 1;
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         case CMD_LIST_PREVIOUS_PAGE:
596                 if (lw->start > lw->rows)
597                         lw->start -= lw->rows;
598                 else
599                         lw->start = 0;
600                 break;
602         case CMD_LIST_SCROLL_UP_HALF:
603                 if (lw->start > (lw->rows - 1) / 2)
604                         lw->start -= (lw->rows - 1) / 2;
605                 else
606                         lw->start = 0;
607                 break;
609         case CMD_LIST_SCROLL_DOWN_HALF:
610                 lw->start += (lw->rows - 1) / 2;
611                 if (lw->start + lw->rows > rows) {
612                         if (rows > lw->rows)
613                                 lw->start = rows - lw->rows;
614                         else
615                                 lw->start = 0;
616                 }
617                 break;
619         default:
620                 return false;
621         }
623         return true;
626 #ifdef HAVE_GETMOUSE
627 bool
628 list_window_mouse(struct list_window *lw, unsigned rows,
629                   unsigned long bstate, int y)
631         assert(lw != NULL);
633         /* if the even occurred above the list window move up */
634         if (y < 0) {
635                 if (bstate & BUTTON3_CLICKED)
636                         list_window_first(lw);
637                 else
638                         list_window_previous_page(lw);
639                 return true;
640         }
642         /* if the even occurred below the list window move down */
643         if ((unsigned)y >= rows) {
644                 if (bstate & BUTTON3_CLICKED)
645                         list_window_last(lw, rows);
646                 else
647                         list_window_next_page(lw, rows);
648                 return true;
649         }
651         return false;
653 #endif