1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
24 #include "applimits.h"
29 #include "backlight.h"
34 #include "main_menu.h"
40 #include "wps-display.h"
58 #include "filetypes.h"
62 #include "recorder/recording.h"
65 #ifdef HAVE_LCD_BITMAP
69 /* a table for the know file types */
70 const struct filetype filetypes
[] = {
71 { ".mp3", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
72 { ".mp2", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
73 { ".mpa", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
74 #if CONFIG_HWCODEC == MASNONE
75 /* Temporary hack to allow playlist creation */
76 { ".mp1", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
77 { ".ogg", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
78 { ".wma", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
79 { ".wav", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
80 { ".flac", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
81 { ".ac3", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
82 { ".a52", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
83 { ".mpc", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
84 { ".wv", TREE_ATTR_MPA
, File
, VOICE_EXT_MPA
},
86 { ".m3u", TREE_ATTR_M3U
, Playlist
, LANG_PLAYLIST
},
87 { ".cfg", TREE_ATTR_CFG
, Config
, VOICE_EXT_CFG
},
88 { ".wps", TREE_ATTR_WPS
, Wps
, VOICE_EXT_WPS
},
89 { ".lng", TREE_ATTR_LNG
, Language
, LANG_LANGUAGE
},
90 { ".rock",TREE_ATTR_ROCK
,Plugin
, VOICE_EXT_ROCK
},
91 #ifdef HAVE_LCD_BITMAP
92 { ".fnt", TREE_ATTR_FONT
,Font
, VOICE_EXT_FONT
},
94 { ".bmark",TREE_ATTR_BMARK
, Bookmark
, VOICE_EXT_BMARK
},
96 { BOOTFILE_EXT
, TREE_ATTR_MOD
, Mod_Ajz
, VOICE_EXT_AJZ
},
97 #endif /* #ifndef SIMULATOR */
100 static struct tree_context tc
;
102 bool boot_changed
= false;
104 char lastfile
[MAX_PATH
];
105 static char lastdir
[MAX_PATH
];
106 static int lasttable
, lastextra
, lastfirstpos
;
107 static int max_files
= 0;
109 static bool reload_dir
= false;
111 static bool start_wps
= false;
112 static bool dirbrowse(void);
113 static int curr_context
= false;
115 bool check_rockboxdir(void)
117 DIR *dir
= opendir(ROCKBOX_DIR
);
121 splash(HZ
*2, true, str(LANG_NO_ROCKBOX_DIR
));
123 splash(HZ
*2, true, str(LANG_INSTALLATION_INCOMPLETE
));
130 void browse_root(void)
135 strcpy(tc
.currdir
, "/");
136 #ifdef HAVE_LCD_CHARCELLS
137 lcd_double_height(false);
144 DEBUGF("No filesystem found. Have you forgotten to create it?\n");
149 void tree_get_filetypes(const struct filetype
** types
, int* count
)
152 *count
= sizeof(filetypes
) / sizeof(*filetypes
);
155 struct tree_context
* tree_get_context(void)
160 #ifdef HAVE_LCD_BITMAP
163 #define MARGIN_X (global_settings.scrollbar && \
164 tc.filesindir > tree_max_on_screen ? SCROLLBAR_WIDTH : 0) + \
165 CURSOR_WIDTH + (global_settings.show_icons && ICON_WIDTH > 0 ? ICON_WIDTH :0)
166 #define MARGIN_Y (global_settings.statusbar ? STATUSBAR_HEIGHT : 0)
168 /* position the entry-list starts at */
170 #define LINE_Y (global_settings.statusbar ? 1 : 0)
172 #define CURSOR_X (global_settings.scrollbar && \
173 tc.filesindir > tree_max_on_screen ? 1 : 0)
174 #define CURSOR_Y 0 /* the cursor is not positioned in regard to
175 the margins, so this is the amount of lines
176 we add to the cursor Y position to position
178 #define CURSOR_WIDTH (global_settings.invert_cursor ? 0 : 4)
182 #define SCROLLBAR_X 0
183 #define SCROLLBAR_Y lcd_getymargin()
184 #define SCROLLBAR_WIDTH 6
186 #else /* HAVE_LCD_BITMAP */
188 #define TREE_MAX_ON_SCREEN 2
189 #define TREE_MAX_LEN_DISPLAY 11 /* max length that fits on screen */
190 #define LINE_X 2 /* X position the entry-list starts at */
191 #define LINE_Y 0 /* Y position the entry-list starts at */
194 #define CURSOR_Y 0 /* not really used for players */
196 #endif /* HAVE_LCD_BITMAP */
198 /* talkbox hovering delay, to avoid immediate disk activity */
199 #define HOVER_DELAY (HZ/2)
201 static void showfileline(int line
, char* name
, int attr
, bool scroll
)
206 #ifdef HAVE_LCD_CHARCELLS
207 if (!global_settings
.show_icons
)
211 /* if any file filter is on, strip the extension */
212 if (*tc
.dirfilter
!= SHOW_ID3DB
&&
213 *tc
.dirfilter
!= SHOW_ALL
&&
214 !(attr
& ATTR_DIRECTORY
))
216 dotpos
= strrchr(name
, '.');
223 #ifdef HAVE_LCD_BITMAP
224 lcd_setfont(FONT_UI
);
225 if (global_settings
.invert_cursor
)
226 lcd_puts_scroll_style(xpos
, line
, name
, STYLE_INVERT
);
229 lcd_puts_scroll(xpos
, line
, name
);
231 lcd_puts(xpos
, line
, name
);
233 /* Restore the dot before the extension if it was removed */
238 #ifdef HAVE_LCD_BITMAP
239 static int recalc_screen_height(void)
242 int height
= LCD_HEIGHT
;
244 lcd_setfont(FONT_UI
);
245 lcd_getstringsize("A", &fw
, &fh
);
246 if(global_settings
.statusbar
)
247 height
-= STATUSBAR_HEIGHT
;
249 #if CONFIG_KEYPAD == RECORDER_PAD
250 if(global_settings
.buttonbar
)
251 height
-= BUTTONBAR_HEIGHT
;
258 static int showdir(void)
260 struct entry
*dircache
= tc
.dircache
;
262 int tree_max_on_screen
;
263 int start
= tc
.dirstart
;
264 bool id3db
= *tc
.dirfilter
== SHOW_ID3DB
;
266 #ifdef HAVE_LCD_BITMAP
270 lcd_setfont(FONT_UI
);
271 lcd_getstringsize("A", &fw
, &fh
);
272 tree_max_on_screen
= recalc_screen_height();
276 tree_max_on_screen
= TREE_MAX_ON_SCREEN
;
279 /* new file dir? load it */
281 if (tc
.currtable
!= lasttable
||
282 tc
.currextra
!= lastextra
||
283 tc
.firstpos
!= lastfirstpos
)
285 if (db_load(&tc
) < 0)
287 lasttable
= tc
.currtable
;
288 lastextra
= tc
.currextra
;
289 lastfirstpos
= tc
.firstpos
;
294 if (strncmp(tc
.currdir
, lastdir
, sizeof(lastdir
)) || reload_dir
) {
295 if (ft_load(&tc
, NULL
) < 0)
297 strcpy(lastdir
, tc
.currdir
);
302 if (newdir
&& !id3db
&&
303 (tc
.dirfull
|| tc
.filesindir
== global_settings
.max_files_in_dir
) )
305 #ifdef HAVE_LCD_CHARCELLS
306 lcd_double_height(false);
309 lcd_puts(0,0,str(LANG_SHOWDIR_ERROR_BUFFER
));
310 lcd_puts(0,1,str(LANG_SHOWDIR_ERROR_FULL
));
320 /* use lastfile to determine start (default=0) */
323 for (i
=0; i
< tc
.filesindir
; i
++)
325 struct entry
*dircache
= tc
.dircache
;
327 if (!strcasecmp(dircache
[i
].name
, lastfile
))
334 diff_files
= tc
.filesindir
- start
;
335 if (diff_files
< tree_max_on_screen
)
337 int oldstart
= start
;
339 start
-= (tree_max_on_screen
- diff_files
);
343 tc
.dircursor
= oldstart
- start
;
349 /* The cursor might point to an invalid line, for example if someone
350 deleted the last file in the dir */
353 while (start
+ tc
.dircursor
>= tc
.filesindir
)
364 #ifdef HAVE_LCD_CHARCELLS
366 lcd_double_height(false);
369 #ifdef HAVE_LCD_BITMAP
370 lcd_setmargins(MARGIN_X
,MARGIN_Y
); /* leave room for cursor and icon */
371 lcd_setfont(FONT_UI
);
375 for ( i
=start
; i
< start
+tree_max_on_screen
&& i
< tc
.filesindir
; i
++ ) {
376 int line
= i
- start
;
381 name
= ((char**)tc
.dircache
)[i
* tc
.dentry_size
];
382 icon
= db_get_icon(&tc
);
385 struct entry
* dc
= tc
.dircache
;
386 struct entry
* e
= &dc
[i
];
389 icon
= filetype_get_icon(dircache
[i
].attr
);
393 if (icon
&& global_settings
.show_icons
) {
394 #ifdef HAVE_LCD_BITMAP
396 if ( line_height
> 8 )
397 offset
= (line_height
- 8) / 2;
398 lcd_mono_bitmap(icon
,
399 CURSOR_X
* 6 + CURSOR_WIDTH
,
400 MARGIN_Y
+(i
-start
)*line_height
+ offset
, 6, 8);
404 lcd_putc(LINE_X
-1, i
-start
, icon
);
408 showfileline(line
, name
, attr
, false); /* no scroll */
411 #ifdef HAVE_LCD_BITMAP
412 if (global_settings
.scrollbar
&& (tc
.dirlength
> tree_max_on_screen
))
413 scrollbar(SCROLLBAR_X
, SCROLLBAR_Y
, SCROLLBAR_WIDTH
- 1,
414 tree_max_on_screen
* line_height
, tc
.dirlength
,
416 start
+ tc
.firstpos
+ tree_max_on_screen
, VERTICAL
);
418 #if CONFIG_KEYPAD == RECORDER_PAD
419 if (global_settings
.buttonbar
) {
420 if (*tc
.dirfilter
< NUM_FILTER_MODES
)
421 buttonbar_set(str(LANG_DIRBROWSE_F1
),
422 str(LANG_DIRBROWSE_F2
),
423 str(LANG_DIRBROWSE_F3
));
425 buttonbar_set("<<<", "", "");
432 return tc
.filesindir
;
435 /* load tracks from specified directory to resume play */
436 void resume_directory(const char *dir
)
438 if (ft_load(&tc
, dir
) < 0)
442 ft_build_playlist(&tc
, 0);
445 /* Returns the current working directory and also writes cwd to buf if
446 non-NULL. In case of error, returns NULL. */
447 char *getcwd(char *buf
, int size
)
453 strncpy(buf
, tc
.currdir
, size
);
460 /* Force a reload of the directory next time directory browser is called */
461 void reload_directory(void)
466 static void start_resume(bool just_powered_on
)
468 bool do_resume
= false;
470 if ( global_settings
.resume_index
!= -1 ) {
471 DEBUGF("Resume index %X offset %X\n",
472 global_settings
.resume_index
,
473 global_settings
.resume_offset
);
475 #ifdef HAVE_ALARM_MOD
476 if ( rtc_check_alarm_started(true) ) {
477 rtc_enable_alarm(false);
483 if ( global_settings
.resume
|| ! just_powered_on
)
486 if (! do_resume
) return;
488 if (playlist_resume() != -1)
490 playlist_start(global_settings
.resume_index
,
491 global_settings
.resume_offset
);
496 } else if (! just_powered_on
) {
497 splash(HZ
*2, true, str(LANG_NOTHING_TO_RESUME
));
501 void set_current_file(char *path
)
506 /* in ID3DB mode it is a bad idea to call this function */
507 /* (only happens with `follow playlist') */
508 if( *tc
.dirfilter
== SHOW_ID3DB
)
513 /* separate directory from filename */
514 name
= strrchr(path
+1,'/');
518 strcpy(tc
.currdir
, path
);
524 strcpy(tc
.currdir
, "/");
528 strcpy(lastfile
, name
);
533 if (strncmp(tc
.currdir
,lastdir
,sizeof(lastdir
)))
536 tc
.dirpos
[tc
.dirlevel
] = -1;
537 tc
.cursorpos
[tc
.dirlevel
] = 0;
539 /* use '/' to calculate dirlevel */
540 for (i
=1; i
<strlen(path
)+1; i
++)
545 tc
.dirpos
[tc
.dirlevel
] = -1;
546 tc
.cursorpos
[tc
.dirlevel
] = 0;
552 static bool check_changed_id3mode(bool currmode
)
554 if (currmode
!= (global_settings
.dirfilter
== SHOW_ID3DB
)) {
555 currmode
= global_settings
.dirfilter
== SHOW_ID3DB
;
557 curr_context
=CONTEXT_ID3DB
;
562 curr_context
=CONTEXT_TREE
;
569 static void tree_prepare_usb(void *parameter
)
576 static bool dirbrowse(void)
583 int tree_max_on_screen
;
584 bool reload_root
= false;
585 int lastfilter
= *tc
.dirfilter
;
586 bool lastsortcase
= global_settings
.sort_case
;
587 int lastdircursor
=-1;
588 bool need_update
= true;
589 bool exit_func
= false;
590 long thumbnail_time
= -1; /* for delaying a thumbnail */
591 bool update_all
= false; /* set this to true when the whole file list
592 has been refreshed on screen */
593 unsigned lastbutton
= 0;
594 char* currdir
= tc
.currdir
; /* just a shortcut */
595 bool id3db
= *tc
.dirfilter
== SHOW_ID3DB
;
598 curr_context
=CONTEXT_ID3DB
;
600 curr_context
=CONTEXT_TREE
;
602 #ifdef HAVE_LCD_BITMAP
603 tree_max_on_screen
= recalc_screen_height();
605 tree_max_on_screen
= TREE_MAX_ON_SCREEN
;
616 if (*tc
.dirfilter
< NUM_FILTER_MODES
) {
619 #ifdef HAVE_RECORDING
621 if (global_settings
.rec_startup
&& ! start_wps
) {
622 /* We fake being in the menu structure by calling the appropriate */
623 /* parent when we drop out of each screen */
633 numentries
= showdir();
634 if (numentries
== -1)
635 return false; /* currdir is not a directory */
637 if (*tc
.dirfilter
> NUM_FILTER_MODES
&& numentries
==0)
639 splash(HZ
*2, true, str(LANG_NO_FILES
));
640 return false; /* No files found for rockbox_browser() */
644 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, true);
648 struct entry
*dircache
= tc
.dircache
;
650 bool restore
= false;
652 button
= button_get_w_tmo(HZ
/5);
660 lcd_puts(0,0,str(LANG_BOOT_CHANGED
));
661 lcd_puts(0,1,str(LANG_REBOOT_NOW
));
662 #ifdef HAVE_LCD_BITMAP
663 lcd_puts(0,3,str(LANG_CONFIRM_WITH_PLAY_RECORDER
));
664 lcd_puts(0,4,str(LANG_CANCEL_WITH_ANY_RECORDER
));
668 button
= button_get(true);
674 rolo_load("/" BOOTFILE
);
679 if(default_event_handler(button
) ||
680 (button
& BUTTON_REL
))
687 boot_changed
= false;
694 case TREE_ENTER
| BUTTON_REPEAT
:
701 if (((button
== TREE_RUN
)
702 #ifdef TREE_RC_RUN_PRE
703 || (button
== TREE_RC_RUN
))
704 && ((lastbutton
!= TREE_RC_RUN_PRE
)
706 && (lastbutton
!= TREE_RUN_PRE
)))
719 case 1: reload_dir
= true; break;
720 case 2: start_wps
= true; break;
721 case 3: exit_func
= true; break;
725 #ifdef HAVE_LCD_BITMAP
726 /* maybe we have a new font */
727 tree_max_on_screen
= recalc_screen_height();
729 /* make sure cursor is on screen */
730 while ( tc
.dircursor
> tree_max_on_screen
)
740 case TREE_EXIT
| BUTTON_REPEAT
:
744 if (*tc
.dirfilter
> NUM_FILTER_MODES
&& tc
.dirlevel
< 1) {
755 if (ft_exit(&tc
) == 3)
762 #if (CONFIG_KEYPAD == RECORDER_PAD) && !defined(HAVE_SW_POWEROFF)
764 if (*tc
.dirfilter
< NUM_FILTER_MODES
)
766 /* Stop the music if it is playing, else show the shutdown
771 if (!charger_inserted()) {
781 #if defined(HAVE_CHARGING) && !defined(HAVE_POWEROFF_WHILE_CHARGING)
782 case TREE_OFF
| BUTTON_REPEAT
:
783 if (charger_inserted()) {
792 case TREE_PREV
| BUTTON_REPEAT
:
795 case TREE_RC_PREV
| BUTTON_REPEAT
:
800 /* start scrolling when at 1/3 of the screen */
802 tree_max_on_screen
- (2 * tree_max_on_screen
) / 3
803 || (tc
.dirstart
== 0 && tc
.dircursor
> 0)) {
804 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, false);
806 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, true);
809 if (tc
.dirstart
|| tc
.firstpos
) {
813 if (tc
.firstpos
> max_files
/2) {
814 tc
.firstpos
-= max_files
/2;
815 tc
.dirstart
+= max_files
/2;
819 tc
.dirstart
= tc
.firstpos
- 1;
826 if (button
& BUTTON_REPEAT
)
828 if (numentries
< tree_max_on_screen
) {
829 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
,
831 tc
.dircursor
= numentries
- 1;
832 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
,
835 else if (id3db
&& tc
.dirfull
) {
836 /* load last dir segment */
837 /* use max_files/2 in case names are longer than
838 AVERAGE_FILE_LENGTH */
839 tc
.firstpos
= tc
.dirlength
- max_files
/2;
840 tc
.dirstart
= tc
.firstpos
;
841 tc
.dircursor
= tree_max_on_screen
- 1;
842 numentries
= showdir();
844 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
,
848 tc
.dirstart
= numentries
- tree_max_on_screen
;
849 tc
.dircursor
= tree_max_on_screen
- 1;
858 case TREE_NEXT
| BUTTON_REPEAT
:
861 case TREE_RC_NEXT
| BUTTON_REPEAT
:
866 if (tc
.dircursor
+ tc
.dirstart
+ 1 < numentries
) {
867 /* start scrolling when at 2/3 of the screen */
868 if(tc
.dircursor
< (2 * tree_max_on_screen
) / 3 ||
869 numentries
- tc
.dirstart
<= tree_max_on_screen
) {
870 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, false);
872 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, true);
879 else if (id3db
&& (tc
.firstpos
|| tc
.dirfull
)) {
880 if (tc
.dircursor
+ tc
.dirstart
+ tc
.firstpos
+ 1 >= tc
.dirlength
) {
881 /* wrap and load first dir segment */
882 if (button
& BUTTON_REPEAT
)
884 tc
.firstpos
= tc
.dirstart
= tc
.dircursor
= 0;
887 /* load next dir segment */
888 tc
.firstpos
+= tc
.dirstart
;
894 if (button
& BUTTON_REPEAT
)
896 if(numentries
< tree_max_on_screen
) {
897 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, false);
898 tc
.dirstart
= tc
.dircursor
= 0;
899 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, true);
902 tc
.dirstart
= tc
.dircursor
= 0;
903 numentries
= showdir();
905 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, true);
913 case TREE_PGUP
| BUTTON_REPEAT
:
915 tc
.dirstart
-= tree_max_on_screen
;
916 if ( tc
.dirstart
< 0 )
919 else if (tc
.firstpos
) {
920 if (tc
.firstpos
> max_files
/2) {
921 tc
.firstpos
-= max_files
/2;
922 tc
.dirstart
+= max_files
/2;
923 tc
.dirstart
-= tree_max_on_screen
;
926 tc
.dirstart
= tc
.firstpos
- tree_max_on_screen
;
936 case TREE_PGDN
| BUTTON_REPEAT
:
937 if ( tc
.dirstart
< numentries
- tree_max_on_screen
) {
938 tc
.dirstart
+= tree_max_on_screen
;
939 if ( tc
.dirstart
> numentries
- tree_max_on_screen
)
940 tc
.dirstart
= numentries
- tree_max_on_screen
;
942 else if (id3db
&& tc
.dirfull
) {
943 /* load next dir segment */
944 tc
.firstpos
+= tc
.dirstart
;
948 tc
.dircursor
= numentries
- tc
.dirstart
- 1;
958 if (lastbutton
!= TREE_MENU_PRE
)
961 /* don't enter menu from plugin browser */
962 if (*tc
.dirfilter
< NUM_FILTER_MODES
)
969 id3db
= check_changed_id3mode(id3db
);
971 else /* use it as a quick exit instead */
980 if ((lastbutton
!= TREE_WPS_PRE
)
982 && (lastbutton
!= TREE_RC_WPS_PRE
)
987 /* don't enter wps from plugin browser etc */
988 if (*tc
.dirfilter
< NUM_FILTER_MODES
)
990 if (audio_status() & AUDIO_STATUS_PLAY
)
1004 /* don't enter f2 from plugin browser */
1005 if (*tc
.dirfilter
< NUM_FILTER_MODES
)
1007 if (quick_screen(curr_context
, TREE_QUICK
))
1011 id3db
= check_changed_id3mode(id3db
);
1019 /* don't enter f3 from plugin browser */
1020 if (*tc
.dirfilter
< NUM_FILTER_MODES
)
1022 if (quick_screen(curr_context
, BUTTON_F3
))
1024 tree_max_on_screen
= recalc_screen_height();
1031 #ifdef TREE_RC_CONTEXT
1032 case TREE_RC_CONTEXT
:
1034 #ifdef TREE_CONTEXT2
1042 onplay_result
= onplay(NULL
, 0, curr_context
);
1045 snprintf(buf
, sizeof buf
, "%s/%s",
1046 currdir
, dircache
[tc
.dircursor
+tc
.dirstart
].name
);
1048 snprintf(buf
, sizeof buf
, "/%s",
1049 dircache
[tc
.dircursor
+tc
.dirstart
].name
);
1051 switch (tc
.currtable
)
1061 attr
= dircache
[tc
.dircursor
+tc
.dirstart
].attr
;
1062 onplay_result
= onplay(buf
, attr
, curr_context
);
1065 switch (onplay_result
)
1071 case ONPLAY_RELOAD_DIR
:
1075 case ONPLAY_START_PLAY
:
1083 if (thumbnail_time
!= -1 &&
1084 TIME_AFTER(current_tick
, thumbnail_time
))
1085 { /* a delayed hovering thumbnail is due now */
1087 if (dircache
[lasti
].attr
& ATTR_DIRECTORY
)
1089 DEBUGF("Playing directory thumbnail: %s", currdir
);
1090 res
= ft_play_dirname(lasti
);
1091 if (res
< 0) /* failed, not existing */
1092 { /* say the number instead, as a fallback */
1093 talk_id(VOICE_DIR
, false);
1094 talk_number(lasti
+1, true);
1099 DEBUGF("Playing file thumbnail: %s/%s%s\n",
1100 currdir
, dircache
[lasti
].name
, file_thumbnail_ext
);
1101 /* no fallback necessary, we knew in advance
1102 that the file exists */
1103 ft_play_filename(currdir
, dircache
[lasti
].name
);
1105 thumbnail_time
= -1; /* job done */
1111 case SYS_FS_CHANGED
:
1114 /* The 'dir no longer valid' situation will be caught later
1115 * by checking the showdir() result. */
1120 if (default_event_handler_ex(button
, tree_prepare_usb
, NULL
)
1121 == SYS_USB_CONNECTED
)
1123 tagdb_init(); /* re-init database */
1125 if(*tc
.dirfilter
> NUM_FILTER_MODES
)
1126 /* leave sub-browsers after usb, doing otherwise
1127 might be confusing to the user */
1138 lastbutton
= button
;
1144 if (wps_show() == SYS_USB_CONNECTED
)
1148 if (!id3db
) /* Try reload to catch 'no longer valid' case. */
1151 #ifdef HAVE_LCD_BITMAP
1152 tree_max_on_screen
= recalc_screen_height();
1154 id3db
= check_changed_id3mode(id3db
);
1160 /* do we need to rescan dir? */
1161 if (reload_dir
|| reload_root
||
1162 lastfilter
!= *tc
.dirfilter
||
1163 lastsortcase
!= global_settings
.sort_case
)
1165 if ( reload_root
) {
1166 strcpy(currdir
, "/");
1172 reload_root
= false;
1181 lastfilter
= *tc
.dirfilter
;
1182 lastsortcase
= global_settings
.sort_case
;
1184 button_clear_queue(); /* clear button queue */
1190 if (restore
|| reload_dir
) {
1191 /* restore display */
1193 #ifdef HAVE_LCD_BITMAP
1194 tree_max_on_screen
= recalc_screen_height();
1197 /* We need to adjust if the number of lines on screen have
1198 changed because of a status bar change */
1199 if(CURSOR_Y
+LINE_Y
+tc
.dircursor
>tree_max_on_screen
) {
1203 #ifdef HAVE_LCD_BITMAP
1204 /* the sub-screen might've ruined the margins */
1205 lcd_setmargins(MARGIN_X
,MARGIN_Y
); /* leave room for cursor and
1207 lcd_setfont(FONT_UI
);
1209 numentries
= showdir();
1210 if (currdir
[1] && (numentries
< 0))
1211 { /* not in root and reload failed */
1212 reload_root
= true; /* try root */
1217 put_cursorxy(CURSOR_X
, CURSOR_Y
+ tc
.dircursor
, true);
1223 if ( (numentries
> 0) && need_update
) {
1224 i
= tc
.dirstart
+tc
.dircursor
;
1226 /* if MP3 filter is on, cut off the extension */
1227 if(lasti
!=i
|| restore
) {
1232 name
= ((char**)tc
.dircache
)[lasti
* tc
.dentry_size
];
1234 struct entry
* dc
= tc
.dircache
;
1235 struct entry
* e
= &dc
[lasti
];
1242 /* So if lastdircursor and dircursor differ, and then full
1243 screen was not refreshed, restore the previous line */
1244 if ((lastdircursor
!= tc
.dircursor
) && !update_all
) {
1245 showfileline(lastdircursor
, name
, attr
, false); /* no scroll */
1248 lastdircursor
=tc
.dircursor
;
1249 thumbnail_time
= -1; /* cancel whatever we were about to say */
1252 name
= ((char**)tc
.dircache
)[lasti
* tc
.dentry_size
];
1254 struct entry
* dc
= tc
.dircache
;
1255 struct entry
* e
= &dc
[lasti
];
1259 showfileline(tc
.dircursor
, name
, attr
, true); /* scroll please */
1262 if (dircache
[i
].attr
& ATTR_DIRECTORY
) /* directory? */
1264 /* play directory thumbnail */
1265 switch (global_settings
.talk_dir
) {
1266 case 1: /* dirs as numbers */
1267 talk_id(VOICE_DIR
, false);
1268 talk_number(i
+1, true);
1271 case 2: /* dirs spelled */
1272 talk_spell(dircache
[i
].name
, false);
1275 case 3: /* thumbnail clip */
1276 /* "schedule" a thumbnail, to have a little dalay */
1277 thumbnail_time
= current_tick
+ HOVER_DELAY
;
1286 switch (global_settings
.talk_file
) {
1287 case 1: /* files as numbers */
1288 ft_play_filenumber(i
-tc
.dirsindir
+1,
1289 dircache
[i
].attr
& TREE_ATTR_MASK
);
1292 case 2: /* files spelled */
1293 talk_spell(dircache
[i
].name
, false);
1296 case 3: /* thumbnail clip */
1297 /* "schedule" a thumbnail, to have a little delay */
1298 if (dircache
[i
].attr
& TREE_ATTR_THUMBNAIL
)
1299 thumbnail_time
= current_tick
+ HOVER_DELAY
;
1301 /* spell the number as fallback */
1302 talk_spell(dircache
[i
].name
, false);
1315 need_update
= false;
1323 static int plsize
= 0;
1324 static bool add_dir(char* dirname
, int len
, int fd
)
1329 /* check for user abort */
1331 if (button_get(false) == BUTTON_STOP
)
1333 if (button_get(false) == BUTTON_OFF
)
1337 dir
= opendir(dirname
);
1342 struct dirent
*entry
;
1344 entry
= readdir(dir
);
1347 if (entry
->attribute
& ATTR_DIRECTORY
) {
1348 int dirlen
= strlen(dirname
);
1351 if (!strcmp(entry
->d_name
, ".") ||
1352 !strcmp(entry
->d_name
, ".."))
1356 snprintf(dirname
+dirlen
, len
-dirlen
, "/%s", entry
->d_name
);
1358 snprintf(dirname
, len
, "/%s", entry
->d_name
);
1360 result
= add_dir(dirname
, len
, fd
);
1361 dirname
[dirlen
] = '\0';
1368 int x
= strlen(entry
->d_name
);
1372 /* add all supported audio files to playlists */
1373 for (i
=0; i
< sizeof(filetypes
); i
++) {
1374 if (filetypes
[i
].tree_attr
== TREE_ATTR_MPA
) {
1375 xl
=strlen(filetypes
[i
].extension
);
1376 if (!strcasecmp(&entry
->d_name
[x
-xl
],
1377 filetypes
[i
].extension
))
1380 write(fd
, dirname
, strlen(dirname
));
1382 write(fd
, entry
->d_name
, x
);
1386 snprintf(buf
, sizeof buf
, "%d", plsize
);
1387 #ifdef HAVE_LCD_BITMAP
1414 bool create_playlist(void)
1417 char filename
[MAX_PATH
];
1419 snprintf(filename
, sizeof filename
, "%s.m3u",
1420 tc
.currdir
[1] ? tc
.currdir
: "/root");
1422 lcd_clear_display();
1423 lcd_puts(0,0,str(LANG_CREATING
));
1424 lcd_puts_scroll(0,1,filename
);
1427 fd
= creat(filename
,0);
1431 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1435 snprintf(filename
, sizeof(filename
), "%s",
1436 tc
.currdir
[1] ? tc
.currdir
: "/");
1438 add_dir(filename
, sizeof(filename
), fd
);
1441 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1450 bool rockbox_browse(const char *root
, int dirfilter
)
1452 static struct tree_context backup
;
1456 memcpy(tc
.currdir
, root
, sizeof(tc
.currdir
));
1458 tc
.dirfilter
= &dirfilter
;
1468 void tree_init(void)
1470 /* We copy the settings value in case it is changed by the user. We can't
1471 use it until the next reboot. */
1472 max_files
= global_settings
.max_files_in_dir
;
1474 /* initialize tree context struct */
1475 memset(&tc
, 0, sizeof(tc
));
1476 tc
.dirfilter
= &global_settings
.dirfilter
;
1480 tc
.name_buffer_size
= AVERAGE_FILENAME_LENGTH
* max_files
;
1481 tc
.name_buffer
= buffer_alloc(tc
.name_buffer_size
);
1483 tc
.dircache_size
= max_files
* sizeof(struct entry
);
1484 tc
.dircache
= buffer_alloc(tc
.dircache_size
);
1487 void bookmark_play(char *resume_file
, int index
, int offset
, int seed
,
1491 int len
=strlen(resume_file
);
1493 if (!strcasecmp(&resume_file
[len
-4], ".m3u"))
1495 /* Playlist playback */
1497 // check that the file exists
1498 int fd
= open(resume_file
, O_RDONLY
);
1503 slash
= strrchr(resume_file
,'/');
1513 if (playlist_create(cp
, slash
+1) != -1)
1515 if (global_settings
.playlist_shuffle
)
1516 playlist_shuffle(seed
, -1);
1517 playlist_start(index
,offset
);
1524 /* Directory playback */
1526 if (playlist_create(resume_file
, NULL
) != -1)
1528 resume_directory(resume_file
);
1529 if (global_settings
.playlist_shuffle
)
1530 playlist_shuffle(seed
, -1);
1532 /* Check if the file is at the same spot in the directory,
1533 else search for it */
1534 if ((strcmp(strrchr(playlist_peek(index
) + 1,'/') + 1,
1537 for ( i
=0; i
< playlist_amount(); i
++ )
1539 if ((strcmp(strrchr(playlist_peek(i
) + 1,'/') + 1,
1543 if (i
< playlist_amount())
1548 playlist_start(index
,offset
);
1555 int ft_play_filenumber(int pos
, int attr
)
1557 /* try to find a voice ID for the extension, if known */
1559 int ext_id
= -1; /* default to none */
1560 for (j
=0; j
<sizeof(filetypes
)/sizeof(*filetypes
); j
++)
1562 if (attr
== filetypes
[j
].tree_attr
)
1564 ext_id
= filetypes
[j
].voiceclip
;
1569 talk_id(VOICE_FILE
, false);
1570 talk_number(pos
, true);
1571 talk_id(ext_id
, true);
1575 int ft_play_dirname(int start_index
)
1578 char dirname_mp3_filename
[MAX_PATH
+1];
1579 struct entry
*dircache
= tc
.dircache
;
1581 if (audio_status() & AUDIO_STATUS_PLAY
)
1584 snprintf(dirname_mp3_filename
, sizeof(dirname_mp3_filename
), "%s/%s/%s",
1585 tc
.currdir
[1] ? tc
.currdir
: "" , dircache
[start_index
].name
,
1586 dir_thumbnail_name
);
1588 DEBUGF("Checking for %s\n", dirname_mp3_filename
);
1590 fd
= open(dirname_mp3_filename
, O_RDONLY
);
1593 DEBUGF("Failed to find: %s\n", dirname_mp3_filename
);
1599 DEBUGF("Found: %s\n", dirname_mp3_filename
);
1601 talk_file(dirname_mp3_filename
, false);
1605 void ft_play_filename(char *dir
, char *file
)
1607 char name_mp3_filename
[MAX_PATH
+1];
1609 if (audio_status() & AUDIO_STATUS_PLAY
)
1612 if (strcasecmp(&file
[strlen(file
) - strlen(file_thumbnail_ext
)],
1613 file_thumbnail_ext
))
1614 { /* file has no .talk extension */
1615 snprintf(name_mp3_filename
, sizeof(name_mp3_filename
),
1616 "%s/%s%s", dir
, file
, file_thumbnail_ext
);
1618 talk_file(name_mp3_filename
, false);
1621 { /* it already is a .talk file, play this directly */
1622 snprintf(name_mp3_filename
, sizeof(name_mp3_filename
),
1623 "%s/%s", dir
, file
);
1624 talk_id(LANG_VOICE_DIR_HOVER
, false); /* prefix it */
1625 talk_file(name_mp3_filename
, true);