2 * Copyright (c) 2006-2011 Ed Schouten <ed@80386.nl>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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
28 * @brief Generic directory/playlist display for textual user interface.
35 #include "gui_internal.h"
36 #include "gui_vfslist.h"
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
;
46 * @brief Make sure the cursor resides inside the viewport. Move the
50 gui_vfslist_cursor_adjust(struct gui_vfslist
*gv
)
54 /* Shortcut - nothing to adjust when the list is empty */
55 if (gv
->vr_selected
== NULL
)
58 /* Three possible cases here */
59 if (gv
->idx_top
> gv
->idx_selected
) {
61 * The entry is above the viewport. Move the viewport up
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
;
76 vr
= vfs_list_prev(gv
->vr_top
);
81 } while (gv
->idx_top
+ gv
->winheight
- 1 > gv
->idx_selected
);
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
89 while (gv
->idx_top
+ gv
->winheight
- 1 > vfs_list_items(gv
->list
)) {
90 vr
= vfs_list_prev(gv
->vr_top
);
100 * @brief Calculate what width the column with the index numbers should
101 * have, based on the amount of songs in the playlist.
104 gui_vfslist_idxcol_width(struct gui_vfslist
*gv
)
106 unsigned int len
, idx
;
108 if (gv
->list
== NULL
)
112 * Calculate the number of digits needed to print the number of
115 for (len
= 1, idx
= vfs_list_items(gv
->list
);idx
>= 10;
122 * @brief Recalculate the percentage string.
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) ");
137 strcpy(gv
->percent
, " (end) ");
139 /* Write back a percentage */
140 sprintf(gv
->percent
, " (%d%%) ", (bottom
* 100) / length
);
145 * @brief Redraw the contents of the dialog.
148 gui_vfslist_refresh(struct gui_vfslist
*gv
)
150 unsigned int i
, idx
, idxw
, idxmaxw
= 0;
157 /* Make sure we have everything in sight */
158 gui_vfslist_cursor_adjust(gv
);
166 idxmaxw
= gui_vfslist_idxcol_width(gv
);
167 for (i
= 0; i
< gv
->winheight
; i
++) {
169 /* We've reached the bottom of the list */
170 mvwaddch(gv
->win
, i
, 0, ' ');
171 wbkgdset(gv
->win
, COLOR_PAIR(GUI_COLOR_BLOCK
));
177 g_assert((vr
== gv
->vr_selected
) == (idx
== gv
->idx_selected
));
179 if (vr
== gv
->vr_selected
&& gv
->winfocused
)
181 wbkgdset(gv
->win
, COLOR_PAIR(GUI_COLOR_SELECT
));
182 else if (vfs_marked(vr
))
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
) {
192 wattron(gv
->win
, A_BOLD
);
193 mvwaddch(gv
->win
, i
, 0, '>');
195 mvwaddch(gv
->win
, i
, 0, ' ');
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
));
205 waddstr(gv
->win
, vfs_name(vr
));
208 /* Marking character for dirs and such */
209 mark
= vfs_marking(vr
);
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
);
220 wnoutrefresh(gv
->win
);
223 gui_vfslist_percent(gv
);
225 if (gv
->callback
!= NULL
)
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
;
243 gui_vfslist_destroy(struct gui_vfslist
*gv
)
247 g_slice_free(struct gui_vfslist
, gv
);
251 gui_vfslist_setlist(struct gui_vfslist
*gv
, const struct vfslist
*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."));
272 gui_vfslist_move(struct gui_vfslist
*gv
,
273 int x
, int y
, int width
, int height
)
276 if (gv
->win
== NULL
) {
277 gv
->win
= newwin(height
, width
, y
, x
);
279 wresize(gv
->win
, height
, width
);
280 mvwin(gv
->win
, y
, x
);
282 clearok(gv
->win
, TRUE
);
284 gv
->winheight
= height
;
286 gui_vfslist_refresh(gv
);
290 gui_vfslist_setfocus(struct gui_vfslist
*gv
, int focus
)
292 gv
->winfocused
= focus
;
293 gui_vfslist_refresh(gv
);
297 gui_vfslist_setselected(struct gui_vfslist
*gv
, struct vfsref
*vr
,
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
);
312 gui_vfslist_refresh(gv
);
316 gui_vfslist_cursor_up(struct gui_vfslist
*gv
)
320 if (gui_vfslist_warn_isempty(gv
))
323 vr
= vfs_list_prev(gv
->vr_selected
);
325 gv
->vr_selected
= vr
;
328 if (scrollpages
&& gv
->idx_top
> gv
->idx_selected
) {
329 gv
->vr_top
= vfs_list_first(gv
->list
);
333 gui_vfslist_refresh(gv
);
335 gui_msgbar_warn(_("You are at the first song."));
340 gui_vfslist_cursor_down(struct gui_vfslist
*gv
, int silent
)
344 if (gui_vfslist_warn_isempty(gv
))
347 vr
= vfs_list_next(gv
->vr_selected
);
349 gv
->vr_selected
= vr
;
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."));
365 gui_vfslist_cursor_head(struct gui_vfslist
*gv
)
367 if (gui_vfslist_warn_isempty(gv
))
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
);
377 gui_vfslist_cursor_tail(struct gui_vfslist
*gv
)
379 if (gui_vfslist_warn_isempty(gv
))
382 gv
->vr_selected
= vfs_list_last(gv
->list
);
383 gv
->idx_selected
= vfs_list_items(gv
->list
);
385 gui_vfslist_refresh(gv
);
389 gui_vfslist_cursor_pageup(struct gui_vfslist
*gv
)
393 if (gui_vfslist_warn_isempty(gv
))
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
);
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
);
419 gui_vfslist_cursor_pagedown(struct gui_vfslist
*gv
)
422 struct vfsref
*vr_toptmp
= gv
->vr_top
;
424 if (gui_vfslist_warn_isempty(gv
))
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
);
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
);
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.
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 */
470 /* Keep the current offset */
473 } else if (*curidx
== newidx
) {
474 /* Current item gets removed */
475 if (vr_new
!= NULL
) {
476 /* Stay at the same position */
479 /* Cannot go down - go up */
480 *vr
= vfs_list_prev(*vr
);
485 g_assert((*vr
== NULL
) == (*curidx
== 0));
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
,
497 * @brief Adjust a reference with index to an entry in the playlist,
498 * right after a new entry in the playlist gets inserted.
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));
507 /* List was empty - we take the first slot */
508 *vr
= vfs_list_first(gv
->list
);
510 } else if (*curidx
>= newidx
) {
511 /* It happened above us */
513 /* Follow our selection */
516 /* Keep the current offset */
517 *vr
= vfs_list_prev(*vr
);
521 g_assert((*vr
!= NULL
) && (*curidx
> 0));
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
,
529 gui_vfslist_adjust_post_insertion(gv
, &gv
->vr_selected
,
530 &gv
->idx_selected
, index
, 1);
534 gui_vfslist_notify_post_randomization(struct gui_vfslist
*gv
)
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
545 if (gv
->idx_top
> 0) {
547 for (idx
= 1, gv
->vr_top
= vfs_list_first(gv
->list
);
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
));
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
)
569 g_assert(vm
!= NULL
);
571 if (gv
->vr_selected
== NULL
)
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
))) {
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."));
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 */
604 gui_vfslist_fullpath(struct gui_vfslist
*gv
)
608 if (gui_vfslist_warn_isempty(gv
))
611 msg
= g_strdup_printf("%s: %s",
612 _("Full pathname"), vfs_filename(gv
->vr_selected
));
613 gui_msgbar_warn(msg
);