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 }
101 }
103 void
104 list_window_center(struct list_window *lw, unsigned rows, unsigned n)
105 {
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 }
117 }
119 void
120 list_window_set_selected(struct list_window *lw, unsigned n)
121 {
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 }
141 }
143 static void
144 list_window_next(struct list_window *lw, unsigned length)
145 {
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);
150 }
152 static void
153 list_window_previous(struct list_window *lw, unsigned length)
154 {
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);
159 }
161 static void
162 list_window_top(struct list_window *lw)
163 {
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);
171 }
173 static void
174 list_window_middle(struct list_window *lw, unsigned length)
175 {
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);
180 }
182 static void
183 list_window_bottom(struct list_window *lw, unsigned length)
184 {
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);
195 }
197 static void
198 list_window_first(struct list_window *lw)
199 {
200 lw->xoffset = 0;
201 list_window_set_selected(lw, 0);
202 }
204 static void
205 list_window_last(struct list_window *lw, unsigned length)
206 {
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);
212 }
214 static void
215 list_window_next_page(struct list_window *lw, unsigned length)
216 {
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);
223 }
225 static void
226 list_window_previous_page(struct list_window *lw)
227 {
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);
234 }
236 static void
237 list_window_scroll_up(struct list_window *lw, unsigned n)
238 {
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 }
259 }
261 static void
262 list_window_scroll_down(struct list_window *lw, unsigned length, unsigned n)
263 {
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 }
285 }
287 void
288 list_window_paint(struct list_window *lw,
289 list_window_callback_fn_t callback,
290 void *callback_data)
291 {
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 }
354 }
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)
363 {
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;
393 }
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)
403 {
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;
434 }
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)
441 {
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;
467 }
469 /* perform basic list window commands (movement) */
470 bool
471 list_window_cmd(struct list_window *lw, unsigned rows, command_t cmd)
472 {
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;
532 }
534 bool
535 list_window_scroll_cmd(struct list_window *lw, unsigned rows, command_t cmd)
536 {
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;
600 }
602 #ifdef HAVE_GETMOUSE
603 bool
604 list_window_mouse(struct list_window *lw, unsigned rows,
605 unsigned long bstate, int y)
606 {
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;
628 }
629 #endif