1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2005 by Miika Pekkarinen
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
21 * Basic structure on this file was copied from dbtree.c and modified to
22 * support the tag cache interface.
46 #define FILE_SEARCH_INSTRUCTIONS ROCKBOX_DIR "/tagnavi.config"
48 static int tagtree_play_folder(struct tree_context
* c
);
50 static char searchstring
[32];
52 /* Capacity 10 000 entries (for example 10k different artists) */
53 #define UNIQBUF_SIZE (64*1024)
59 * "%3d. %s" autoscore title
62 * formatstr = "%-3d. %s"
63 * tags[0] = tag_autoscore
67 struct display_format
{
74 struct search_instruction
{
76 int tagorder
[MAX_TAGS
];
78 struct tagcache_search_clause clause
[MAX_TAGS
][TAGCACHE_MAX_CLAUSES
];
79 struct display_format format
[MAX_TAGS
];
80 int clause_count
[MAX_TAGS
];
81 int result_seek
[MAX_TAGS
];
84 static struct search_instruction
*si
, *csi
;
85 static int si_count
= 0;
86 static const char *strp
;
88 static int current_offset
;
89 static int current_entry_count
;
91 static struct tree_context
*tc
;
93 static int get_token_str(char *buf
, int size
)
96 while (*strp
!= '"' && *strp
!= '\0')
99 if (*strp
== '\0' || *(++strp
) == '\0')
103 while (*strp
!= '"' && *strp
!= '\0' && --size
> 0)
104 *(buf
++) = *(strp
++);
115 #define MATCH(tag,str1,str2,settag) \
116 if (!strcasecmp(str1, str2)) { \
121 static int get_tag(int *tag
)
126 /* Find the start. */
127 while (*strp
== ' ' && *strp
!= '\0')
133 for (i
= 0; i
< (int)sizeof(buf
)-1; i
++)
135 if (*strp
== '\0' || *strp
== ' ')
142 MATCH(tag
, buf
, "album", tag_album
);
143 MATCH(tag
, buf
, "artist", tag_artist
);
144 MATCH(tag
, buf
, "bitrate", tag_bitrate
);
145 MATCH(tag
, buf
, "composer", tag_composer
);
146 MATCH(tag
, buf
, "genre", tag_genre
);
147 MATCH(tag
, buf
, "length", tag_length
);
148 MATCH(tag
, buf
, "title", tag_title
);
149 MATCH(tag
, buf
, "filename", tag_filename
);
150 MATCH(tag
, buf
, "tracknum", tag_tracknumber
);
151 MATCH(tag
, buf
, "year", tag_year
);
152 MATCH(tag
, buf
, "playcount", tag_playcount
);
153 MATCH(tag
, buf
, "autoscore", tag_virt_autoscore
);
155 logf("NO MATCH: %s\n", buf
);
162 static int get_clause(int *condition
)
167 /* Find the start. */
168 while (*strp
== ' ' && *strp
!= '\0')
174 for (i
= 0; i
< (int)sizeof(buf
)-1; i
++)
176 if (*strp
== '\0' || *strp
== ' ')
183 MATCH(condition
, buf
, "=", clause_is
);
184 MATCH(condition
, buf
, "==", clause_is
);
185 MATCH(condition
, buf
, "!=", clause_is_not
);
186 MATCH(condition
, buf
, ">", clause_gt
);
187 MATCH(condition
, buf
, ">=", clause_gteq
);
188 MATCH(condition
, buf
, "<", clause_lt
);
189 MATCH(condition
, buf
, "<=", clause_lteq
);
190 MATCH(condition
, buf
, "~", clause_contains
);
191 MATCH(condition
, buf
, "!~", clause_not_contains
);
192 MATCH(condition
, buf
, "^", clause_begins_with
);
193 MATCH(condition
, buf
, "!^", clause_not_begins_with
);
194 MATCH(condition
, buf
, "$", clause_ends_with
);
195 MATCH(condition
, buf
, "!$", clause_not_ends_with
);
200 static bool add_clause(struct search_instruction
*inst
,
201 int tag
, int type
, const char *str
)
203 int len
= strlen(str
);
204 struct tagcache_search_clause
*clause
;
206 if (inst
->clause_count
[inst
->tagorder_count
] >= TAGCACHE_MAX_CLAUSES
)
208 logf("Too many clauses");
212 clause
= &inst
->clause
[inst
->tagorder_count
]
213 [inst
->clause_count
[inst
->tagorder_count
]];
214 if (len
>= (int)sizeof(clause
->str
) - 1)
216 logf("Too long str in condition");
223 clause
->input
= true;
225 clause
->input
= false;
227 if (tagcache_is_numeric_tag(tag
))
229 clause
->numeric
= true;
230 clause
->numeric_data
= atoi(str
);
234 clause
->numeric
= false;
235 strcpy(clause
->str
, str
);
238 inst
->clause_count
[inst
->tagorder_count
]++;
243 static int get_format_str(struct display_format
*fmt
)
247 memset(fmt
, 0, sizeof(struct display_format
));
249 if (get_token_str(fmt
->formatstr
, sizeof fmt
->formatstr
) < 0)
252 while (fmt
->tag_count
< MAX_TAGS
)
254 ret
= get_tag(&fmt
->tags
[fmt
->tag_count
]);
269 static int get_condition(struct search_instruction
*inst
)
271 struct display_format format
;
272 struct display_format
*fmt
= NULL
;
280 if (get_format_str(&format
) < 0)
282 logf("get_format_str() parser failed!");
301 memcpy(&inst
->format
[inst
->tagorder_count
], fmt
,
302 sizeof(struct display_format
));
305 inst
->format
[inst
->tagorder_count
].valid
= false;
307 if (get_tag(&tag
) <= 0)
310 if (get_clause(&condition
) <= 0)
313 if (get_token_str(buf
, sizeof buf
) < 0)
316 logf("got clause: %d/%d [%s]", tag
, condition
, buf
);
317 add_clause(inst
, tag
, condition
, buf
);
323 * "Best" artist ? year >= "2000" & title !^ "crap" & genre = "good genre" \
324 * : album ? year >= "2000" : songs
330 static bool parse_search(struct search_instruction
*inst
, const char *str
)
334 memset(inst
, 0, sizeof(struct search_instruction
));
337 if (get_token_str(inst
->name
, sizeof inst
->name
) < 0)
339 logf("No name found.");
343 while (inst
->tagorder_count
< MAX_TAGS
)
345 ret
= get_tag(&inst
->tagorder
[inst
->tagorder_count
]);
348 logf("Parse error #1");
355 logf("tag: %d", inst
->tagorder
[inst
->tagorder_count
]);
357 while (get_condition(inst
) > 0) ;
359 inst
->tagorder_count
++;
366 static struct tagcache_search tcs
, tcs2
;
368 static int compare(const void *p1
, const void *p2
)
370 struct tagentry
*e1
= (struct tagentry
*)p1
;
371 struct tagentry
*e2
= (struct tagentry
*)p2
;
373 return strncasecmp(e1
->name
, e2
->name
, MAX_PATH
);
376 static void tagtree_buffer_event(struct mp3entry
*id3
, bool last_track
)
381 logf("be:%d%s", last_track
, id3
->path
);
384 static void tagtree_unbuffer_event(struct mp3entry
*id3
, bool last_track
)
391 /* Do not gather data unless proper setting has been enabled. */
392 if (!global_settings
.runtimedb
)
394 logf("runtimedb gathering not enabled");
398 /* Don't process unplayed tracks. */
399 if (id3
->elapsed
== 0)
401 logf("not logging unplayed track");
405 if (!tagcache_find_index(&tcs
, id3
->path
))
407 logf("tc stat: not found: %s", id3
->path
);
411 playcount
= tagcache_get_numeric(&tcs
, tag_playcount
);
412 playtime
= tagcache_get_numeric(&tcs
, tag_playtime
);
413 lastplayed
= tagcache_get_numeric(&tcs
, tag_lastplayed
);
417 lastplayed
= tagcache_increase_serial();
420 logf("incorrect tc serial:%d", lastplayed
);
421 tagcache_search_finish(&tcs
);
425 /* Ignore the last 15s (crossfade etc.) */
426 playtime
+= MIN(id3
->length
, id3
->elapsed
+ 15 * 1000);
428 logf("ube:%s", id3
->path
);
429 logf("-> %d/%d/%d", last_track
, playcount
, playtime
);
430 logf("-> %d/%d/%d", id3
->elapsed
, id3
->length
, MIN(id3
->length
, id3
->elapsed
+ 15 * 1000));
432 /* lastplayed not yet supported. */
434 if (!tagcache_modify_numeric_entry(&tcs
, tag_playcount
, playcount
)
435 || !tagcache_modify_numeric_entry(&tcs
, tag_playtime
, playtime
)
436 || !tagcache_modify_numeric_entry(&tcs
, tag_lastplayed
, lastplayed
))
438 logf("tc stat: modify failed!");
439 tagcache_search_finish(&tcs
);
443 tagcache_search_finish(&tcs
);
446 bool tagtree_export(void)
448 gui_syncsplash(0, true, str(LANG_CREATING
));
449 if (!tagcache_create_changelog(&tcs
))
451 gui_syncsplash(HZ
*2, true, str(LANG_FAILED
));
457 bool tagtree_import(void)
459 gui_syncsplash(0, true, str(LANG_WAIT
));
460 if (!tagcache_import_changelog())
462 gui_syncsplash(HZ
*2, true, str(LANG_FAILED
));
468 void tagtree_init(void)
475 fd
= open(FILE_SEARCH_INSTRUCTIONS
, O_RDONLY
);
478 logf("Search instruction file not found.");
482 /* Pre-pass search instructions file to count how many entries */
486 rc
= read_line(fd
, buf
, sizeof(buf
)-1);
492 /* Allocate memory for searches */
493 si
= (struct search_instruction
*) buffer_alloc(sizeof(struct search_instruction
) * line_count
+ 4);
495 /* Now read file for real, parsing into si */
496 lseek(fd
, 0L, SEEK_SET
);
499 rc
= read_line(fd
, buf
, sizeof(buf
)-1);
502 if (!parse_search(si
+ si_count
, buf
))
508 uniqbuf
= buffer_alloc(UNIQBUF_SIZE
);
509 audio_set_track_buffer_event(tagtree_buffer_event
);
510 audio_set_track_unbuffer_event(tagtree_unbuffer_event
);
513 bool show_search_progress(bool init
, int count
)
515 static int last_tick
= 0;
519 last_tick
= current_tick
;
523 if (current_tick
- last_tick
> HZ
/4)
525 gui_syncsplash(0, true, str(LANG_PLAYLIST_SEARCH_MSG
), count
,
526 #if CONFIG_KEYPAD == PLAYER_PAD
532 if (action_userabort(TIMEOUT_NOBLOCK
))
534 last_tick
= current_tick
;
541 int retrieve_entries(struct tree_context
*c
, struct tagcache_search
*tcs
,
542 int offset
, bool init
)
544 struct tagentry
*dptr
= (struct tagentry
*)c
->dircache
;
548 int special_entry_count
= 0;
549 int level
= c
->currextra
;
554 #ifdef HAVE_TC_RAMCACHE
555 && !tagcache_is_ramcache()
559 show_search_progress(true, 0);
560 gui_syncsplash(0, true, str(LANG_PLAYLIST_SEARCH_MSG
),
564 if (c
->currtable
== allsubentries
)
570 tag
= csi
->tagorder
[level
];
572 if (!tagcache_search(tcs
, tag
))
575 /* Prevent duplicate entries in the search list. */
576 tagcache_search_set_uniqbuf(tcs
, uniqbuf
, UNIQBUF_SIZE
);
578 if (level
|| csi
->clause_count
[0] || tagcache_is_numeric_tag(tag
))
581 for (i
= 0; i
< level
; i
++)
583 if (tagcache_is_numeric_tag(csi
->tagorder
[i
]))
585 static struct tagcache_search_clause cc
;
587 memset(&cc
, 0, sizeof(struct tagcache_search_clause
));
588 cc
.tag
= csi
->tagorder
[i
];
591 cc
.numeric_data
= csi
->result_seek
[i
];
592 tagcache_search_add_clause(tcs
, &cc
);
596 tagcache_search_add_filter(tcs
, csi
->tagorder
[i
],
597 csi
->result_seek
[i
]);
601 for (i
= 0; i
<= level
; i
++)
605 for (j
= 0; j
< csi
->clause_count
[i
]; j
++)
606 tagcache_search_add_clause(tcs
, &csi
->clause
[i
][j
]);
609 current_offset
= offset
;
610 current_entry_count
= 0;
613 if (tag
!= tag_title
&& tag
!= tag_filename
)
617 dptr
->newtable
= allsubentries
;
618 dptr
->name
= str(LANG_TAGNAVI_ALL_TRACKS
);
620 current_entry_count
++;
622 special_entry_count
++;
625 total_count
+= special_entry_count
;
627 while (tagcache_get_next(tcs
))
629 struct display_format
*fmt
= &csi
->format
[level
];
631 if (total_count
++ < offset
)
634 dptr
->newtable
= navibrowse
;
635 dptr
->extraseek
= tcs
->result_seek
;
636 if (tag
== tag_title
|| tag
== tag_filename
)
637 dptr
->newtable
= playtrack
;
639 if (!tcs
->ramsearch
|| fmt
->valid
640 || tagcache_is_numeric_tag(tag
))
648 bool read_format
= false;
652 memset(buf
, 0, sizeof buf
);
653 for (i
= 0; fmt
->formatstr
[i
] != '\0'; i
++)
655 if (fmt
->formatstr
[i
] == '%')
659 if (parpos
>= fmt
->tag_count
)
661 logf("too many format tags");
668 fmtbuf
[fmtbuf_pos
++] = fmt
->formatstr
[i
];
669 if (fmtbuf_pos
>= (long)sizeof(fmtbuf
))
671 logf("format parse error");
675 if (fmt
->formatstr
[i
] == 's')
677 fmtbuf
[fmtbuf_pos
] = '\0';
679 snprintf(&buf
[buf_pos
], MAX_PATH
- buf_pos
, fmtbuf
, tcs
->result
);
680 buf_pos
+= strlen(&buf
[buf_pos
]);
683 else if (fmt
->formatstr
[i
] == 'd')
685 fmtbuf
[fmtbuf_pos
] = '\0';
687 snprintf(&buf
[buf_pos
], MAX_PATH
- buf_pos
, fmtbuf
,
688 tagcache_get_numeric(tcs
, fmt
->tags
[parpos
]));
689 buf_pos
+= strlen(&buf
[buf_pos
]);
695 buf
[buf_pos
++] = fmt
->formatstr
[i
];
697 if (buf_pos
- 1 >= (long)sizeof(buf
))
699 logf("buffer overflow");
704 buf
[buf_pos
++] = '\0';
707 dptr
->name
= &c
->name_buffer
[namebufused
];
709 namebufused
+= buf_pos
;
711 namebufused
+= tcs
->result_len
;
713 if (namebufused
>= c
->name_buffer_size
)
715 logf("chunk mode #2: %d", current_entry_count
);
721 strcpy(dptr
->name
, buf
);
723 strcpy(dptr
->name
, tcs
->result
);
726 dptr
->name
= tcs
->result
;
729 current_entry_count
++;
731 if (current_entry_count
>= global_settings
.max_files_in_dir
)
733 logf("chunk mode #3: %d", current_entry_count
);
739 if (init
&& !tcs
->ramsearch
)
741 if (!show_search_progress(false, i
))
743 tagcache_search_finish(tcs
);
744 return current_entry_count
;
750 qsort(c
->dircache
+ special_entry_count
* c
->dentry_size
,
751 current_entry_count
- special_entry_count
,
752 c
->dentry_size
, compare
);
756 tagcache_search_finish(tcs
);
757 return current_entry_count
;
760 while (tagcache_get_next(tcs
))
764 if (!show_search_progress(false, total_count
))
770 tagcache_search_finish(tcs
);
774 static int load_root(struct tree_context
*c
)
776 struct tagentry
*dptr
= (struct tagentry
*)c
->dircache
;
781 for (i
= 0; i
< si_count
; i
++)
783 dptr
->name
= (si
+i
)->name
;
784 dptr
->newtable
= navibrowse
;
790 current_entry_count
= i
;
795 int tagtree_load(struct tree_context
* c
)
798 int table
= c
->currtable
;
800 c
->dentry_size
= sizeof(struct tagentry
);
806 c
->currtable
= table
;
812 count
= load_root(c
);
818 logf("navibrowse...");
820 count
= retrieve_entries(c
, &tcs
, 0, true);
825 logf("Unsupported table %d\n", table
);
832 count
= load_root(c
);
833 gui_syncsplash(HZ
, true, str(LANG_TAGCACHE_BUSY
));
836 /* The _total_ numer of entries available. */
837 c
->dirlength
= c
->filesindir
= count
;
842 int tagtree_enter(struct tree_context
* c
)
845 struct tagentry
*dptr
;
849 dptr
= tagtree_get_entry(c
, c
->selected_item
);
852 newextra
= dptr
->newtable
;
853 seek
= dptr
->extraseek
;
855 if (c
->dirlevel
>= MAX_DIR_LEVELS
)
858 c
->selected_item_history
[c
->dirlevel
]=c
->selected_item
;
859 c
->table_history
[c
->dirlevel
] = c
->currtable
;
860 c
->extra_history
[c
->dirlevel
] = c
->currextra
;
861 c
->pos_history
[c
->dirlevel
] = c
->firstpos
;
864 switch (c
->currtable
) {
866 c
->currextra
= newextra
;
868 if (newextra
== navibrowse
)
875 /* Read input as necessary. */
876 for (i
= 0; i
< csi
->tagorder_count
; i
++)
878 for (j
= 0; j
< csi
->clause_count
[i
]; j
++)
880 if (!csi
->clause
[i
][j
].input
)
883 rc
= kbd_input(searchstring
, sizeof(searchstring
));
884 if (rc
== -1 || !searchstring
[0])
890 if (csi
->clause
[i
][j
].numeric
)
891 csi
->clause
[i
][j
].numeric_data
= atoi(searchstring
);
893 strncpy(csi
->clause
[i
][j
].str
, searchstring
,
894 sizeof(csi
->clause
[i
][j
].str
)-1);
898 c
->currtable
= newextra
;
904 if (newextra
== playtrack
)
907 /* about to create a new current playlist...
908 allow user to cancel the operation */
909 if (global_settings
.warnon_erase_dynplaylist
&&
910 !global_settings
.party_mode
&&
911 playlist_modified(NULL
))
913 char *lines
[]={str(LANG_WARN_ERASEDYNPLAYLIST_PROMPT
)};
914 struct text_message message
={lines
, 1};
916 if (gui_syncyesno_run(&message
, NULL
, NULL
) != YESNO_YES
)
920 if (tagtree_play_folder(c
) >= 0)
925 c
->currtable
= newextra
;
926 csi
->result_seek
[c
->currextra
] = seek
;
927 if (c
->currextra
< csi
->tagorder_count
-1)
939 gui_synclist_select_item(&tree_lists
, c
->selected_item
);
944 void tagtree_exit(struct tree_context
* c
)
948 c
->selected_item
=c
->selected_item_history
[c
->dirlevel
];
949 gui_synclist_select_item(&tree_lists
, c
->selected_item
);
950 c
->currtable
= c
->table_history
[c
->dirlevel
];
951 c
->currextra
= c
->extra_history
[c
->dirlevel
];
952 c
->firstpos
= c
->pos_history
[c
->dirlevel
];
955 int tagtree_get_filename(struct tree_context
* c
, char *buf
, int buflen
)
957 struct tagentry
*entry
;
959 entry
= tagtree_get_entry(c
, c
->selected_item
);
961 if (!tagcache_search(&tcs
, tag_filename
))
964 if (!tagcache_retrieve(&tcs
, entry
->extraseek
, buf
, buflen
))
966 tagcache_search_finish(&tcs
);
970 tagcache_search_finish(&tcs
);
975 bool insert_all_playlist(struct tree_context
*c
, int position
, bool queue
)
979 int from
, to
, direction
;
982 if (!tagcache_search(&tcs
, tag_filename
))
984 gui_syncsplash(HZ
, true, str(LANG_TAGCACHE_BUSY
));
988 if (position
== PLAYLIST_INSERT_FIRST
)
990 from
= c
->filesindir
- 1;
1001 for (i
= from
; i
!= to
; i
+= direction
)
1003 if (!show_search_progress(false, i
))
1006 if (!tagcache_retrieve(&tcs
, tagtree_get_entry(c
, i
)->extraseek
,
1012 if (playlist_insert_track(NULL
, buf
, position
, queue
, false) < 0)
1014 logf("playlist_insert_track failed");
1019 playlist_sync(NULL
);
1020 tagcache_search_finish(&tcs
);
1026 bool tagtree_insert_selection_playlist(int position
, bool queue
)
1028 struct tagentry
*dptr
;
1030 int dirlevel
= tc
->dirlevel
;
1032 /* We need to set the table to allsubentries. */
1033 show_search_progress(true, 0);
1035 dptr
= tagtree_get_entry(tc
, tc
->selected_item
);
1037 /* Insert a single track? */
1038 if (dptr
->newtable
== playtrack
)
1040 if (tagtree_get_filename(tc
, buf
, sizeof buf
) < 0)
1042 logf("tagtree_get_filename failed");
1045 playlist_insert_track(NULL
, buf
, position
, queue
, true);
1050 if (dptr
->newtable
== navibrowse
)
1054 dptr
= tagtree_get_entry(tc
, tc
->selected_item
);
1056 else if (dptr
->newtable
!= allsubentries
)
1058 logf("unsupported table: %d", dptr
->newtable
);
1062 /* Now the current table should be allsubentries. */
1063 if (dptr
->newtable
!= playtrack
)
1067 dptr
= tagtree_get_entry(tc
, tc
->selected_item
);
1069 /* And now the newtable should be playtrack. */
1070 if (dptr
->newtable
!= playtrack
)
1072 logf("newtable: %d !!", dptr
->newtable
);
1073 tc
->dirlevel
= dirlevel
;
1078 if (tc
->filesindir
<= 0)
1079 gui_syncsplash(HZ
, true, str(LANG_END_PLAYLIST_PLAYER
));
1082 logf("insert_all_playlist");
1083 if (!insert_all_playlist(tc
, position
, queue
))
1084 gui_syncsplash(HZ
*2, true, str(LANG_FAILED
));
1087 /* Finally return the dirlevel to its original value. */
1088 while (tc
->dirlevel
> dirlevel
)
1095 static int tagtree_play_folder(struct tree_context
* c
)
1097 if (playlist_create(NULL
, NULL
) < 0)
1099 logf("Failed creating playlist\n");
1103 if (!insert_all_playlist(c
, PLAYLIST_INSERT
, false))
1106 if (global_settings
.playlist_shuffle
)
1107 c
->selected_item
= playlist_shuffle(current_tick
, c
->selected_item
);
1108 if (!global_settings
.play_selected
)
1109 c
->selected_item
= 0;
1110 gui_synclist_select_item(&tree_lists
, c
->selected_item
);
1112 playlist_start(c
->selected_item
,0);
1117 struct tagentry
* tagtree_get_entry(struct tree_context
*c
, int id
)
1119 struct tagentry
*entry
= (struct tagentry
*)c
->dircache
;
1120 int realid
= id
- current_offset
;
1122 /* Load the next chunk if necessary. */
1123 if (realid
>= current_entry_count
|| realid
< 0)
1126 if (retrieve_entries(c
, &tcs2
, MAX(0, id
- (current_entry_count
/ 2)),
1129 logf("retrieve failed");
1132 realid
= id
- current_offset
;
1136 return &entry
[realid
];
1139 int tagtree_get_attr(struct tree_context
* c
)
1142 switch (c
->currtable
)
1145 if (csi
->tagorder
[c
->currextra
] == tag_title
)
1146 attr
= TREE_ATTR_MPA
;
1148 attr
= ATTR_DIRECTORY
;
1152 attr
= TREE_ATTR_MPA
;
1156 attr
= ATTR_DIRECTORY
;
1163 #ifdef HAVE_LCD_BITMAP
1164 const unsigned char* tagtree_get_icon(struct tree_context
* c
)
1166 int tagtree_get_icon(struct tree_context
* c
)
1169 int icon
= Icon_Folder
;
1171 if (tagtree_get_attr(c
) == TREE_ATTR_MPA
)
1174 #ifdef HAVE_LCD_BITMAP
1175 return bitmap_icons_6x8
[icon
];