Fix whitespace inconsistencies.
[herrie-working.git] / herrie / src / gui_vfslist.c
blobd1c969d40f062a6680022fc8970b663d0ee0d778
1 /*
2 * Copyright (c) 2006-2011 Ed Schouten <ed@80386.nl>
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
26 /**
27 * @file gui_vfslist.c
28 * @brief Generic directory/playlist display for textual user interface.
31 #include "stdinc.h"
33 #include "config.h"
34 #include "gui.h"
35 #include "gui_internal.h"
36 #include "gui_vfslist.h"
37 #include "vfs.h"
39 /**
40 * @brief Whether or not we want to scroll entire pages instead of
41 * single lines when the selection gets out of sight.
43 static int scrollpages;
45 /**
46 * @brief Make sure the cursor resides inside the viewport. Move the
47 * viewport if not.
49 static void
50 gui_vfslist_cursor_adjust(struct gui_vfslist *gv)
52 struct vfsref *vr;
54 /* Shortcut - nothing to adjust when the list is empty */
55 if (gv->vr_selected == NULL)
56 return;
58 /* Three possible cases here */
59 if (gv->idx_top > gv->idx_selected) {
61 * The entry is above the viewport. Move the viewport up
62 * so we can see it.
64 gv->vr_top = gv->vr_selected;
65 gv->idx_top = gv->idx_selected;
66 } else if (gv->idx_top + gv->winheight <= gv->idx_selected) {
68 * The entry is below the viewport. Start counting
69 * backward from the selected entry, so we can keep it
70 * just in screen. This is faster than counting forward,
71 * because there could be a lot of items in between.
73 gv->vr_top = gv->vr_selected;
74 gv->idx_top = gv->idx_selected;
75 do {
76 vr = vfs_list_prev(gv->vr_top);
77 if (vr == NULL)
78 break;
79 gv->vr_top = vr;
80 gv->idx_top--;
81 } while (gv->idx_top + gv->winheight - 1 > gv->idx_selected);
82 } else {
84 * The item is in reach, but it could be possible that
85 * there is some blank space at the bottom. Move the
86 * viewport up, so we fill the terminal as much as
87 * possible.
89 while (gv->idx_top + gv->winheight - 1 > vfs_list_items(gv->list)) {
90 vr = vfs_list_prev(gv->vr_top);
91 if (vr == NULL)
92 break;
93 gv->vr_top = vr;
94 gv->idx_top--;
99 /**
100 * @brief Calculate what width the column with the index numbers should
101 * have, based on the amount of songs in the playlist.
103 static unsigned int
104 gui_vfslist_idxcol_width(struct gui_vfslist *gv)
106 unsigned int len, idx;
108 if (gv->list == NULL)
109 return (0);
112 * Calculate the number of digits needed to print the number of
113 * entries
115 for (len = 1, idx = vfs_list_items(gv->list);idx >= 10;
116 len++, idx /= 10);
118 return (len);
122 * @brief Recalculate the percentage string.
124 static void
125 gui_vfslist_percent(struct gui_vfslist *gv)
127 unsigned int bottom, length;
129 length = gv->list != NULL ? vfs_list_items(gv->list) : 0;
130 bottom = gv->idx_top + gv->winheight - 1;
132 if (bottom >= length) {
133 /* We can see the end */
134 if (gv->idx_top <= 1)
135 strcpy(gv->percent, " (all) ");
136 else
137 strcpy(gv->percent, " (end) ");
138 } else {
139 /* Write back a percentage */
140 sprintf(gv->percent, " (%d%%) ", (bottom * 100) / length);
145 * @brief Redraw the contents of the dialog.
147 static void
148 gui_vfslist_refresh(struct gui_vfslist *gv)
150 unsigned int i, idx, idxw, idxmaxw = 0;
151 char num[16], mark;
152 struct vfsref *vr;
154 if (gv->win == NULL)
155 return;
157 /* Make sure we have everything in sight */
158 gui_vfslist_cursor_adjust(gv);
160 gui_lock();
161 werase(gv->win);
163 vr = gv->vr_top;
164 idx = gv->idx_top;
165 if (gv->shownumbers)
166 idxmaxw = gui_vfslist_idxcol_width(gv);
167 for (i = 0; i < gv->winheight; i++) {
168 if (vr == NULL) {
169 /* We've reached the bottom of the list */
170 mvwaddch(gv->win, i, 0, ' ');
171 wbkgdset(gv->win, COLOR_PAIR(GUI_COLOR_BLOCK));
172 wclrtobot(gv->win);
173 break;
176 /* Sanity check */
177 g_assert((vr == gv->vr_selected) == (idx == gv->idx_selected));
179 if (vr == gv->vr_selected && gv->winfocused)
180 /* Selected */
181 wbkgdset(gv->win, COLOR_PAIR(GUI_COLOR_SELECT));
182 else if (vfs_marked(vr))
183 /* Marked */
184 wbkgdset(gv->win, COLOR_PAIR(GUI_COLOR_MARKED));
185 else if (vr == gv->vr_selected)
186 /* Selected, but inactive */
187 wbkgdset(gv->win, COLOR_PAIR(GUI_COLOR_DESELECT));
189 /* Small whitespace on the left, or > when black & white */
190 if ((vr == gv->vr_selected) && !gui_draw_colors) {
191 if (gv->winfocused)
192 wattron(gv->win, A_BOLD);
193 mvwaddch(gv->win, i, 0, '>');
194 } else {
195 mvwaddch(gv->win, i, 0, ' ');
197 wclrtoeol(gv->win);
199 if (gv->shownumbers) {
200 idxw = snprintf(num, sizeof num, "%d", idx);
201 mvwaddstr(gv->win, i, 1 + idxmaxw - idxw, num);
202 waddstr(gv->win, ". ");
203 waddstr(gv->win, vfs_name(vr));
204 } else {
205 waddstr(gv->win, vfs_name(vr));
208 /* Marking character for dirs and such */
209 mark = vfs_marking(vr);
210 if (mark != '\0')
211 waddch(gv->win, mark);
213 wbkgdset(gv->win, COLOR_PAIR(GUI_COLOR_BLOCK));
214 wattroff(gv->win, A_BOLD);
216 vr = vfs_list_next(vr);
217 idx++;
220 wnoutrefresh(gv->win);
221 gui_unlock();
223 gui_vfslist_percent(gv);
225 if (gv->callback != NULL)
226 gv->callback();
229 struct gui_vfslist *
230 gui_vfslist_new(int shownumbers)
232 struct gui_vfslist *ret;
234 scrollpages = config_getopt_bool("gui.vfslist.scrollpages");
236 ret = g_slice_new0(struct gui_vfslist);
237 ret->shownumbers = shownumbers;
239 return (ret);
242 void
243 gui_vfslist_destroy(struct gui_vfslist *gv)
245 if (gv->win != NULL)
246 delwin(gv->win);
247 g_slice_free(struct gui_vfslist, gv);
250 void
251 gui_vfslist_setlist(struct gui_vfslist *gv, const struct vfslist *vl)
253 gv->list = vl;
254 gv->vr_top = gv->vr_selected = vfs_list_first(gv->list);
255 gv->idx_top = gv->idx_selected = (gv->vr_selected != NULL) ? 1 : 0;
257 gui_vfslist_refresh(gv);
261 gui_vfslist_warn_isempty(struct gui_vfslist *gv)
263 if (gv->vr_selected == NULL) {
264 gui_msgbar_warn(_("There are no songs."));
265 return (1);
266 } else {
267 return (0);
271 void
272 gui_vfslist_move(struct gui_vfslist *gv,
273 int x, int y, int width, int height)
275 gui_lock();
276 if (gv->win == NULL) {
277 gv->win = newwin(height, width, y, x);
278 } else {
279 wresize(gv->win, height, width);
280 mvwin(gv->win, y, x);
282 clearok(gv->win, TRUE);
283 gui_unlock();
284 gv->winheight = height;
286 gui_vfslist_refresh(gv);
289 void
290 gui_vfslist_setfocus(struct gui_vfslist *gv, int focus)
292 gv->winfocused = focus;
293 gui_vfslist_refresh(gv);
296 void
297 gui_vfslist_setselected(struct gui_vfslist *gv, struct vfsref *vr,
298 unsigned int index)
300 unsigned int i;
302 gv->vr_selected = gv->vr_top = vr;
303 gv->idx_selected = gv->idx_top = index;
305 /* Put the selected item in the center */
306 for (i = 0; i < (gv->winheight - 1) / 2 &&
307 vfs_list_prev(gv->vr_top) != NULL; i++) {
308 gv->vr_top = vfs_list_prev(gv->vr_top);
309 gv->idx_top--;
312 gui_vfslist_refresh(gv);
315 void
316 gui_vfslist_cursor_up(struct gui_vfslist *gv)
318 struct vfsref *vr;
320 if (gui_vfslist_warn_isempty(gv))
321 return;
323 vr = vfs_list_prev(gv->vr_selected);
324 if (vr != NULL) {
325 gv->vr_selected = vr;
326 gv->idx_selected--;
328 if (scrollpages && gv->idx_top > gv->idx_selected) {
329 gv->vr_top = vfs_list_first(gv->list);
330 gv->idx_top = 1;
333 gui_vfslist_refresh(gv);
334 } else {
335 gui_msgbar_warn(_("You are at the first song."));
339 void
340 gui_vfslist_cursor_down(struct gui_vfslist *gv, int silent)
342 struct vfsref *vr;
344 if (gui_vfslist_warn_isempty(gv))
345 return;
347 vr = vfs_list_next(gv->vr_selected);
348 if (vr != NULL) {
349 gv->vr_selected = vr;
350 gv->idx_selected++;
352 g_assert(gv->idx_top + gv->winheight >= gv->idx_selected);
353 if (scrollpages && gv->idx_top + gv->winheight == gv->idx_selected) {
354 gv->vr_top = gv->vr_selected;
355 gv->idx_top = gv->idx_selected;
358 gui_vfslist_refresh(gv);
359 } else if (!silent) {
360 gui_msgbar_warn(_("You are at the last song."));
364 void
365 gui_vfslist_cursor_head(struct gui_vfslist *gv)
367 if (gui_vfslist_warn_isempty(gv))
368 return;
370 gv->vr_top = gv->vr_selected = vfs_list_first(gv->list);
371 gv->idx_top = gv->idx_selected = (gv->vr_selected != NULL) ? 1 : 0;
373 gui_vfslist_refresh(gv);
376 void
377 gui_vfslist_cursor_tail(struct gui_vfslist *gv)
379 if (gui_vfslist_warn_isempty(gv))
380 return;
382 gv->vr_selected = vfs_list_last(gv->list);
383 gv->idx_selected = vfs_list_items(gv->list);
385 gui_vfslist_refresh(gv);
388 void
389 gui_vfslist_cursor_pageup(struct gui_vfslist *gv)
391 unsigned int i;
393 if (gui_vfslist_warn_isempty(gv))
394 return;
396 /* Cursor = bottom of the screen */
397 gv->vr_selected = vfs_list_next(gv->vr_top);
398 gv->idx_selected = gv->idx_top + 1;
400 /* Adjust the viewport */
401 for (i = 2; i < gv->winheight && gv->vr_top != NULL; i++) {
402 gv->vr_top = vfs_list_prev(gv->vr_top);
403 gv->idx_top--;
405 if (gv->vr_top == NULL) {
406 /* Don't scroll out of reach */
407 gv->vr_selected = gv->vr_top = vfs_list_first(gv->list);
408 gv->idx_selected = gv->idx_top = 1;
409 } else if (gv->vr_selected == NULL) {
410 /* Original screen may have had one item */
411 gv->vr_selected = gv->vr_top;
412 gv->idx_selected = gv->idx_top;
415 gui_vfslist_refresh(gv);
418 void
419 gui_vfslist_cursor_pagedown(struct gui_vfslist *gv)
421 unsigned int i;
422 struct vfsref *vr_toptmp = gv->vr_top;
424 if (gui_vfslist_warn_isempty(gv))
425 return;
427 /* A la vi: page down minus two */
428 for (i = 2; i < gv->winheight && gv->vr_top != NULL; i++) {
429 gv->vr_top = vfs_list_next(gv->vr_top);
430 gv->idx_top++;
433 if (gv->vr_top == NULL) {
434 gv->vr_top = vfs_list_last(gv->list);
435 gv->idx_top = vfs_list_items(gv->list);
437 gv->vr_selected = gv->vr_top;
438 gv->idx_selected = gv->idx_top;
440 gui_vfslist_cursor_adjust(gv);
441 if (vr_toptmp == gv->vr_top) {
443 * This action had no effect. We've reached the bottom,
444 * so move the cursor to the bottom then.
446 gui_vfslist_cursor_tail(gv);
447 } else {
448 gui_vfslist_refresh(gv);
453 * @brief Adjust our pointer to a VFS reference so it doesn't point to
454 * an object that is about to get removed.
456 static void
457 gui_vfslist_adjust_pre_removal(struct vfsref **vr, unsigned int *curidx,
458 unsigned int newidx, int follow)
460 struct vfsref *vr_new;
461 g_assert((*vr != NULL) && (*curidx > 0));
463 vr_new = vfs_list_next(*vr);
464 if (*curidx > newidx) {
465 /* Item got removed above us */
466 if (follow || vr_new == NULL) {
467 /* Follow our selection */
468 (*curidx)--;
469 } else {
470 /* Keep the current offset */
471 *vr = vr_new;
473 } else if (*curidx == newidx) {
474 /* Current item gets removed */
475 if (vr_new != NULL) {
476 /* Stay at the same position */
477 *vr = vr_new;
478 } else {
479 /* Cannot go down - go up */
480 *vr = vfs_list_prev(*vr);
481 (*curidx)--;
485 g_assert((*vr == NULL) == (*curidx == 0));
488 void
489 gui_vfslist_notify_pre_removal(struct gui_vfslist *gv, unsigned int index)
491 gui_vfslist_adjust_pre_removal(&gv->vr_top, &gv->idx_top, index, 0);
492 gui_vfslist_adjust_pre_removal(&gv->vr_selected, &gv->idx_selected,
493 index, 1);
497 * @brief Adjust a reference with index to an entry in the playlist,
498 * right after a new entry in the playlist gets inserted.
500 static void
501 gui_vfslist_adjust_post_insertion(struct gui_vfslist *gv, struct vfsref **vr,
502 unsigned int *curidx, unsigned int newidx, int follow)
504 g_assert((*vr == NULL) == (*curidx == 0));
506 if (*curidx == 0) {
507 /* List was empty - we take the first slot */
508 *vr = vfs_list_first(gv->list);
509 *curidx = 1;
510 } else if (*curidx >= newidx) {
511 /* It happened above us */
512 if (follow) {
513 /* Follow our selection */
514 (*curidx)++;
515 } else {
516 /* Keep the current offset */
517 *vr = vfs_list_prev(*vr);
521 g_assert((*vr != NULL) && (*curidx > 0));
524 void
525 gui_vfslist_notify_post_insertion(struct gui_vfslist *gv, unsigned int index)
527 gui_vfslist_adjust_post_insertion(gv, &gv->vr_top, &gv->idx_top,
528 index, 0);
529 gui_vfslist_adjust_post_insertion(gv, &gv->vr_selected,
530 &gv->idx_selected, index, 1);
533 void
534 gui_vfslist_notify_post_randomization(struct gui_vfslist *gv)
536 unsigned int idx;
539 * After a randomization the indices do not match the
540 * corresponding objects. We could trace the objects and update
541 * the indices, but we might as well better keep the current
542 * indices and update the objects. This is more convenient for
543 * the user.
545 if (gv->idx_top > 0) {
546 /* Top position */
547 for (idx = 1, gv->vr_top = vfs_list_first(gv->list);
548 idx < gv->idx_top;
549 idx++, gv->vr_top = vfs_list_next(gv->vr_top));
550 /* Selection - just continue previous traversal */
551 for (gv->vr_selected = gv->vr_top;
552 idx < gv->idx_selected;
553 idx++, gv->vr_selected = vfs_list_next(gv->vr_selected));
557 void
558 gui_vfslist_notify_done(struct gui_vfslist *gv)
560 gui_vfslist_refresh(gv);
564 gui_vfslist_searchnext(struct gui_vfslist *gv, const struct vfsmatch *vm)
566 struct vfsref *vr;
567 unsigned int idx;
569 g_assert(vm != NULL);
571 if (gv->vr_selected == NULL)
572 return (-1);
574 /* Step 1: search from selection to end */
575 for (vr = vfs_list_next(gv->vr_selected), idx = gv->idx_selected + 1;
576 vr != NULL; vr = vfs_list_next(vr), idx++) {
577 if (vfs_match_compare(vm, vfs_name(vr))) {
578 gui_msgbar_flush();
579 goto found;
583 /* Step 2: search from beginning to selection */
584 for (vr = vfs_list_first(gv->list), idx = 1;
585 vr != vfs_list_next(gv->vr_selected);
586 vr = vfs_list_next(vr), idx++) {
587 if (vfs_match_compare(vm, vfs_name(vr))) {
588 gui_msgbar_warn(_("Search wrapped to top."));
589 goto found;
593 return (-1);
595 found: /* Select our found item */
596 gv->vr_selected = vr;
597 gv->idx_selected = idx;
599 /* No need to redraw, as window switching is performed */
600 return (0);
603 void
604 gui_vfslist_fullpath(struct gui_vfslist *gv)
606 char *msg;
608 if (gui_vfslist_warn_isempty(gv))
609 return;
611 msg = g_strdup_printf("%s: %s",
612 _("Full pathname"), vfs_filename(gv->vr_selected));
613 gui_msgbar_warn(msg);
614 g_free(msg);