Fix for ff/rw in long MP3 files.
[kugel-rb.git] / apps / tree.c
blob8e1afb8b67302abbb04a2777e348801deb48dfea
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 "main_menu.h"
35 #include "sprintf.h"
36 #include "audio.h"
37 #include "playlist.h"
38 #include "menu.h"
39 #include "wps.h"
40 #include "wps-display.h"
41 #include "settings.h"
42 #include "status.h"
43 #include "debug.h"
44 #include "ata.h"
45 #include "rolo.h"
46 #include "icons.h"
47 #include "lang.h"
48 #include "language.h"
49 #include "screens.h"
50 #include "keyboard.h"
51 #include "bookmark.h"
52 #include "onplay.h"
53 #include "buffer.h"
54 #include "plugin.h"
55 #include "power.h"
56 #include "action.h"
57 #include "talk.h"
58 #include "filetypes.h"
59 #include "misc.h"
60 #include "filetree.h"
61 #include "dbtree.h"
62 #include "recorder/recording.h"
63 #include "rtc.h"
65 #ifdef HAVE_LCD_BITMAP
66 #include "widgets.h"
67 #endif
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 },
85 #endif
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 },
93 #endif
94 { ".bmark",TREE_ATTR_BMARK, Bookmark, VOICE_EXT_BMARK },
95 #ifdef BOOTFILE_EXT
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);
118 if(!dir)
120 lcd_clear_display();
121 splash(HZ*2, true, str(LANG_NO_ROCKBOX_DIR));
122 lcd_clear_display();
123 splash(HZ*2, true, str(LANG_INSTALLATION_INCOMPLETE));
124 return false;
126 closedir(dir);
127 return true;
130 void browse_root(void)
132 filetype_init();
133 check_rockboxdir();
135 strcpy(tc.currdir, "/");
136 #ifdef HAVE_LCD_CHARCELLS
137 lcd_double_height(false);
138 #endif
139 #ifndef SIMULATOR
140 dirbrowse();
142 #else
143 if (!dirbrowse()) {
144 DEBUGF("No filesystem found. Have you forgotten to create it?\n");
146 #endif
149 void tree_get_filetypes(const struct filetype** types, int* count)
151 *types = filetypes;
152 *count = sizeof(filetypes) / sizeof(*filetypes);
155 struct tree_context* tree_get_context(void)
157 return &tc;
160 #ifdef HAVE_LCD_BITMAP
162 /* pixel margins */
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 */
169 #define LINE_X 0
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
177 it on a line */
178 #define CURSOR_WIDTH (global_settings.invert_cursor ? 0 : 4)
180 #define ICON_WIDTH 6
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 */
193 #define CURSOR_X 0
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)
203 int xpos = LINE_X;
204 char* dotpos = NULL;
206 #ifdef HAVE_LCD_CHARCELLS
207 if (!global_settings.show_icons)
208 xpos--;
209 #endif
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, '.');
217 if (dotpos) {
218 *dotpos = 0;
222 if(scroll) {
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);
227 else
228 #endif
229 lcd_puts_scroll(xpos, line, name);
230 } else
231 lcd_puts(xpos, line, name);
233 /* Restore the dot before the extension if it was removed */
234 if (dotpos)
235 *dotpos = '.';
238 #ifdef HAVE_LCD_BITMAP
239 static int recalc_screen_height(void)
241 int fw, fh;
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;
252 #endif
254 return height / fh;
256 #endif
258 static int showdir(void)
260 struct entry *dircache = tc.dircache;
261 int i;
262 int tree_max_on_screen;
263 int start = tc.dirstart;
264 bool id3db = *tc.dirfilter == SHOW_ID3DB;
265 bool newdir = false;
266 #ifdef HAVE_LCD_BITMAP
267 const char* icon;
268 int line_height;
269 int fw, fh;
270 lcd_setfont(FONT_UI);
271 lcd_getstringsize("A", &fw, &fh);
272 tree_max_on_screen = recalc_screen_height();
273 line_height = fh;
274 #else
275 int icon;
276 tree_max_on_screen = TREE_MAX_ON_SCREEN;
277 #endif
279 /* new file dir? load it */
280 if (id3db) {
281 if (tc.currtable != lasttable ||
282 tc.currextra != lastextra ||
283 tc.firstpos != lastfirstpos)
285 if (db_load(&tc) < 0)
286 return -1;
287 lasttable = tc.currtable;
288 lastextra = tc.currextra;
289 lastfirstpos = tc.firstpos;
290 newdir = true;
293 else {
294 if (strncmp(tc.currdir, lastdir, sizeof(lastdir)) || reload_dir) {
295 if (ft_load(&tc, NULL) < 0)
296 return -1;
297 strcpy(lastdir, tc.currdir);
298 newdir = true;
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);
307 #endif
308 lcd_clear_display();
309 lcd_puts(0,0,str(LANG_SHOWDIR_ERROR_BUFFER));
310 lcd_puts(0,1,str(LANG_SHOWDIR_ERROR_FULL));
311 lcd_update();
312 sleep(HZ*2);
313 lcd_clear_display();
316 if (start == -1)
318 int diff_files;
320 /* use lastfile to determine start (default=0) */
321 start = 0;
323 for (i=0; i < tc.filesindir; i++)
325 struct entry *dircache = tc.dircache;
327 if (!strcasecmp(dircache[i].name, lastfile))
329 start = i;
330 break;
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);
340 if (start < 0)
341 start = 0;
343 tc.dircursor = oldstart - start;
346 tc.dirstart = start;
349 /* The cursor might point to an invalid line, for example if someone
350 deleted the last file in the dir */
351 if (tc.filesindir)
353 while (start + tc.dircursor >= tc.filesindir)
355 if (start)
356 start--;
357 else
358 if (tc.dircursor)
359 tc.dircursor--;
361 tc.dirstart = start;
364 #ifdef HAVE_LCD_CHARCELLS
365 lcd_stop_scroll();
366 lcd_double_height(false);
367 #endif
368 lcd_clear_display();
369 #ifdef HAVE_LCD_BITMAP
370 lcd_setmargins(MARGIN_X,MARGIN_Y); /* leave room for cursor and icon */
371 lcd_setfont(FONT_UI);
372 #endif
375 for ( i=start; i < start+tree_max_on_screen && i < tc.filesindir; i++ ) {
376 int line = i - start;
377 char* name;
378 int attr = 0;
380 if (id3db) {
381 name = ((char**)tc.dircache)[i * tc.dentry_size];
382 icon = db_get_icon(&tc);
384 else {
385 struct entry* dc = tc.dircache;
386 struct entry* e = &dc[i];
387 name = e->name;
388 attr = e->attr;
389 icon = filetype_get_icon(dircache[i].attr);
393 if (icon && global_settings.show_icons) {
394 #ifdef HAVE_LCD_BITMAP
395 int offset=0;
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);
401 #else
402 if (icon < 0 )
403 icon = Unknown;
404 lcd_putc(LINE_X-1, i-start, icon);
405 #endif
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,
415 start + tc.firstpos,
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));
424 else
425 buttonbar_set("<<<", "", "");
426 buttonbar_draw();
428 #endif
429 #endif
430 status_draw(true);
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)
439 return;
440 lastdir[0] = 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)
449 if (!buf)
450 return tc.currdir;
451 else if (size > 0)
453 strncpy(buf, tc.currdir, size);
454 return buf;
456 else
457 return NULL;
460 /* Force a reload of the directory next time directory browser is called */
461 void reload_directory(void)
463 reload_dir = true;
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);
478 do_resume = true;
480 #endif
482 /* always resume? */
483 if ( global_settings.resume || ! just_powered_on)
484 do_resume = true;
486 if (! do_resume) return;
488 if (playlist_resume() != -1)
490 playlist_start(global_settings.resume_index,
491 global_settings.resume_offset);
493 start_wps = true;
495 else return;
496 } else if (! just_powered_on) {
497 splash(HZ*2, true, str(LANG_NOTHING_TO_RESUME));
501 void set_current_file(char *path)
503 char *name;
504 unsigned int i;
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 )
510 return;
513 /* separate directory from filename */
514 name = strrchr(path+1,'/');
515 if (name)
517 *name = 0;
518 strcpy(tc.currdir, path);
519 *name = '/';
520 name++;
522 else
524 strcpy(tc.currdir, "/");
525 name = path+1;
528 strcpy(lastfile, name);
530 tc.dircursor = 0;
531 tc.dirstart = -1;
533 if (strncmp(tc.currdir,lastdir,sizeof(lastdir)))
535 tc.dirlevel = 0;
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++)
542 if (path[i] == '/')
544 tc.dirlevel++;
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;
556 if (currmode) {
557 curr_context=CONTEXT_ID3DB;
558 db_load(&tc);
560 else
562 curr_context=CONTEXT_TREE;
563 ft_load(&tc, NULL);
566 return currmode;
569 static void tree_prepare_usb(void *parameter)
571 (void) parameter;
572 rundb_shutdown();
573 tagdb_shutdown();
576 static bool dirbrowse(void)
578 int numentries=0;
579 char buf[MAX_PATH];
580 int i;
581 int lasti=-1;
582 unsigned button;
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;
597 if (id3db)
598 curr_context=CONTEXT_ID3DB;
599 else
600 curr_context=CONTEXT_TREE;
602 #ifdef HAVE_LCD_BITMAP
603 tree_max_on_screen = recalc_screen_height();
604 #else
605 tree_max_on_screen = TREE_MAX_ON_SCREEN;
606 #endif
608 tc.dircursor=0;
609 tc.dirstart=0;
610 tc.dirlevel=0;
611 tc.firstpos=0;
612 lasttable = -1;
613 lastextra = -1;
614 lastfirstpos = 0;
616 if (*tc.dirfilter < NUM_FILTER_MODES) {
617 start_resume(true);
619 #ifdef HAVE_RECORDING
620 #ifndef SIMULATOR
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 */
624 recording_screen();
625 rec_menu();
626 main_menu();
628 #endif
629 #endif
632 if (!start_wps) {
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() */
642 update_all = true;
644 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
647 while(1) {
648 struct entry *dircache = tc.dircache;
650 bool restore = false;
652 button = button_get_w_tmo(HZ/5);
654 #ifdef BOOTFILE
655 if (boot_changed) {
656 bool stop = false;
657 unsigned int button;
659 lcd_clear_display();
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));
665 lcd_update();
666 #endif
667 while (!stop) {
668 button = button_get(true);
669 switch (button) {
670 case TREE_RUN:
671 #ifdef TREE_RC_RUN
672 case TREE_RC_RUN:
673 #endif
674 rolo_load("/" BOOTFILE);
675 stop = true;
676 break;
678 default:
679 if(default_event_handler(button) ||
680 (button & BUTTON_REL))
681 stop = true;
682 break;
686 restore = true;
687 boot_changed = false;
689 #endif
691 switch ( button ) {
692 #ifdef TREE_ENTER
693 case TREE_ENTER:
694 case TREE_ENTER | BUTTON_REPEAT:
695 #endif
696 #ifdef TREE_RC_RUN
697 case TREE_RC_RUN:
698 #endif
699 case TREE_RUN:
700 #ifdef TREE_RUN_PRE
701 if (((button == TREE_RUN)
702 #ifdef TREE_RC_RUN_PRE
703 || (button == TREE_RC_RUN))
704 && ((lastbutton != TREE_RC_RUN_PRE)
705 #endif
706 && (lastbutton != TREE_RUN_PRE)))
707 break;
708 #endif
709 if ( !numentries )
710 break;
712 if (id3db)
713 i = db_enter(&tc);
714 else
715 i = ft_enter(&tc);
717 switch (i)
719 case 1: reload_dir = true; break;
720 case 2: start_wps = true; break;
721 case 3: exit_func = true; break;
722 default: break;
725 #ifdef HAVE_LCD_BITMAP
726 /* maybe we have a new font */
727 tree_max_on_screen = recalc_screen_height();
728 #endif
729 /* make sure cursor is on screen */
730 while ( tc.dircursor > tree_max_on_screen )
732 tc.dircursor--;
733 tc.dirstart++;
736 restore = true;
737 break;
739 case TREE_EXIT:
740 case TREE_EXIT | BUTTON_REPEAT:
741 #ifdef TREE_RC_EXIT
742 case TREE_RC_EXIT:
743 #endif
744 if (*tc.dirfilter > NUM_FILTER_MODES && tc.dirlevel < 1) {
745 exit_func = true;
746 break;
749 if (!tc.dirlevel)
750 break;
752 if (id3db)
753 db_exit(&tc);
754 else
755 if (ft_exit(&tc) == 3)
756 exit_func = true;
758 restore = true;
759 break;
761 #ifdef TREE_OFF
762 #if (CONFIG_KEYPAD == RECORDER_PAD) && !defined(HAVE_SW_POWEROFF)
763 case TREE_OFF:
764 if (*tc.dirfilter < NUM_FILTER_MODES)
766 /* Stop the music if it is playing, else show the shutdown
767 screen */
768 if(audio_status())
769 audio_stop();
770 else {
771 if (!charger_inserted()) {
772 shutdown_screen();
773 } else {
774 charging_splash();
776 restore = true;
779 break;
780 #endif
781 #if defined(HAVE_CHARGING) && !defined(HAVE_POWEROFF_WHILE_CHARGING)
782 case TREE_OFF | BUTTON_REPEAT:
783 if (charger_inserted()) {
784 charging_splash();
785 restore = true;
787 break;
788 #endif
789 #endif
791 case TREE_PREV:
792 case TREE_PREV | BUTTON_REPEAT:
793 #ifdef TREE_RC_PREV
794 case TREE_RC_PREV:
795 case TREE_RC_PREV | BUTTON_REPEAT:
796 #endif
797 if (!tc.filesindir)
798 break;
800 /* start scrolling when at 1/3 of the screen */
801 if (tc.dircursor >=
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);
805 tc.dircursor--;
806 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
808 else {
809 if (tc.dirstart || tc.firstpos) {
810 if (tc.dirstart)
811 tc.dirstart--;
812 else {
813 if (tc.firstpos > max_files/2) {
814 tc.firstpos -= max_files/2;
815 tc.dirstart += max_files/2;
816 tc.dirstart--;
818 else {
819 tc.dirstart = tc.firstpos - 1;
820 tc.firstpos = 0;
823 restore = true;
825 else {
826 if (button & BUTTON_REPEAT)
827 break;
828 if (numentries < tree_max_on_screen) {
829 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
830 false);
831 tc.dircursor = numentries - 1;
832 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
833 true);
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();
843 update_all = true;
844 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
845 true);
847 else {
848 tc.dirstart = numentries - tree_max_on_screen;
849 tc.dircursor = tree_max_on_screen - 1;
850 restore = true;
854 need_update = true;
855 break;
857 case TREE_NEXT:
858 case TREE_NEXT | BUTTON_REPEAT:
859 #ifdef TREE_RC_NEXT
860 case TREE_RC_NEXT:
861 case TREE_RC_NEXT | BUTTON_REPEAT:
862 #endif
863 if (!tc.filesindir)
864 break;
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);
871 tc.dircursor++;
872 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
874 else {
875 tc.dirstart++;
876 restore = 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)
883 break;
884 tc.firstpos = tc.dirstart = tc.dircursor = 0;
886 else {
887 /* load next dir segment */
888 tc.firstpos += tc.dirstart;
889 tc.dirstart = 0;
891 restore = true;
893 else {
894 if (button & BUTTON_REPEAT)
895 break;
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);
901 else {
902 tc.dirstart = tc.dircursor = 0;
903 numentries = showdir();
904 update_all=true;
905 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
908 need_update = true;
909 break;
911 #ifdef TREE_PGUP
912 case TREE_PGUP:
913 case TREE_PGUP | BUTTON_REPEAT:
914 if (tc.dirstart) {
915 tc.dirstart -= tree_max_on_screen;
916 if ( tc.dirstart < 0 )
917 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;
925 else {
926 tc.dirstart = tc.firstpos - tree_max_on_screen;
927 tc.firstpos = 0;
930 else
931 tc.dircursor = 0;
932 restore = true;
933 break;
935 case TREE_PGDN:
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;
945 tc.dirstart = 0;
947 else
948 tc.dircursor = numentries - tc.dirstart - 1;
949 restore = true;
950 break;
951 #endif
953 case TREE_MENU:
954 #ifdef TREE_RC_MENU
955 case TREE_RC_MENU:
956 #endif
957 #ifdef TREE_MENU_PRE
958 if (lastbutton != TREE_MENU_PRE)
959 break;
960 #endif
961 /* don't enter menu from plugin browser */
962 if (*tc.dirfilter < NUM_FILTER_MODES)
964 lcd_stop_scroll();
965 if (main_menu())
966 reload_dir = true;
967 restore = true;
969 id3db = check_changed_id3mode(id3db);
971 else /* use it as a quick exit instead */
972 exit_func = true;
973 break;
975 case TREE_WPS:
976 #ifdef TREE_RC_WPS
977 case TREE_RC_WPS:
978 #endif
979 #ifdef TREE_WPS_PRE
980 if ((lastbutton != TREE_WPS_PRE)
981 #ifdef TREE_RC_WPS
982 && (lastbutton != TREE_RC_WPS_PRE)
983 #endif
985 break;
986 #endif
987 /* don't enter wps from plugin browser etc */
988 if (*tc.dirfilter < NUM_FILTER_MODES)
990 if (audio_status() & AUDIO_STATUS_PLAY)
992 start_wps=true;
994 else
996 start_resume(false);
997 restore = true;
1000 break;
1002 #ifdef TREE_QUICK
1003 case TREE_QUICK:
1004 /* don't enter f2 from plugin browser */
1005 if (*tc.dirfilter < NUM_FILTER_MODES)
1007 if (quick_screen(curr_context, TREE_QUICK))
1008 reload_dir = true;
1009 restore = true;
1011 id3db = check_changed_id3mode(id3db);
1012 break;
1015 #endif
1017 #ifdef BUTTON_F3
1018 case BUTTON_F3:
1019 /* don't enter f3 from plugin browser */
1020 if (*tc.dirfilter < NUM_FILTER_MODES)
1022 if (quick_screen(curr_context, BUTTON_F3))
1023 reload_dir = true;
1024 tree_max_on_screen = recalc_screen_height();
1025 restore = true;
1027 break;
1028 #endif
1030 case TREE_CONTEXT:
1031 #ifdef TREE_RC_CONTEXT
1032 case TREE_RC_CONTEXT:
1033 #endif
1034 #ifdef TREE_CONTEXT2
1035 case TREE_CONTEXT2:
1036 #endif
1038 int onplay_result;
1039 int attr = 0;
1041 if(!numentries)
1042 onplay_result = onplay(NULL, 0, curr_context);
1043 else {
1044 if (currdir[1])
1045 snprintf(buf, sizeof buf, "%s/%s",
1046 currdir, dircache[tc.dircursor+tc.dirstart].name);
1047 else
1048 snprintf(buf, sizeof buf, "/%s",
1049 dircache[tc.dircursor+tc.dirstart].name);
1050 if (id3db)
1051 switch (tc.currtable)
1053 case allsongs:
1054 case songs4album:
1055 case songs4artist:
1056 case searchsongs:
1057 attr=TREE_ATTR_MPA;
1058 break;
1060 else
1061 attr = dircache[tc.dircursor+tc.dirstart].attr;
1062 onplay_result = onplay(buf, attr, curr_context);
1065 switch (onplay_result)
1067 case ONPLAY_OK:
1068 restore = true;
1069 break;
1071 case ONPLAY_RELOAD_DIR:
1072 reload_dir = true;
1073 break;
1075 case ONPLAY_START_PLAY:
1076 start_wps = true;
1077 break;
1079 break;
1082 case BUTTON_NONE:
1083 if (thumbnail_time != -1 &&
1084 TIME_AFTER(current_tick, thumbnail_time))
1085 { /* a delayed hovering thumbnail is due now */
1086 int res;
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);
1097 else
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 */
1107 status_draw(false);
1108 break;
1110 #ifdef HAVE_HOTSWAP
1111 case SYS_FS_CHANGED:
1112 if (!id3db)
1113 reload_dir = true;
1114 /* The 'dir no longer valid' situation will be caught later
1115 * by checking the showdir() result. */
1116 break;
1117 #endif
1119 default:
1120 if (default_event_handler_ex(button, tree_prepare_usb, NULL)
1121 == SYS_USB_CONNECTED)
1123 tagdb_init(); /* re-init database */
1124 rundb_init();
1125 if(*tc.dirfilter > NUM_FILTER_MODES)
1126 /* leave sub-browsers after usb, doing otherwise
1127 might be confusing to the user */
1128 exit_func = true;
1129 else
1130 reload_dir = true;
1132 break;
1135 if ( button )
1137 ata_spin();
1138 lastbutton = button;
1141 if (start_wps)
1143 lcd_stop_scroll();
1144 if (wps_show() == SYS_USB_CONNECTED)
1145 reload_dir = true;
1146 #ifdef HAVE_HOTSWAP
1147 else
1148 if (!id3db) /* Try reload to catch 'no longer valid' case. */
1149 reload_dir = true;
1150 #endif
1151 #ifdef HAVE_LCD_BITMAP
1152 tree_max_on_screen = recalc_screen_height();
1153 #endif
1154 id3db = check_changed_id3mode(id3db);
1155 restore = true;
1156 start_wps=false;
1159 check_rescan:
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, "/");
1167 tc.dirlevel = 0;
1168 tc.currtable = 0;
1169 tc.currextra = 0;
1170 lasttable = -1;
1171 lastextra = -1;
1172 reload_root = false;
1174 if (! reload_dir )
1176 tc.dircursor = 0;
1177 tc.dirstart = 0;
1178 lastdir[0] = 0;
1181 lastfilter = *tc.dirfilter;
1182 lastsortcase = global_settings.sort_case;
1183 restore = true;
1184 button_clear_queue(); /* clear button queue */
1187 if (exit_func)
1188 break;
1190 if (restore || reload_dir) {
1191 /* restore display */
1193 #ifdef HAVE_LCD_BITMAP
1194 tree_max_on_screen = recalc_screen_height();
1195 #endif
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) {
1200 tc.dirstart++;
1201 tc.dircursor--;
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
1206 icon */
1207 lcd_setfont(FONT_UI);
1208 #endif
1209 numentries = showdir();
1210 if (currdir[1] && (numentries < 0))
1211 { /* not in root and reload failed */
1212 reload_root = true; /* try root */
1213 reload_dir = false;
1214 goto check_rescan;
1216 update_all = true;
1217 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
1219 need_update = true;
1220 reload_dir = false;
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) {
1228 char* name;
1229 int attr = 0;
1231 if (id3db)
1232 name = ((char**)tc.dircache)[lasti * tc.dentry_size];
1233 else {
1234 struct entry* dc = tc.dircache;
1235 struct entry* e = &dc[lasti];
1236 name = e->name;
1237 attr = e->attr;
1240 lcd_stop_scroll();
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 */
1247 lasti=i;
1248 lastdircursor=tc.dircursor;
1249 thumbnail_time = -1; /* cancel whatever we were about to say */
1251 if (id3db)
1252 name = ((char**)tc.dircache)[lasti * tc.dentry_size];
1253 else {
1254 struct entry* dc = tc.dircache;
1255 struct entry* e = &dc[lasti];
1256 name = e->name;
1257 attr = e->attr;
1259 showfileline(tc.dircursor, name, attr, true); /* scroll please */
1260 need_update = true;
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);
1269 break;
1271 case 2: /* dirs spelled */
1272 talk_spell(dircache[i].name, false);
1273 break;
1275 case 3: /* thumbnail clip */
1276 /* "schedule" a thumbnail, to have a little dalay */
1277 thumbnail_time = current_tick + HOVER_DELAY;
1278 break;
1280 default:
1281 break;
1284 else /* file */
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);
1290 break;
1292 case 2: /* files spelled */
1293 talk_spell(dircache[i].name, false);
1294 break;
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;
1300 else
1301 /* spell the number as fallback */
1302 talk_spell(dircache[i].name, false);
1303 break;
1305 default:
1306 break;
1312 if(need_update) {
1313 lcd_update();
1315 need_update = false;
1316 update_all = false;
1320 return true;
1323 static int plsize = 0;
1324 static bool add_dir(char* dirname, int len, int fd)
1326 bool abort = false;
1327 DIR* dir;
1329 /* check for user abort */
1330 #ifdef BUTTON_STOP
1331 if (button_get(false) == BUTTON_STOP)
1332 #else
1333 if (button_get(false) == BUTTON_OFF)
1334 #endif
1335 return true;
1337 dir = opendir(dirname);
1338 if(!dir)
1339 return true;
1341 while (true) {
1342 struct dirent *entry;
1344 entry = readdir(dir);
1345 if (!entry)
1346 break;
1347 if (entry->attribute & ATTR_DIRECTORY) {
1348 int dirlen = strlen(dirname);
1349 bool result;
1351 if (!strcmp(entry->d_name, ".") ||
1352 !strcmp(entry->d_name, ".."))
1353 continue;
1355 if (dirname[1])
1356 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
1357 else
1358 snprintf(dirname, len, "/%s", entry->d_name);
1360 result = add_dir(dirname, len, fd);
1361 dirname[dirlen] = '\0';
1362 if (result) {
1363 abort = true;
1364 break;
1367 else {
1368 int x = strlen(entry->d_name);
1369 int xl;
1370 unsigned int i;
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))
1379 char buf[8];
1380 write(fd, dirname, strlen(dirname));
1381 write(fd, "/", 1);
1382 write(fd, entry->d_name, x);
1383 write(fd, "\n", 1);
1385 plsize++;
1386 snprintf(buf, sizeof buf, "%d", plsize);
1387 #ifdef HAVE_LCD_BITMAP
1388 lcd_puts(0,4,buf);
1389 lcd_update();
1390 #else
1391 x = 10;
1392 if (plsize > 999)
1393 x=7;
1394 else {
1395 if (plsize > 99)
1396 x=8;
1397 else {
1398 if (plsize > 9)
1399 x=9;
1402 lcd_puts(x,0,buf);
1403 #endif
1409 closedir(dir);
1411 return abort;
1414 bool create_playlist(void)
1416 int fd;
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);
1425 lcd_update();
1427 fd = creat(filename,0);
1428 if (fd < 0)
1429 return false;
1431 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1432 cpu_boost(true);
1433 #endif
1435 snprintf(filename, sizeof(filename), "%s",
1436 tc.currdir[1] ? tc.currdir : "/");
1437 plsize = 0;
1438 add_dir(filename, sizeof(filename), fd);
1439 close(fd);
1441 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
1442 cpu_boost(false);
1443 #endif
1445 sleep(HZ);
1447 return true;
1450 bool rockbox_browse(const char *root, int dirfilter)
1452 static struct tree_context backup;
1454 backup = tc;
1455 reload_dir = true;
1456 memcpy(tc.currdir, root, sizeof(tc.currdir));
1457 start_wps = false;
1458 tc.dirfilter = &dirfilter;
1460 dirbrowse();
1462 tc = backup;
1463 reload_dir = true;
1465 return false;
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;
1478 tagdb_init();
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,
1488 char *filename)
1490 int i;
1491 int len=strlen(resume_file);
1493 if (!strcasecmp(&resume_file[len-4], ".m3u"))
1495 /* Playlist playback */
1496 char* slash;
1497 // check that the file exists
1498 int fd = open(resume_file, O_RDONLY);
1499 if(fd<0)
1500 return;
1501 close(fd);
1503 slash = strrchr(resume_file,'/');
1504 if (slash)
1506 char* cp;
1507 *slash=0;
1509 cp=resume_file;
1510 if (!cp[0])
1511 cp="/";
1513 if (playlist_create(cp, slash+1) != -1)
1515 if (global_settings.playlist_shuffle)
1516 playlist_shuffle(seed, -1);
1517 playlist_start(index,offset);
1519 *slash='/';
1522 else
1524 /* Directory playback */
1525 lastdir[0]='\0';
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,
1535 filename)))
1537 for ( i=0; i < playlist_amount(); i++ )
1539 if ((strcmp(strrchr(playlist_peek(i) + 1,'/') + 1,
1540 filename)) == 0)
1541 break;
1543 if (i < playlist_amount())
1544 index = i;
1545 else
1546 return;
1548 playlist_start(index,offset);
1552 start_wps=true;
1555 int ft_play_filenumber(int pos, int attr)
1557 /* try to find a voice ID for the extension, if known */
1558 unsigned int j;
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;
1565 break;
1569 talk_id(VOICE_FILE, false);
1570 talk_number(pos, true);
1571 talk_id(ext_id, true);
1572 return 1;
1575 int ft_play_dirname(int start_index)
1577 int fd;
1578 char dirname_mp3_filename[MAX_PATH+1];
1579 struct entry *dircache = tc.dircache;
1581 if (audio_status() & AUDIO_STATUS_PLAY)
1582 return 0;
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);
1591 if (fd < 0)
1593 DEBUGF("Failed to find: %s\n", dirname_mp3_filename);
1594 return -1;
1597 close(fd);
1599 DEBUGF("Found: %s\n", dirname_mp3_filename);
1601 talk_file(dirname_mp3_filename, false);
1602 return 1;
1605 void ft_play_filename(char *dir, char *file)
1607 char name_mp3_filename[MAX_PATH+1];
1609 if (audio_status() & AUDIO_STATUS_PLAY)
1610 return;
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);
1620 else
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);