remove the need for action_signalscreenchange().
[Rockbox.git] / apps / tree.c
blob5b159d14bc5de76be94b9f5f3de9874b1dee3187
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Daniel Stenberg
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 ****************************************************************************/
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
24 #include "applimits.h"
25 #include "dir.h"
26 #include "file.h"
27 #include "lcd.h"
28 #include "font.h"
29 #include "backlight.h"
30 #include "button.h"
31 #include "kernel.h"
32 #include "usb.h"
33 #include "tree.h"
34 #include "sprintf.h"
35 #include "audio.h"
36 #include "playlist.h"
37 #include "menu.h"
38 #include "gwps.h"
39 #include "settings.h"
40 #include "status.h"
41 #include "debug.h"
42 #include "ata.h"
43 #include "rolo.h"
44 #include "icons.h"
45 #include "lang.h"
46 #include "language.h"
47 #include "screens.h"
48 #include "keyboard.h"
49 #include "bookmark.h"
50 #include "onplay.h"
51 #include "buffer.h"
52 #include "plugin.h"
53 #include "power.h"
54 #include "action.h"
55 #include "talk.h"
56 #include "filetypes.h"
57 #include "misc.h"
58 #include "filetree.h"
59 #include "tagtree.h"
60 #ifdef HAVE_RECORDING
61 #include "recorder/recording.h"
62 #endif
63 #include "rtc.h"
64 #include "dircache.h"
65 #ifdef HAVE_TAGCACHE
66 #include "tagcache.h"
67 #endif
68 #include "yesno.h"
69 #include "gwps-common.h"
70 #include "eeprom_settings.h"
71 #include "scrobbler.h"
73 /* gui api */
74 #include "list.h"
75 #include "statusbar.h"
76 #include "splash.h"
77 #include "buttonbar.h"
78 #include "textarea.h"
79 #include "action.h"
81 #include "root_menu.h"
83 #if (LCD_DEPTH > 1) || (defined(HAVE_LCD_REMOTE) && (LCD_REMOTE_DEPTH > 1))
84 #include "backdrop.h"
85 #endif
87 static const struct filetype *filetypes;
88 static int filetypes_count;
90 struct gui_synclist tree_lists;
92 /* I put it here because other files doesn't use it yet,
93 * but should be elsewhere since it will be used mostly everywhere */
94 #ifdef HAS_BUTTONBAR
95 struct gui_buttonbar tree_buttonbar;
96 #endif
97 static struct tree_context tc;
99 bool boot_changed = false;
101 char lastfile[MAX_PATH];
102 static char lastdir[MAX_PATH];
103 #ifdef HAVE_TAGCACHE
104 static int lasttable, lastextra, lastfirstpos;
105 #endif
106 static int max_files = 0;
108 static bool reload_dir = false;
110 static bool start_wps = false;
111 static int curr_context = false;/* id3db or tree*/
113 static int dirbrowse(void);
114 static int ft_play_filenumber(int pos, int attr);
115 static int ft_play_dirname(char* name);
116 static void ft_play_filename(char *dir, char *file);
119 * removes the extension of filename (if it doesn't start with a .)
120 * puts the result in buffer
122 static char * strip_extension(char * filename, char * buffer)
124 int dotpos;
125 char * dot=strrchr(filename, '.');
126 if(dot!=0 && filename[0]!='.')
128 dotpos = dot-filename;
129 strncpy(buffer, filename, dotpos);
130 buffer[dotpos]='\0';
131 return(buffer);
133 else
134 return(filename);
137 static char * tree_get_filename(int selected_item, void * data, char *buffer)
139 struct tree_context * local_tc=(struct tree_context *)data;
140 char *name;
141 int attr=0;
142 #ifdef HAVE_TAGCACHE
143 bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB;
145 if (id3db)
147 return tagtree_get_entry(&tc, selected_item)->name;
149 else
150 #endif
152 struct entry* dc = local_tc->dircache;
153 struct entry* e = &dc[selected_item];
154 name = e->name;
155 attr = e->attr;
157 /* if any file filter is on, and if it's not a directory,
158 * strip the extension */
160 if ( (*(local_tc->dirfilter) != SHOW_ID3DB) && !(attr & ATTR_DIRECTORY)
161 && (*(local_tc->dirfilter) != SHOW_ALL) )
163 return(strip_extension(name, buffer));
165 return(name);
168 #ifdef HAVE_LCD_COLOR
169 static int tree_get_filecolor(int selected_item, void * data)
171 if (*tc.dirfilter == SHOW_ID3DB)
172 return -1;
173 struct tree_context * local_tc=(struct tree_context *)data;
174 struct entry* dc = local_tc->dircache;
175 struct entry* e = &dc[selected_item];
176 return filetype_get_color(e->name, e->attr);
178 #endif
180 static int tree_get_fileicon(int selected_item, void * data)
182 struct tree_context * local_tc=(struct tree_context *)data;
183 #ifdef HAVE_TAGCACHE
184 bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB;
185 if (id3db) {
186 return tagtree_get_icon(&tc);
188 else
189 #endif
191 struct entry* dc = local_tc->dircache;
192 struct entry* e = &dc[selected_item];
193 return filetype_get_icon(e->attr);
197 bool check_rockboxdir(void)
199 DIR *dir = opendir(ROCKBOX_DIR);
200 if(!dir)
201 { /* No need to localise this message.
202 If .rockbox is missing, it wouldn't work anyway */
203 int i;
204 FOR_NB_SCREENS(i)
205 screens[i].clear_display();
206 gui_syncsplash(HZ*2, "No .rockbox directory");
207 FOR_NB_SCREENS(i)
208 screens[i].clear_display();
209 gui_syncsplash(HZ*2, "Installation incomplete");
210 return false;
212 closedir(dir);
213 return true;
216 /* do this really late in the init sequence */
217 void tree_gui_init(void)
219 gui_sync_wps_screen_init();
221 check_rockboxdir();
223 strcpy(tc.currdir, "/");
225 #ifdef HAVE_LCD_CHARCELLS
226 int i;
227 FOR_NB_SCREENS(i)
228 screens[i].double_height(false);
229 #endif
230 #ifdef HAS_BUTTONBAR
231 gui_buttonbar_init(&tree_buttonbar);
232 /* since archos only have one screen, no need to create more than that */
233 gui_buttonbar_set_display(&tree_buttonbar, &(screens[SCREEN_MAIN]) );
234 #endif
235 gui_synclist_init(&tree_lists, &tree_get_filename, &tc, false, 1);
236 gui_synclist_set_icon_callback(&tree_lists, &tree_get_fileicon);
237 #ifdef HAVE_LCD_COLOR
238 gui_list_set_color_callback(&tree_lists.gui_list[SCREEN_MAIN],
239 &tree_get_filecolor);
240 #endif
245 struct tree_context* tree_get_context(void)
247 return &tc;
250 /* talkbox hovering delay, to avoid immediate disk activity */
251 #define HOVER_DELAY (HZ/2)
253 * Returns the position of a given file in the current directory
254 * returns -1 if not found
256 static int tree_get_file_position(char * filename)
258 int i;
260 /* use lastfile to determine the selected item (default=0) */
261 for (i=0; i < tc.filesindir; i++)
263 struct entry* dc = tc.dircache;
264 struct entry* e = &dc[i];
265 if (!strcasecmp(e->name, filename))
266 return(i);
268 return(-1);/* no file can match, returns undefined */
272 * Called when a new dir is loaded (for example when returning from other apps ...)
273 * also completely redraws the tree
275 static int update_dir(void)
277 bool changed = false;
278 #ifdef HAVE_TAGCACHE
279 bool id3db = *tc.dirfilter == SHOW_ID3DB;
280 /* Checks for changes */
281 if (id3db) {
282 if (tc.currtable != lasttable ||
283 tc.currextra != lastextra ||
284 tc.firstpos != lastfirstpos ||
285 reload_dir)
287 if (tagtree_load(&tc) < 0)
288 return -1;
290 lasttable = tc.currtable;
291 lastextra = tc.currextra;
292 lastfirstpos = tc.firstpos;
293 changed = true;
296 else
297 #endif
299 /* if the tc.currdir has been changed, reload it ...*/
300 if (strncmp(tc.currdir, lastdir, sizeof(lastdir)) || reload_dir) {
302 if (ft_load(&tc, NULL) < 0)
303 return -1;
304 strcpy(lastdir, tc.currdir);
305 changed = true;
308 /* if selected item is undefined */
309 if (tc.selected_item == -1)
311 /* use lastfile to determine the selected item */
312 tc.selected_item = tree_get_file_position(lastfile);
314 /* If the file doesn't exists, select the first one (default) */
315 if(tc.selected_item < 0)
316 tc.selected_item = 0;
317 changed = true;
319 if (changed)
322 #ifdef HAVE_TAGCACHE
323 !id3db &&
324 #endif
325 (tc.dirfull ||
326 tc.filesindir == global_settings.max_files_in_dir) )
328 gui_syncsplash(HZ, str(LANG_SHOWDIR_BUFFER_FULL));
331 #ifdef HAVE_TAGCACHE
332 if (id3db)
334 if (global_settings.show_path_in_browser == SHOW_PATH_FULL
335 || global_settings.show_path_in_browser == SHOW_PATH_CURRENT)
337 gui_synclist_set_title(&tree_lists, tagtree_get_title(&tc),
338 filetype_get_icon(ATTR_DIRECTORY));
340 else
342 /* Must clear the title as the list is reused */
343 gui_synclist_set_title(&tree_lists, NULL, NOICON);
346 else
347 #endif
349 if (global_settings.show_path_in_browser == SHOW_PATH_FULL)
351 gui_synclist_set_title(&tree_lists, tc.currdir,
352 filetype_get_icon(ATTR_DIRECTORY));
354 else if (global_settings.show_path_in_browser == SHOW_PATH_CURRENT)
356 char *title = strrchr(tc.currdir, '/') + 1;
357 if (*title == '\0')
359 /* Display "Files" for the root dir */
360 gui_synclist_set_title(&tree_lists, str(LANG_DIR_BROWSER),
361 filetype_get_icon(ATTR_DIRECTORY));
363 else if(0 == strcasecmp(tc.currdir, PLUGIN_DIR))
365 /* Display "Plugins" for the rocks dir */
366 gui_synclist_set_title(&tree_lists, str(LANG_PLUGINS),
367 filetype_get_icon(ATTR_DIRECTORY));
369 else
370 gui_synclist_set_title(&tree_lists, title,
371 filetype_get_icon(ATTR_DIRECTORY));
373 else
375 /* Must clear the title as the list is reused */
376 gui_synclist_set_title(&tree_lists, NULL, NOICON);
380 gui_synclist_set_nb_items(&tree_lists, tc.filesindir);
381 gui_synclist_set_icon_callback(&tree_lists, tree_get_fileicon);
382 if( tc.selected_item >= tc.filesindir)
383 tc.selected_item=tc.filesindir-1;
385 gui_synclist_select_item(&tree_lists, tc.selected_item);
386 #ifdef HAS_BUTTONBAR
387 if (global_settings.buttonbar) {
388 if (*tc.dirfilter < NUM_FILTER_MODES)
389 gui_buttonbar_set(&tree_buttonbar, str(LANG_SYSFONT_DIRBROWSE_F1),
390 str(LANG_SYSFONT_DIRBROWSE_F2),
391 str(LANG_SYSFONT_DIRBROWSE_F3));
392 else
393 gui_buttonbar_set(&tree_buttonbar, "<<<", "", "");
394 gui_buttonbar_draw(&tree_buttonbar);
396 #endif
397 gui_synclist_draw(&tree_lists);
398 gui_syncstatusbar_draw(&statusbars, true);
399 return tc.filesindir;
402 /* load tracks from specified directory to resume play */
403 void resume_directory(const char *dir)
405 #ifdef HAVE_TAGCACHE
406 bool id3db = *tc.dirfilter == SHOW_ID3DB;
407 #endif
409 if (ft_load(&tc, dir) < 0)
410 return;
411 lastdir[0] = 0;
413 ft_build_playlist(&tc, 0);
415 #ifdef HAVE_TAGCACHE
416 if (id3db)
417 tagtree_load(&tc);
418 #endif
421 /* Returns the current working directory and also writes cwd to buf if
422 non-NULL. In case of error, returns NULL. */
423 char *getcwd(char *buf, int size)
425 if (!buf)
426 return tc.currdir;
427 else if (size > 0)
429 strncpy(buf, tc.currdir, size);
430 return buf;
432 else
433 return NULL;
436 /* Force a reload of the directory next time directory browser is called */
437 void reload_directory(void)
439 reload_dir = true;
442 void get_current_file(char* buffer, int buffer_len)
444 #ifdef HAVE_TAGCACHE
445 /* in ID3DB mode it is a bad idea to call this function */
446 /* (only happens with `follow playlist') */
447 if( *tc.dirfilter == SHOW_ID3DB )
448 return;
449 #endif
451 struct entry* dc = tc.dircache;
452 struct entry* e = &dc[tc.selected_item];
453 snprintf(buffer, buffer_len, "%s/%s", getcwd(NULL,0),
454 tc.dirlength ? e->name : "");
457 /* Selects a file and update tree context properly */
458 static void set_current_file(char *path)
460 char *name;
461 int i;
463 #ifdef HAVE_TAGCACHE
464 /* in ID3DB mode it is a bad idea to call this function */
465 /* (only happens with `follow playlist') */
466 if( *tc.dirfilter == SHOW_ID3DB )
467 return;
468 #endif
470 /* separate directory from filename */
471 /* gets the directory's name and put it into tc.currdir */
472 name = strrchr(path+1,'/');
473 if (name)
475 *name = 0;
476 strcpy(tc.currdir, path);
477 *name = '/';
478 name++;
480 else
482 strcpy(tc.currdir, "/");
483 name = path+1;
486 strcpy(lastfile, name);
489 /* If we changed dir we must recalculate the dirlevel
490 and adjust the selected history properly */
491 if (strncmp(tc.currdir,lastdir,sizeof(lastdir)))
493 tc.dirlevel = 0;
494 tc.selected_item_history[tc.dirlevel] = -1;
496 /* use '/' to calculate dirlevel */
497 for (i = 1; path[i] != '\0'; i++)
499 if (path[i] == '/')
501 tc.dirlevel++;
502 tc.selected_item_history[tc.dirlevel] = -1;
506 if (ft_load(&tc, NULL) >= 0)
508 tc.selected_item = tree_get_file_position(lastfile);
513 /* main loop, handles key events */
514 static int dirbrowse()
516 int numentries=0;
517 char buf[MAX_PATH];
518 int lasti = -1;
519 unsigned button, returned_button;
520 bool reload_root = false;
521 int lastfilter = *tc.dirfilter;
522 bool lastsortcase = global_settings.sort_case;
523 bool need_update = true;
524 bool exit_func = false;
525 long thumbnail_time = -1; /* for delaying a thumbnail */
526 long last_cancel = 0;
528 char* currdir = tc.currdir; /* just a shortcut */
529 #ifdef HAVE_TAGCACHE
530 bool id3db = *tc.dirfilter == SHOW_ID3DB;
532 if (id3db)
533 curr_context=CONTEXT_ID3DB;
534 else
535 #endif
536 curr_context=CONTEXT_TREE;
537 if (tc.selected_item < 0)
538 tc.selected_item = 0;
539 #ifdef HAVE_TAGCACHE
540 tc.firstpos=0;
541 lasttable = -1;
542 lastextra = -1;
543 lastfirstpos = 0;
544 #endif
546 start_wps = false;
547 numentries = update_dir();
548 if (numentries == -1)
549 return false; /* currdir is not a directory */
551 if (*tc.dirfilter > NUM_FILTER_MODES && numentries==0)
553 gui_syncsplash(HZ*2, str(LANG_NO_FILES));
554 return false; /* No files found for rockbox_browser() */
557 while(1) {
558 struct entry *dircache = tc.dircache;
559 bool restore = false;
560 if (tc.dirlevel < 0)
561 tc.dirlevel = 0; /* shouldnt be needed.. this code needs work! */
562 #ifdef BOOTFILE
563 if (boot_changed) {
564 char *lines[]={str(LANG_BOOT_CHANGED), str(LANG_REBOOT_NOW)};
565 struct text_message message={lines, 2};
566 if(gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES)
567 rolo_load("/" BOOTFILE);
568 restore = true;
569 boot_changed = false;
571 #endif
572 button = get_action(CONTEXT_TREE,HZ/5);
573 returned_button = gui_synclist_do_button(&tree_lists, button,LIST_WRAP_UNLESS_HELD);
574 if (returned_button)
575 need_update = true;
576 if (returned_button == ACTION_STD_CANCEL)
577 button = ACTION_STD_CANCEL;
579 tc.selected_item = gui_synclist_get_sel_pos(&tree_lists);
580 switch ( button ) {
581 case ACTION_STD_OK:
582 /* nothing to do if no files to display */
583 if ( numentries == 0 )
584 break;
586 #ifdef HAVE_TAGCACHE
587 switch (id3db?tagtree_enter(&tc):ft_enter(&tc))
588 #else
589 switch (ft_enter(&tc))
590 #endif
592 case 1: reload_dir = true; break;
593 case 2: start_wps = true; break;
594 case 3: exit_func = true; break;
595 default: break;
597 restore = true;
598 break;
600 case ACTION_STD_CANCEL:
601 if (*tc.dirfilter > NUM_FILTER_MODES && tc.dirlevel < 1) {
602 exit_func = true;
603 break;
605 if ((*tc.dirfilter == SHOW_ID3DB && tc.dirlevel == 0) ||
606 ((*tc.dirfilter != SHOW_ID3DB && !strcmp(currdir,"/"))))
608 if (last_cancel && TIME_BEFORE(current_tick, last_cancel+HZ/2))
610 last_cancel = 0;
611 break;
613 else
614 return GO_TO_ROOT;
616 last_cancel = current_tick;
618 #ifdef HAVE_TAGCACHE
619 if (id3db)
620 tagtree_exit(&tc);
621 else
622 #endif
623 if (ft_exit(&tc) == 3)
624 exit_func = true;
626 restore = true;
627 break;
629 case ACTION_TREE_STOP:
630 if (list_stop_handler())
631 restore = true;
632 break;
634 case ACTION_STD_MENU:
635 return GO_TO_ROOT;
636 break;
638 case ACTION_TREE_WPS:
639 return GO_TO_PREVIOUS_MUSIC;
640 break;
641 #ifdef HAVE_QUICKSCREEN
642 case ACTION_STD_QUICKSCREEN:
643 /* don't enter f2 from plugin browser */
644 if (*tc.dirfilter < NUM_FILTER_MODES)
646 if (quick_screen_quick(button))
647 reload_dir = true;
648 restore = true;
650 break;
651 #endif
652 #ifdef BUTTON_F3
653 case ACTION_F3:
654 /* don't enter f3 from plugin browser */
655 if (*tc.dirfilter < NUM_FILTER_MODES)
657 if (quick_screen_f3(BUTTON_F3))
658 reload_dir = true;
659 restore = true;
661 break;
662 #endif
664 case ACTION_STD_CONTEXT:
666 int onplay_result;
667 int attr = 0;
669 if(!numentries)
670 onplay_result = onplay(NULL, 0, curr_context);
671 else {
672 #ifdef HAVE_TAGCACHE
673 if (id3db)
675 if (tagtree_get_attr(&tc) == FILE_ATTR_AUDIO)
677 attr = FILE_ATTR_AUDIO;
678 tagtree_get_filename(&tc, buf, sizeof(buf));
680 else
681 attr = ATTR_DIRECTORY;
683 else
684 #endif
686 attr = dircache[tc.selected_item].attr;
688 if (currdir[1]) /* Not in / */
689 snprintf(buf, sizeof buf, "%s/%s",
690 currdir,
691 dircache[tc.selected_item].name);
692 else /* In / */
693 snprintf(buf, sizeof buf, "/%s",
694 dircache[tc.selected_item].name);
696 onplay_result = onplay(buf, attr, curr_context);
698 switch (onplay_result)
700 case ONPLAY_MAINMENU:
701 return GO_TO_ROOT;
703 case ONPLAY_OK:
704 restore = true;
705 break;
707 case ONPLAY_RELOAD_DIR:
708 reload_dir = true;
709 break;
711 case ONPLAY_START_PLAY:
712 return GO_TO_WPS;
713 break;
715 break;
718 case ACTION_NONE:
719 if (thumbnail_time != -1 &&
720 TIME_AFTER(current_tick, thumbnail_time))
721 { /* a delayed hovering thumbnail is due now */
722 int res;
723 int attr;
724 char* name;
726 #ifdef HAVE_TAGCACHE
727 if (id3db)
729 attr = tagtree_get_attr(&tc);
730 name = tagtree_get_entry(&tc, lasti)->name;
732 else
733 #endif
735 attr = dircache[lasti].attr;
736 name = dircache[lasti].name;
739 if (attr & ATTR_DIRECTORY)
741 DEBUGF("Playing directory thumbnail: %s", currdir);
742 res = ft_play_dirname(name);
743 if (res < 0) /* failed, not existing */
745 /* say the number or spell if required as a fallback */
746 switch (global_settings.talk_dir)
748 case 1: /* dirs as numbers */
749 talk_id(VOICE_DIR, false);
750 talk_number(lasti+1, true);
751 break;
753 case 2: /* dirs spelled */
754 talk_spell(name, false);
755 break;
759 else
761 DEBUGF("Playing file thumbnail: %s/%s%s\n",
762 currdir, name,
763 file_thumbnail_ext);
764 /* no fallback necessary, we knew in advance
765 that the file exists */
766 ft_play_filename(currdir, name);
768 thumbnail_time = -1; /* job done */
770 gui_syncstatusbar_draw(&statusbars, false);
771 break;
773 #ifdef HAVE_HOTSWAP
774 case SYS_FS_CHANGED:
775 #ifdef HAVE_TAGCACHE
776 if (!id3db)
777 #endif
778 reload_dir = true;
779 /* The 'dir no longer valid' situation will be caught later
780 * by checking the showdir() result. */
781 break;
782 #endif
784 default:
785 if (default_event_handler(button) == SYS_USB_CONNECTED)
787 if(*tc.dirfilter > NUM_FILTER_MODES)
788 /* leave sub-browsers after usb, doing otherwise
789 might be confusing to the user */
790 exit_func = true;
791 else
792 reload_dir = true;
794 break;
796 if (start_wps)
797 return GO_TO_WPS;
798 if ( button )
800 ata_spin();
804 check_rescan:
805 /* do we need to rescan dir? */
806 if (reload_dir || reload_root ||
807 lastfilter != *tc.dirfilter ||
808 lastsortcase != global_settings.sort_case)
810 if ( reload_root ) {
811 strcpy(currdir, "/");
812 tc.dirlevel = 0;
813 #ifdef HAVE_TAGCACHE
814 tc.currtable = 0;
815 tc.currextra = 0;
816 lasttable = -1;
817 lastextra = -1;
818 #endif
819 reload_root = false;
822 if (! reload_dir )
824 gui_synclist_select_item(&tree_lists, 0);
825 gui_synclist_draw(&tree_lists);
826 tc.selected_item = 0;
827 lastdir[0] = 0;
830 lastfilter = *tc.dirfilter;
831 lastsortcase = global_settings.sort_case;
832 restore = true;
835 if (exit_func)
836 return GO_TO_PREVIOUS;
838 if (restore || reload_dir) {
839 /* restore display */
840 numentries = update_dir();
841 if (currdir[1] && (numentries < 0))
842 { /* not in root and reload failed */
843 reload_root = true; /* try root */
844 reload_dir = false;
845 goto check_rescan;
847 need_update = true;
848 reload_dir = false;
851 if(need_update) {
852 need_update=false;
853 if ( numentries > 0 ) {
854 /* Voice the file if changed */
855 if(lasti != tc.selected_item || restore) {
856 int attr;
857 char* name;
859 lasti = tc.selected_item;
860 thumbnail_time = -1; /* Cancel whatever we were
861 about to say */
863 #ifdef HAVE_TAGCACHE
864 if (id3db)
866 attr = tagtree_get_attr(&tc);
867 name = tagtree_get_entry(&tc, tc.selected_item)->name;
869 else
870 #endif
872 attr = dircache[tc.selected_item].attr;
873 name = dircache[tc.selected_item].name;
876 /* Directory? */
877 if (attr & ATTR_DIRECTORY)
879 /* schedule thumbnail playback if required */
880 if (global_settings.talk_dir_clip)
881 thumbnail_time = current_tick + HOVER_DELAY;
882 else
884 /* talk directly */
885 switch (global_settings.talk_dir)
887 case 1: /* dirs as numbers */
888 talk_id(VOICE_DIR, false);
889 talk_number(tc.selected_item+1, true);
890 break;
892 case 2: /* dirs spelled */
893 talk_spell(name, false);
894 break;
898 else /* file */
900 /* schedule thumbnail playback if required */
901 if (global_settings.talk_file_clip && (attr & FILE_ATTR_THUMBNAIL))
902 thumbnail_time = current_tick + HOVER_DELAY;
903 else
905 /* talk directly */
906 switch (global_settings.talk_file)
908 case 1: /* files as numbers */
909 ft_play_filenumber(
910 tc.selected_item-tc.dirsindir+1,
911 attr & FILE_ATTR_MASK);
912 break;
914 case 2: /* files spelled */
915 talk_spell(name, false);
916 break;
924 return true;
927 static int plsize = 0;
928 static long pltick;
929 static bool add_dir(char* dirname, int len, int fd)
931 bool abort = false;
932 DIR* dir;
934 /* check for user abort */
935 if (action_userabort(TIMEOUT_NOBLOCK))
936 return true;
938 dir = opendir(dirname);
939 if(!dir)
940 return true;
942 while (true) {
943 struct dirent *entry;
945 entry = readdir(dir);
946 if (!entry)
947 break;
948 if (entry->attribute & ATTR_DIRECTORY) {
949 int dirlen = strlen(dirname);
950 bool result;
952 if (!strcmp((char *)entry->d_name, ".") ||
953 !strcmp((char *)entry->d_name, ".."))
954 continue;
956 if (dirname[1])
957 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
958 else
959 snprintf(dirname, len, "/%s", entry->d_name);
961 result = add_dir(dirname, len, fd);
962 dirname[dirlen] = '\0';
963 if (result) {
964 abort = true;
965 break;
968 else {
969 int x = strlen((char *)entry->d_name);
970 int i;
971 char *cp = strrchr((char *)entry->d_name,'.');
973 if (cp) {
974 cp++;
976 /* add all supported audio files to playlists */
977 for (i=0; i < filetypes_count; i++) {
978 if (filetypes[i].tree_attr == FILE_ATTR_AUDIO) {
979 if (!strcasecmp(cp, filetypes[i].extension)) {
980 char buf[8];
981 int i;
982 write(fd, dirname, strlen(dirname));
983 write(fd, "/", 1);
984 write(fd, entry->d_name, x);
985 write(fd, "\n", 1);
987 plsize++;
988 if(TIME_AFTER(current_tick, pltick+HZ/4)) {
989 pltick = current_tick;
991 snprintf(buf, sizeof buf, "%d", plsize);
992 #ifdef HAVE_LCD_BITMAP
993 FOR_NB_SCREENS(i)
995 screens[i].puts(0, 4, (unsigned char *)buf);
996 gui_textarea_update(&screens[i]);
998 #else
999 x = 10;
1000 if (plsize > 999)
1001 x=7;
1002 else {
1003 if (plsize > 99)
1004 x=8;
1005 else {
1006 if (plsize > 9)
1007 x=9;
1010 FOR_NB_SCREENS(i) {
1011 screens[i].puts(x,0,buf);
1013 #endif
1015 break;
1022 closedir(dir);
1024 return abort;
1027 bool create_playlist(void)
1029 int fd;
1030 int i;
1031 char filename[MAX_PATH];
1033 pltick = current_tick;
1035 snprintf(filename, sizeof filename, "%s.m3u8",
1036 tc.currdir[1] ? tc.currdir : "/root");
1037 FOR_NB_SCREENS(i)
1039 gui_textarea_clear(&screens[i]);
1040 screens[i].puts(0, 0, str(LANG_CREATING));
1041 screens[i].puts_scroll(0, 1, (unsigned char *)filename);
1042 #if defined(HAVE_LCD_BITMAP) || defined(SIMULATOR)
1043 gui_textarea_update(&screens[i]);
1044 #endif
1046 fd = creat(filename);
1047 if (fd < 0)
1048 return false;
1050 trigger_cpu_boost();
1052 snprintf(filename, sizeof(filename), "%s",
1053 tc.currdir[1] ? tc.currdir : "/");
1054 plsize = 0;
1055 add_dir(filename, sizeof(filename), fd);
1056 close(fd);
1058 sleep(HZ);
1060 return true;
1063 int rockbox_browse(const char *root, int dirfilter)
1065 int ret_val = 0;
1066 int *last_filter = tc.dirfilter;
1067 tc.dirfilter = &dirfilter;
1069 reload_dir = true;
1070 if (dirfilter >= NUM_FILTER_MODES)
1072 static struct tree_context backup;
1073 int last_context;
1075 backup = tc;
1076 tc.selected_item = 0;
1077 tc.dirlevel = 0;
1078 memcpy(tc.currdir, root, sizeof(tc.currdir));
1079 start_wps = false;
1080 last_context = curr_context;
1082 ret_val = dirbrowse();
1083 tc = backup;
1084 curr_context = last_context;
1086 else
1088 static char buf[MAX_PATH];
1089 if (dirfilter != SHOW_ID3DB)
1090 tc.dirfilter = &global_settings.dirfilter;
1091 strcpy(buf,root);
1092 set_current_file(buf);
1093 ret_val = dirbrowse();
1095 tc.dirfilter = last_filter;
1096 return ret_val;
1099 void tree_mem_init(void)
1101 /* We copy the settings value in case it is changed by the user. We can't
1102 use it until the next reboot. */
1103 max_files = global_settings.max_files_in_dir;
1105 /* initialize tree context struct */
1106 memset(&tc, 0, sizeof(tc));
1107 tc.dirfilter = &global_settings.dirfilter;
1109 tc.name_buffer_size = AVERAGE_FILENAME_LENGTH * max_files;
1110 tc.name_buffer = buffer_alloc(tc.name_buffer_size);
1112 tc.dircache_size = max_files * sizeof(struct entry);
1113 tc.dircache = buffer_alloc(tc.dircache_size);
1114 tree_get_filetypes(&filetypes, &filetypes_count);
1117 void bookmark_play(char *resume_file, int index, int offset, int seed,
1118 char *filename)
1120 int i;
1121 char* suffix = strrchr(resume_file, '.');
1123 if (suffix != NULL &&
1124 (!strcasecmp(suffix, ".m3u") || !strcasecmp(suffix, ".m3u8")))
1126 /* Playlist playback */
1127 char* slash;
1128 /* check that the file exists */
1129 int fd = open(resume_file, O_RDONLY);
1130 if(fd<0)
1131 return;
1132 close(fd);
1134 slash = strrchr(resume_file,'/');
1135 if (slash)
1137 char* cp;
1138 *slash=0;
1140 cp=resume_file;
1141 if (!cp[0])
1142 cp="/";
1144 if (playlist_create(cp, slash+1) != -1)
1146 if (global_settings.playlist_shuffle)
1147 playlist_shuffle(seed, -1);
1148 playlist_start(index,offset);
1150 *slash='/';
1153 else
1155 /* Directory playback */
1156 lastdir[0]='\0';
1157 if (playlist_create(resume_file, NULL) != -1)
1159 char* peek_filename;
1160 resume_directory(resume_file);
1161 if (global_settings.playlist_shuffle)
1162 playlist_shuffle(seed, -1);
1164 /* Check if the file is at the same spot in the directory,
1165 else search for it */
1166 peek_filename = playlist_peek(index);
1168 if (peek_filename == NULL)
1169 return;
1171 if (strcmp(strrchr(peek_filename, '/') + 1, filename))
1173 for ( i=0; i < playlist_amount(); i++ )
1175 peek_filename = playlist_peek(i);
1177 if (peek_filename == NULL)
1178 return;
1180 if (!strcmp(strrchr(peek_filename, '/') + 1, filename))
1181 break;
1183 if (i < playlist_amount())
1184 index = i;
1185 else
1186 return;
1188 playlist_start(index,offset);
1192 start_wps=true;
1195 static int ft_play_filenumber(int pos, int attr)
1197 /* try to find a voice ID for the extension, if known */
1198 int j;
1199 int ext_id = -1; /* default to none */
1200 for (j=0; j<filetypes_count; j++)
1202 if (attr == filetypes[j].tree_attr)
1204 ext_id = filetypes[j].voiceclip;
1205 break;
1209 talk_id(VOICE_FILE, false);
1210 talk_number(pos, true);
1211 talk_id(ext_id, true);
1212 return 1;
1215 static int ft_play_dirname(char* name)
1217 int fd;
1218 char dirname_mp3_filename[MAX_PATH+1];
1220 #if CONFIG_CODEC != SWCODEC
1221 if (audio_status() & AUDIO_STATUS_PLAY)
1222 return 0;
1223 #endif
1225 snprintf(dirname_mp3_filename, sizeof(dirname_mp3_filename), "%s/%s/%s",
1226 tc.currdir[1] ? tc.currdir : "" , name,
1227 dir_thumbnail_name);
1229 DEBUGF("Checking for %s\n", dirname_mp3_filename);
1231 fd = open(dirname_mp3_filename, O_RDONLY);
1232 if (fd < 0)
1234 DEBUGF("Failed to find: %s\n", dirname_mp3_filename);
1235 return -1;
1238 close(fd);
1240 DEBUGF("Found: %s\n", dirname_mp3_filename);
1242 talk_file(dirname_mp3_filename, false);
1243 return 1;
1246 static void ft_play_filename(char *dir, char *file)
1248 char name_mp3_filename[MAX_PATH+1];
1250 #if CONFIG_CODEC != SWCODEC
1251 if (audio_status() & AUDIO_STATUS_PLAY)
1252 return;
1253 #endif
1255 if (strcasecmp(&file[strlen(file) - strlen(file_thumbnail_ext)],
1256 file_thumbnail_ext))
1257 { /* file has no .talk extension */
1258 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1259 "%s/%s%s", dir, file, file_thumbnail_ext);
1261 talk_file(name_mp3_filename, false);
1263 else
1264 { /* it already is a .talk file, play this directly */
1265 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1266 "%s/%s", dir, file);
1267 talk_id(LANG_VOICE_DIR_HOVER, false); /* prefix it */
1268 talk_file(name_mp3_filename, true);
1272 /* These two functions are called by the USB and shutdown handlers */
1273 void tree_flush(void)
1275 scrobbler_shutdown();
1276 #ifdef HAVE_TAGCACHE
1277 tagcache_shutdown();
1278 #endif
1279 playlist_shutdown();
1281 #ifdef HAVE_TC_RAMCACHE
1282 tagcache_unload_ramcache();
1283 #endif
1285 #ifdef HAVE_DIRCACHE
1287 int old_val = global_status.dircache_size;
1288 if (global_settings.dircache)
1290 global_status.dircache_size = dircache_get_cache_size();
1291 # ifdef HAVE_EEPROM_SETTINGS
1292 if (firmware_settings.initialized)
1293 dircache_save();
1294 # endif
1295 dircache_disable();
1297 else
1299 global_status.dircache_size = 0;
1301 if (old_val != global_status.dircache_size)
1302 status_save();
1304 #endif
1307 void tree_restore(void)
1309 #ifdef HAVE_EEPROM_SETTINGS
1310 firmware_settings.disk_clean = false;
1311 #endif
1313 #ifdef HAVE_TC_RAMCACHE
1314 remove(TAGCACHE_STATEFILE);
1315 #endif
1317 #ifdef HAVE_DIRCACHE
1318 remove(DIRCACHE_FILE);
1319 if (global_settings.dircache)
1321 /* Print "Scanning disk..." to the display. */
1322 int i;
1323 FOR_NB_SCREENS(i)
1325 screens[i].putsxy((LCD_WIDTH/2) -
1326 ((strlen(str(LANG_DIRCACHE_BUILDING)) *
1327 screens[i].char_width)/2),
1328 LCD_HEIGHT-screens[i].char_height*3,
1329 str(LANG_DIRCACHE_BUILDING));
1330 gui_textarea_update(&screens[i]);
1333 dircache_build(global_status.dircache_size);
1335 /* Clean the text when we are done. */
1336 FOR_NB_SCREENS(i)
1338 gui_textarea_clear(&screens[i]);
1341 #endif
1342 #ifdef HAVE_TAGCACHE
1343 tagcache_start_scan();
1344 #endif
1345 scrobbler_init();