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->visual_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->visual_selection = false;
66 lw->visual_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->visual_selection)
90 {
91 if(lw->visual_base > lw->selected_end)
92 lw->selected_end = lw->selected;
93 if(lw->visual_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->visual_selection)
124 {
125 if(n >= lw->visual_base)
126 lw->selected_end = n;
127 if(n <= lw->visual_base)
128 lw->selected_start = n;
129 }
130 else
131 {
132 lw->selected_start = n;
133 lw->selected_end = n;
134 }
135 }
137 static void
138 list_window_next(struct list_window *lw, unsigned length)
139 {
140 if (lw->selected + 1 < length)
141 list_window_set_selected(lw, lw->selected + 1);
142 else if (options.list_wrap)
143 list_window_set_selected(lw, 0);
144 }
146 static void
147 list_window_previous(struct list_window *lw, unsigned length)
148 {
149 if (lw->selected > 0)
150 list_window_set_selected(lw, lw->selected - 1);
151 else if (options.list_wrap)
152 list_window_set_selected(lw, length-1);
153 }
155 static void
156 list_window_top(struct list_window *lw)
157 {
158 list_window_set_selected(lw, lw->start);
159 }
161 static void
162 list_window_middle(struct list_window *lw, unsigned length)
163 {
164 if (length >= lw->rows)
165 list_window_set_selected(lw, lw->start + lw->rows / 2);
166 else
167 list_window_set_selected(lw, length / 2);
168 }
170 static void
171 list_window_bottom(struct list_window *lw, unsigned length)
172 {
173 if (length >= lw->rows)
174 list_window_set_selected(lw, lw->start + lw->rows - 1);
175 else
176 list_window_set_selected(lw, length - 1);
177 }
179 static void
180 list_window_first(struct list_window *lw)
181 {
182 lw->xoffset = 0;
183 list_window_set_selected(lw, 0);
184 }
186 static void
187 list_window_last(struct list_window *lw, unsigned length)
188 {
189 lw->xoffset = 0;
190 if (length > 0)
191 list_window_set_selected(lw, length - 1);
192 else
193 list_window_set_selected(lw, 0);
194 }
196 static void
197 list_window_next_page(struct list_window *lw, unsigned length)
198 {
199 if (lw->rows < 2)
200 return;
201 if (lw->selected + lw->rows < length)
202 list_window_set_selected(lw, lw->selected + lw->rows - 1);
203 else
204 list_window_last(lw, length);
205 }
207 static void
208 list_window_previous_page(struct list_window *lw)
209 {
210 if (lw->rows < 2)
211 return;
212 if (lw->selected > lw->rows - 1)
213 list_window_set_selected(lw, lw->selected - lw->rows + 1);
214 else
215 list_window_first(lw);
216 }
219 void
220 list_window_paint(struct list_window *lw,
221 list_window_callback_fn_t callback,
222 void *callback_data)
223 {
224 unsigned i;
225 bool fill = options.wide_cursor;
226 bool show_cursor = !lw->hide_cursor;
228 if (show_cursor) {
229 if (lw->selected < lw->start)
230 lw->start = lw->selected;
232 if (lw->selected >= lw->start + lw->rows)
233 lw->start = lw->selected - lw->rows + 1;
234 }
236 for (i = 0; i < lw->rows; i++) {
237 bool highlight = false;
238 const char *label;
240 label = callback(lw->start + i, &highlight, callback_data);
241 wmove(lw->w, i, 0);
243 if (label) {
244 bool selected = (lw->start + i >= lw->selected_start && lw->start + i <= lw->selected_end);
245 unsigned len = utf8_width(label);
247 if (highlight)
248 colors_use(lw->w, COLOR_LIST_BOLD);
249 else
250 colors_use(lw->w, COLOR_LIST);
252 if (show_cursor && selected)
253 wattron(lw->w, A_REVERSE);
255 //waddnstr(lw->w, label, lw->cols);
256 waddstr(lw->w, label);
257 if (fill && len < lw->cols)
258 whline(lw->w, ' ', lw->cols-len);
260 if (selected)
261 wattroff(lw->w, A_REVERSE);
263 if (!fill && len < lw->cols)
264 wclrtoeol(lw->w);
265 } else
266 wclrtoeol(lw->w);
267 }
268 }
270 bool
271 list_window_find(struct list_window *lw,
272 list_window_callback_fn_t callback,
273 void *callback_data,
274 const char *str,
275 bool wrap,
276 bool bell_on_wrap)
277 {
278 bool h;
279 unsigned i = lw->selected + 1;
280 const char *label;
282 do {
283 while ((label = callback(i,&h,callback_data))) {
284 if (str && label && match_line(label, str)) {
285 lw->selected = i;
286 if(!lw->visual_selection || i > lw->selected_end)
287 lw->selected_end = i;
288 if(!lw->visual_selection || i < lw->selected_start)
289 lw->selected_start = i;
290 return true;
291 }
292 if (wrap && i == lw->selected)
293 return false;
294 i++;
295 }
296 if (wrap) {
297 if (i == 0) /* empty list */
298 return 1;
299 i=0; /* first item */
300 if (bell_on_wrap) {
301 screen_bell();
302 }
303 }
304 } while (wrap);
306 return false;
307 }
309 bool
310 list_window_rfind(struct list_window *lw,
311 list_window_callback_fn_t callback,
312 void *callback_data,
313 const char *str,
314 bool wrap,
315 bool bell_on_wrap,
316 unsigned rows)
317 {
318 bool h;
319 int i = lw->selected - 1;
320 const char *label;
322 if (rows == 0)
323 return false;
325 do {
326 while (i >= 0 && (label = callback(i,&h,callback_data))) {
327 if( str && label && match_line(label, str) ) {
328 lw->selected = i;
329 if(!lw->visual_selection || i > (int)lw->selected_end)
330 lw->selected_end = i;
331 if(!lw->visual_selection || i < (int)lw->selected_start)
332 lw->selected_start = i;
333 return true;
334 }
335 if (wrap && i == (int)lw->selected)
336 return false;
337 i--;
338 }
339 if (wrap) {
340 i = rows - 1; /* last item */
341 if (bell_on_wrap) {
342 screen_bell();
343 }
344 }
345 } while (wrap);
347 return false;
348 }
350 bool
351 list_window_jump(struct list_window *lw,
352 list_window_callback_fn_t callback,
353 void *callback_data,
354 const char *str)
355 {
356 bool h;
357 unsigned i = 0;
358 const char *label;
360 while ((label = callback(i,&h,callback_data))) {
361 if (label && label[0] != '[')
362 {
363 if (str && label && g_ascii_strncasecmp(label, str, strlen(str)) == 0) {
364 lw->selected = i;
365 if(!lw->visual_selection || i > lw->selected_end)
366 lw->selected_end = i;
367 if(!lw->visual_selection || i < lw->selected_start)
368 lw->selected_start = i;
369 return true;
370 }
371 }
372 else if (str && label && g_ascii_strncasecmp(label+1, str, strlen(str)) == 0) {
373 lw->selected = i;
374 if(!lw->visual_selection || i > lw->selected_end)
375 lw->selected_end = i;
376 if(!lw->visual_selection || i < lw->selected_start)
377 lw->selected_start = i;
378 return true;
379 }
380 i++;
381 }
382 return false;
383 }
385 /* perform basic list window commands (movement) */
386 bool
387 list_window_cmd(struct list_window *lw, unsigned rows, command_t cmd)
388 {
389 switch (cmd) {
390 case CMD_LIST_PREVIOUS:
391 list_window_previous(lw, rows);
392 break;
393 case CMD_LIST_NEXT:
394 list_window_next(lw, rows);
395 break;
396 case CMD_LIST_TOP:
397 list_window_top(lw);
398 break;
399 case CMD_LIST_MIDDLE:
400 list_window_middle(lw,rows);
401 break;
402 case CMD_LIST_BOTTOM:
403 list_window_bottom(lw,rows);
404 break;
405 case CMD_LIST_FIRST:
406 list_window_first(lw);
407 break;
408 case CMD_LIST_LAST:
409 list_window_last(lw, rows);
410 break;
411 case CMD_LIST_NEXT_PAGE:
412 list_window_next_page(lw, rows);
413 break;
414 case CMD_LIST_PREVIOUS_PAGE:
415 list_window_previous_page(lw);
416 break;
417 case CMD_LIST_VISUAL_SELECT:
418 if(lw->visual_selection)
419 {
420 screen_status_printf(_("Visual selection disabled"));
421 lw->visual_selection = false;
422 list_window_set_selected(lw, lw->selected);
423 }
424 else
425 {
426 screen_status_printf(_("Visual selection enabled"));
427 lw->visual_base = lw->selected;
428 lw->visual_selection = true;
429 }
430 break;
431 default:
432 return false;
433 }
435 return true;
436 }
438 bool
439 list_window_scroll_cmd(struct list_window *lw, unsigned rows, command_t cmd)
440 {
441 switch (cmd) {
442 case CMD_LIST_PREVIOUS:
443 if (lw->start > 0)
444 lw->start--;
445 break;
447 case CMD_LIST_NEXT:
448 if (lw->start + lw->rows < rows)
449 lw->start++;
450 break;
452 case CMD_LIST_FIRST:
453 lw->start = 0;
454 break;
456 case CMD_LIST_LAST:
457 if (rows > lw->rows)
458 lw->start = rows - lw->rows;
459 else
460 lw->start = 0;
461 break;
463 case CMD_LIST_NEXT_PAGE:
464 lw->start += lw->rows - 1;
465 if (lw->start + lw->rows > rows) {
466 if (rows > lw->rows)
467 lw->start = rows - lw->rows;
468 else
469 lw->start = 0;
470 }
471 break;
473 case CMD_LIST_PREVIOUS_PAGE:
474 if (lw->start > lw->rows)
475 lw->start -= lw->rows;
476 else
477 lw->start = 0;
478 break;
480 default:
481 return false;
482 }
484 return true;
485 }
487 #ifdef HAVE_GETMOUSE
488 bool
489 list_window_mouse(struct list_window *lw, unsigned rows,
490 unsigned long bstate, int y)
491 {
492 assert(lw != NULL);
494 /* if the even occured above the list window move up */
495 if (y < 0) {
496 if (bstate & BUTTON3_CLICKED)
497 list_window_first(lw);
498 else
499 list_window_previous_page(lw);
500 return true;
501 }
503 /* if the even occured below the list window move down */
504 if ((unsigned)y >= rows) {
505 if (bstate & BUTTON3_CLICKED)
506 list_window_last(lw, rows);
507 else
508 list_window_next_page(lw, rows);
509 return true;
510 }
512 return false;
513 }
514 #endif