2 * Copyright 2006 Timo Hirvonen
6 #include "search_mode.h"
13 struct searchable
*tree_searchable
;
14 struct window
*lib_tree_win
;
15 struct window
*lib_track_win
;
16 struct window
*lib_cur_win
;
17 LIST_HEAD(lib_artist_head
);
19 /* tree (search) iterators {{{ */
20 static int tree_search_get_prev(struct iter
*iter
)
22 struct list_head
*head
= iter
->data0
;
23 struct tree_track
*track
= iter
->data1
;
24 struct artist
*artist
;
31 /* head, get last track */
32 if (head
->prev
== head
) {
33 /* empty, iter points to the head already */
36 artist
= to_artist(head
->prev
);
37 album
= to_album(artist
->album_head
.prev
);
38 iter
->data1
= to_tree_track(album
->track_head
.prev
);
42 if (track
->node
.prev
== &track
->album
->track_head
|| search_restricted
) {
44 if (track
->album
->node
.prev
== &track
->album
->artist
->album_head
) {
46 if (track
->album
->artist
->node
.prev
== &lib_artist_head
)
48 artist
= to_artist(track
->album
->artist
->node
.prev
);
49 album
= to_album(artist
->album_head
.prev
);
50 track
= to_tree_track(album
->track_head
.prev
);
52 album
= to_album(track
->album
->node
.prev
);
53 track
= to_tree_track(album
->track_head
.prev
);
56 track
= to_tree_track(track
->node
.prev
);
62 static int tree_search_get_next(struct iter
*iter
)
64 struct list_head
*head
= iter
->data0
;
65 struct tree_track
*track
= iter
->data1
;
66 struct artist
*artist
;
73 /* head, get first track */
74 if (head
->next
== head
) {
75 /* empty, iter points to the head already */
78 artist
= to_artist(head
->next
);
79 album
= to_album(artist
->album_head
.next
);
80 iter
->data1
= to_tree_track(album
->track_head
.next
);
84 if (track
->node
.next
== &track
->album
->track_head
|| search_restricted
) {
86 if (track
->album
->node
.next
== &track
->album
->artist
->album_head
) {
88 if (track
->album
->artist
->node
.next
== &lib_artist_head
)
90 artist
= to_artist(track
->album
->artist
->node
.next
);
91 album
= to_album(artist
->album_head
.next
);
92 track
= to_tree_track(album
->track_head
.next
);
94 album
= to_album(track
->album
->node
.next
);
95 track
= to_tree_track(album
->track_head
.next
);
98 track
= to_tree_track(track
->node
.next
);
105 /* tree window iterators {{{ */
106 static int tree_get_prev(struct iter
*iter
)
108 struct list_head
*head
= iter
->data0
;
109 struct artist
*artist
= iter
->data1
;
110 struct album
*album
= iter
->data2
;
112 BUG_ON(head
== NULL
);
113 BUG_ON(artist
== NULL
&& album
!= NULL
);
114 if (artist
== NULL
) {
115 /* head, get last artist and/or album */
116 if (head
->prev
== head
) {
117 /* empty, iter points to the head already */
120 artist
= to_artist(head
->prev
);
121 if (artist
->expanded
) {
122 album
= to_album(artist
->album_head
.prev
);
126 iter
->data1
= artist
;
130 if (artist
->expanded
&& album
) {
132 if (album
->node
.prev
== &artist
->album_head
) {
136 iter
->data2
= to_album(album
->node
.prev
);
142 if (artist
->node
.prev
== &lib_artist_head
) {
147 artist
= to_artist(artist
->node
.prev
);
148 iter
->data1
= artist
;
150 if (artist
->expanded
) {
152 iter
->data2
= to_album(artist
->album_head
.prev
);
157 static int tree_get_next(struct iter
*iter
)
159 struct list_head
*head
= iter
->data0
;
160 struct artist
*artist
= iter
->data1
;
161 struct album
*album
= iter
->data2
;
163 BUG_ON(head
== NULL
);
164 BUG_ON(artist
== NULL
&& album
!= NULL
);
165 if (artist
== NULL
) {
166 /* head, get first artist */
167 if (head
->next
== head
) {
168 /* empty, iter points to the head already */
171 iter
->data1
= to_artist(head
->next
);
175 if (artist
->expanded
) {
179 iter
->data2
= to_album(artist
->album_head
.next
);
182 if (album
->node
.next
!= &artist
->album_head
) {
183 iter
->data2
= to_album(album
->node
.next
);
189 if (artist
->node
.next
== head
) {
194 iter
->data1
= to_artist(artist
->node
.next
);
200 static GENERIC_ITER_PREV(tree_track_get_prev
, struct tree_track
, node
)
201 static GENERIC_ITER_NEXT(tree_track_get_next
, struct tree_track
, node
)
203 static inline void tree_search_track_to_iter(struct tree_track
*track
, struct iter
*iter
)
205 iter
->data0
= &lib_artist_head
;
210 static inline void album_to_iter(struct album
*album
, struct iter
*iter
)
212 iter
->data0
= &lib_artist_head
;
213 iter
->data1
= album
->artist
;
217 static inline void artist_to_iter(struct artist
*artist
, struct iter
*iter
)
219 iter
->data0
= &lib_artist_head
;
220 iter
->data1
= artist
;
224 static inline void tree_track_to_iter(struct tree_track
*track
, struct iter
*iter
)
226 iter
->data0
= &track
->album
->track_head
;
231 /* search (tree) {{{ */
232 static int tree_search_get_current(void *data
, struct iter
*iter
)
234 struct artist
*artist
;
236 struct tree_track
*track
;
239 if (list_empty(&lib_artist_head
))
241 if (window_get_sel(lib_track_win
, &tmpiter
)) {
242 track
= iter_to_tree_track(&tmpiter
);
243 tree_search_track_to_iter(track
, iter
);
247 /* artist not expanded. track_win is empty
248 * set tmp to the first track of the selected artist */
249 window_get_sel(lib_tree_win
, &tmpiter
);
250 artist
= iter_to_artist(&tmpiter
);
251 album
= to_album(artist
->album_head
.next
);
252 track
= to_tree_track(album
->track_head
.next
);
253 tree_search_track_to_iter(track
, iter
);
257 static inline struct tree_track
*iter_to_tree_search_track(const struct iter
*iter
)
259 BUG_ON(iter
->data0
!= &lib_artist_head
);
263 static int tree_search_matches(void *data
, struct iter
*iter
, const char *text
)
265 struct tree_track
*track
;
267 unsigned int flags
= TI_MATCH_ARTIST
| TI_MATCH_ALBUM
;
269 if (!search_restricted
)
270 flags
|= TI_MATCH_TITLE
;
271 track
= iter_to_tree_search_track(iter
);
272 if (!track_info_matches(tree_track_info(track
), text
, flags
))
274 track
->album
->artist
->expanded
= 1;
275 album_to_iter(track
->album
, &tmpiter
);
276 window_set_sel(lib_tree_win
, &tmpiter
);
278 tree_track_to_iter(track
, &tmpiter
);
279 window_set_sel(lib_track_win
, &tmpiter
);
283 static const struct searchable_ops tree_search_ops
= {
284 .get_prev
= tree_search_get_prev
,
285 .get_next
= tree_search_get_next
,
286 .get_current
= tree_search_get_current
,
287 .matches
= tree_search_matches
289 /* search (tree) }}} */
291 static inline int album_selected(struct album
*album
)
295 if (window_get_sel(lib_tree_win
, &sel
))
296 return album
== iter_to_album(&sel
);
300 static void tree_sel_changed(void)
305 window_get_sel(lib_tree_win
, &sel
);
306 album
= iter_to_album(&sel
);
308 window_set_empty(lib_track_win
);
310 window_set_contents(lib_track_win
, &album
->track_head
);
314 static inline void tree_win_get_selected(struct artist
**artist
, struct album
**album
)
320 if (window_get_sel(lib_tree_win
, &sel
)) {
321 *artist
= iter_to_artist(&sel
);
322 *album
= iter_to_album(&sel
);
326 static void artist_free(struct artist
*artist
)
328 free(artist
->raw_name
);
333 static void album_free(struct album
*album
)
343 list_init(&lib_artist_head
);
345 lib_tree_win
= window_new(tree_get_prev
, tree_get_next
);
346 lib_track_win
= window_new(tree_track_get_prev
, tree_track_get_next
);
347 lib_cur_win
= lib_tree_win
;
349 lib_tree_win
->sel_changed
= tree_sel_changed
;
351 window_set_empty(lib_track_win
);
352 window_set_contents(lib_tree_win
, &lib_artist_head
);
354 iter
.data0
= &lib_artist_head
;
357 tree_searchable
= searchable_new(NULL
, &iter
, &tree_search_ops
);
360 struct track_info
*tree_set_selected(void)
362 struct artist
*artist
;
364 struct track_info
*info
;
367 if (list_empty(&lib_artist_head
))
370 tree_win_get_selected(&artist
, &album
);
372 /* only artist selected, track window is empty
373 * => get first album of the selected artist and first track of that album
375 album
= to_album(artist
->album_head
.next
);
376 lib_cur_track
= to_tree_track(album
->track_head
.next
);
378 window_get_sel(lib_track_win
, &sel
);
379 lib_cur_track
= iter_to_tree_track(&sel
);
382 lib_tree_win
->changed
= 1;
383 lib_track_win
->changed
= 1;
385 info
= tree_track_info(lib_cur_track
);
386 track_info_ref(info
);
390 static const char *artist_name_skip_the(const char *a
)
392 if (!strncasecmp(a
, "the ", 4)) {
394 while (*a
== ' ' || *a
== '\t')
400 static int artist_name_cmp(const char *a
, const char *b
)
402 a
= artist_name_skip_the(a
);
403 b
= artist_name_skip_the(b
);
405 return u_strcasecmp(a
, b
);
408 static void find_artist_and_album(const char *artist_raw_name
,
409 const char *album_name
, struct artist
**_artist
,
410 struct album
**_album
)
412 struct artist
*artist
;
415 list_for_each_entry(artist
, &lib_artist_head
, node
) {
418 res
= artist_name_cmp(artist
->raw_name
, artist_raw_name
);
421 list_for_each_entry(album
, &artist
->album_head
, node
) {
422 res
= u_strcasecmp(album
->name
, album_name
);
437 static int special_name_cmp(const char *a
, const char *b
)
439 /* keep <Stream> etc. top */
440 int cmp
= (*a
!= '<') - (*b
!= '<');
444 return u_strcasecmp(a
, b
);
447 static struct artist
*add_artist(const char *name
, const char *raw_name
)
449 struct list_head
*item
;
450 struct artist
*artist
;
452 artist
= xnew(struct artist
, 1);
453 artist
->name
= xstrdup(name
);
454 artist
->raw_name
= xstrdup(raw_name
);
455 list_init(&artist
->album_head
);
456 artist
->expanded
= 0;
458 list_for_each(item
, &lib_artist_head
) {
459 if (special_name_cmp(name
, to_artist(item
)->name
) < 0)
462 /* add before item */
463 list_add_tail(&artist
->node
, item
);
467 static struct album
*artist_add_album(struct artist
*artist
, const char *name
, int date
)
469 struct list_head
*item
;
472 album
= xnew(struct album
, 1);
473 album
->name
= xstrdup(name
);
475 list_init(&album
->track_head
);
476 album
->artist
= artist
;
478 list_for_each(item
, &artist
->album_head
) {
479 struct album
*a
= to_album(item
);
485 if (special_name_cmp(name
, a
->name
) < 0)
488 /* add before item */
489 list_add_tail(&album
->node
, item
);
493 static void album_add_track(struct album
*album
, struct tree_track
*track
)
496 * NOTE: This is not perfect. You should ignore track numbers if
497 * either is unset and use filename instead, but usually you
498 * have all track numbers set or all unset (within one album
501 static const char * const album_track_sort_keys
[] = {
502 "discnumber", "tracknumber", "filename", NULL
504 struct list_head
*item
;
506 track
->album
= album
;
507 list_for_each(item
, &album
->track_head
) {
508 const struct simple_track
*a
= (const struct simple_track
*)track
;
509 const struct simple_track
*b
= (const struct simple_track
*)to_tree_track(item
);
511 if (track_info_cmp(a
->info
, b
->info
, album_track_sort_keys
) < 0)
514 /* add before item */
515 list_add_tail(&track
->node
, item
);
518 void tree_add_track(struct tree_track
*track
)
520 const struct track_info
*ti
= tree_track_info(track
);
521 const char *album_name
, *artist_name
, *albumartist
,
522 *artist_sort
, *albumartist_sort
, *compilation
;
523 struct artist
*artist
;
527 album_name
= keyvals_get_val(ti
->comments
, "album");
528 artist_name
= keyvals_get_val(ti
->comments
, "artist");
529 albumartist
= keyvals_get_val(ti
->comments
, "albumartist");
530 artist_sort
= keyvals_get_val(ti
->comments
, "artistsort");
531 albumartist_sort
= keyvals_get_val(ti
->comments
, "albumartistsort");
532 compilation
= keyvals_get_val(ti
->comments
, "compilation");
534 if (is_url(ti
->filename
)) {
535 artist_name
= "<Stream>";
536 album_name
= "<Stream>";
539 if (artist_name
== NULL
)
540 artist_name
= "<No Name>";
541 if (album_name
== NULL
)
542 album_name
= "<No Name>";
544 if (compilation
&& (!strcasecmp(compilation
, "1")
545 || !strcasecmp(compilation
, "yes"))) {
546 /* Store all compilations under compilations */
547 artist_name
= "<Compilations>";
550 find_artist_and_album(artist_name
, album_name
, &artist
, &album
);
552 album_add_track(album
, track
);
554 /* is the album where we added the track selected? */
555 if (album_selected(album
)) {
556 /* update track window */
557 window_changed(lib_track_win
);
560 date
= comments_get_date(ti
->comments
, "date");
561 album
= artist_add_album(artist
, album_name
, date
);
562 album_add_track(album
, track
);
564 if (artist
->expanded
) {
565 /* update tree window */
566 window_changed(lib_tree_win
);
567 /* album is not selected => no need to update track_win */
570 const char *artist_name_fancy
= NULL
;
571 const char *artist_name_no_the
= artist_name_skip_the(artist_name
);
573 if (albumartist_sort
)
574 artist_name_fancy
= albumartist_sort
;
575 else if (albumartist
)
576 artist_name_fancy
= albumartist
;
577 else if (artist_sort
)
578 artist_name_fancy
= artist_sort
;
580 if (artist_name_fancy
)
581 artist
= add_artist(artist_name_fancy
, artist_name
);
582 else if (artist_name_no_the
== artist_name
)
583 artist
= add_artist(artist_name
, artist_name
);
585 char *artist_name_full
;
587 artist_name_full
= xstrjoin(artist_name_no_the
, ", The");
588 artist
= add_artist(artist_name_full
, artist_name
);
589 free(artist_name_full
);
592 date
= comments_get_date(ti
->comments
, "date");
593 album
= artist_add_album(artist
, album_name
, date
);
594 album_add_track(album
, track
);
596 window_changed(lib_tree_win
);
600 static void remove_sel_artist(struct artist
*artist
)
602 struct list_head
*aitem
, *ahead
;
604 ahead
= &artist
->album_head
;
606 while (aitem
!= ahead
) {
607 struct list_head
*titem
, *thead
;
608 struct list_head
*anext
= aitem
->next
;
609 struct album
*album
= to_album(aitem
);
611 thead
= &album
->track_head
;
613 while (titem
!= thead
) {
614 struct list_head
*tnext
= titem
->next
;
615 struct tree_track
*track
= to_tree_track(titem
);
617 editable_remove_track(&lib_editable
, (struct simple_track
*)track
);
620 /* all tracks removed => album removed
621 * if the last album was removed then the artist was removed too
627 static void remove_sel_album(struct album
*album
)
629 struct list_head
*item
, *head
;
631 head
= &album
->track_head
;
633 while (item
!= head
) {
634 struct list_head
*next
= item
->next
;
635 struct tree_track
*track
= to_tree_track(item
);
637 editable_remove_track(&lib_editable
, (struct simple_track
*)track
);
642 static void tree_win_remove_sel(void)
644 struct artist
*artist
;
647 tree_win_get_selected(&artist
, &album
);
649 remove_sel_album(album
);
651 remove_sel_artist(artist
);
655 static void track_win_remove_sel(void)
658 struct tree_track
*track
;
660 if (window_get_sel(lib_track_win
, &sel
)) {
661 track
= iter_to_tree_track(&sel
);
662 BUG_ON(track
== NULL
);
663 editable_remove_track(&lib_editable
, (struct simple_track
*)track
);
667 void tree_toggle_active_window(void)
669 if (lib_cur_win
== lib_tree_win
) {
670 struct artist
*artist
;
673 tree_win_get_selected(&artist
, &album
);
675 lib_cur_win
= lib_track_win
;
676 lib_tree_win
->changed
= 1;
677 lib_track_win
->changed
= 1;
679 } else if (lib_cur_win
== lib_track_win
) {
680 lib_cur_win
= lib_tree_win
;
681 lib_tree_win
->changed
= 1;
682 lib_track_win
->changed
= 1;
686 void tree_toggle_expand_artist(void)
689 struct artist
*artist
;
691 window_get_sel(lib_tree_win
, &sel
);
692 artist
= iter_to_artist(&sel
);
694 if (artist
->expanded
) {
695 /* deselect album, select artist */
696 artist_to_iter(artist
, &sel
);
697 window_set_sel(lib_tree_win
, &sel
);
699 artist
->expanded
= 0;
700 lib_cur_win
= lib_tree_win
;
702 artist
->expanded
= 1;
704 window_changed(lib_tree_win
);
708 static void remove_track(struct tree_track
*track
)
710 if (album_selected(track
->album
)) {
713 tree_track_to_iter(track
, &iter
);
714 window_row_vanishes(lib_track_win
, &iter
);
716 list_del(&track
->node
);
719 static void remove_album(struct album
*album
)
721 if (album
->artist
->expanded
) {
724 album_to_iter(album
, &iter
);
725 window_row_vanishes(lib_tree_win
, &iter
);
727 list_del(&album
->node
);
730 static void remove_artist(struct artist
*artist
)
734 artist_to_iter(artist
, &iter
);
735 window_row_vanishes(lib_tree_win
, &iter
);
736 list_del(&artist
->node
);
739 void tree_remove(struct tree_track
*track
)
741 struct album
*album
= track
->album
;
742 struct artist
*sel_artist
;
743 struct album
*sel_album
;
745 tree_win_get_selected(&sel_artist
, &sel_album
);
748 /* don't free the track */
750 if (list_empty(&album
->track_head
)) {
751 struct artist
*artist
= album
->artist
;
753 if (sel_album
== album
)
754 lib_cur_win
= lib_tree_win
;
759 if (list_empty(&artist
->album_head
)) {
760 artist
->expanded
= 0;
761 remove_artist(artist
);
767 void tree_remove_sel(void)
769 if (lib_cur_win
== lib_tree_win
) {
770 tree_win_remove_sel();
772 track_win_remove_sel();
776 void tree_sel_current(void)
781 CUR_ARTIST
->expanded
= 1;
783 if (lib_cur_win
== lib_tree_win
) {
784 lib_cur_win
= lib_track_win
;
785 lib_tree_win
->changed
= 1;
786 lib_track_win
->changed
= 1;
789 album_to_iter(CUR_ALBUM
, &iter
);
790 window_set_sel(lib_tree_win
, &iter
);
792 tree_track_to_iter(lib_cur_track
, &iter
);
793 window_set_sel(lib_track_win
, &iter
);
797 static int album_for_each_track(struct album
*album
, int (*cb
)(void *data
, struct track_info
*ti
),
798 void *data
, int reverse
)
800 struct tree_track
*track
;
804 list_for_each_entry_reverse(track
, &album
->track_head
, node
) {
805 rc
= cb(data
, tree_track_info(track
));
810 list_for_each_entry(track
, &album
->track_head
, node
) {
811 rc
= cb(data
, tree_track_info(track
));
819 static int artist_for_each_track(struct artist
*artist
, int (*cb
)(void *data
, struct track_info
*ti
),
820 void *data
, int reverse
)
826 list_for_each_entry_reverse(album
, &artist
->album_head
, node
) {
827 rc
= album_for_each_track(album
, cb
, data
, 1);
832 list_for_each_entry(album
, &artist
->album_head
, node
) {
833 rc
= album_for_each_track(album
, cb
, data
, 0);
841 int __tree_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
845 if (lib_cur_win
== lib_tree_win
) {
846 struct artist
*artist
;
849 tree_win_get_selected(&artist
, &album
);
852 rc
= artist_for_each_track(artist
, cb
, data
, reverse
);
854 rc
= album_for_each_track(album
, cb
, data
, reverse
);
859 struct tree_track
*track
;
861 if (window_get_sel(lib_track_win
, &sel
)) {
862 track
= iter_to_tree_track(&sel
);
863 rc
= cb(data
, tree_track_info(track
));
869 int tree_for_each_sel(int (*cb
)(void *data
, struct track_info
*ti
), void *data
, int reverse
)
871 int rc
= __tree_for_each_sel(cb
, data
, reverse
);
873 window_down(lib_cur_win
, 1);