Fixed another cause of 'stuck in subdir' bug.
[kugel-rb.git] / apps / tree.c
blob7c21dfecdf0284d9141f604a1bd09a5d6fc3de5a
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 "mpeg.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"
63 #ifdef HAVE_LCD_BITMAP
64 #include "widgets.h"
65 #endif
67 /* a table for the know file types */
68 const struct filetype filetypes[] = {
69 { ".mp3", TREE_ATTR_MPA, File, VOICE_EXT_MPA },
70 { ".mp2", TREE_ATTR_MPA, File, VOICE_EXT_MPA },
71 { ".mpa", TREE_ATTR_MPA, File, VOICE_EXT_MPA },
72 { ".m3u", TREE_ATTR_M3U, Playlist, LANG_PLAYINDICES_PLAYLIST },
73 { ".cfg", TREE_ATTR_CFG, Config, VOICE_EXT_CFG },
74 { ".wps", TREE_ATTR_WPS, Wps, VOICE_EXT_WPS },
75 { ".lng", TREE_ATTR_LNG, Language, LANG_LANGUAGE },
76 { ".rock",TREE_ATTR_ROCK,Plugin, VOICE_EXT_ROCK },
77 #ifdef HAVE_LCD_BITMAP
78 { ".fnt", TREE_ATTR_FONT,Font, VOICE_EXT_FONT },
79 #endif
80 { ".bmark",TREE_ATTR_BMARK, Bookmark, VOICE_EXT_BMARK },
81 #ifndef SIMULATOR
82 #ifdef HAVE_LCD_BITMAP
83 { ".ajz", TREE_ATTR_MOD, Mod_Ajz, VOICE_EXT_AJZ },
84 #else
85 { ".mod", TREE_ATTR_MOD, Mod_Ajz, VOICE_EXT_AJZ },
86 #endif
87 #endif /* #ifndef SIMULATOR */
90 static struct tree_context tc;
92 bool boot_changed = false;
94 char lastfile[MAX_PATH];
95 static char lastdir[MAX_PATH];
96 static int lasttable, lastextra, lastfirstpos;
97 static int max_files = 0;
99 static bool reload_dir = false;
101 static bool start_wps = false;
102 static bool dirbrowse(void);
104 bool check_rockboxdir(void)
106 DIR *dir = opendir(ROCKBOX_DIR);
107 if(!dir)
109 lcd_clear_display();
110 splash(HZ*2, true, str(LANG_NO_ROCKBOX_DIR));
111 lcd_clear_display();
112 splash(HZ*2, true, str(LANG_INSTALLATION_INCOMPLETE));
113 return false;
115 closedir(dir);
116 return true;
119 void browse_root(void)
121 filetype_init();
122 check_rockboxdir();
124 strcpy(tc.currdir, "/");
126 #ifndef SIMULATOR
127 dirbrowse();
129 #else
130 if (!dirbrowse()) {
131 DEBUGF("No filesystem found. Have you forgotten to create it?\n");
133 #endif
136 void tree_get_filetypes(const struct filetype** types, int* count)
138 *types = filetypes;
139 *count = sizeof(filetypes) / sizeof(*filetypes);
142 struct tree_context* tree_get_context(void)
144 return &tc;
147 #ifdef HAVE_LCD_BITMAP
149 /* pixel margins */
150 #define MARGIN_X (global_settings.scrollbar && \
151 tc.filesindir > tree_max_on_screen ? SCROLLBAR_WIDTH : 0) + \
152 CURSOR_WIDTH + (global_settings.show_icons && ICON_WIDTH > 0 ? ICON_WIDTH :0)
153 #define MARGIN_Y (global_settings.statusbar ? STATUSBAR_HEIGHT : 0)
155 /* position the entry-list starts at */
156 #define LINE_X 0
157 #define LINE_Y (global_settings.statusbar ? 1 : 0)
159 #define CURSOR_X (global_settings.scrollbar && \
160 tc.filesindir > tree_max_on_screen ? 1 : 0)
161 #define CURSOR_Y 0 /* the cursor is not positioned in regard to
162 the margins, so this is the amount of lines
163 we add to the cursor Y position to position
164 it on a line */
165 #define CURSOR_WIDTH (global_settings.invert_cursor ? 0 : 4)
167 #define ICON_WIDTH 6
169 #define SCROLLBAR_X 0
170 #define SCROLLBAR_Y lcd_getymargin()
171 #define SCROLLBAR_WIDTH 6
173 #else /* HAVE_LCD_BITMAP */
175 #define TREE_MAX_ON_SCREEN 2
176 #define TREE_MAX_LEN_DISPLAY 11 /* max length that fits on screen */
177 #define LINE_X 2 /* X position the entry-list starts at */
178 #define LINE_Y 0 /* Y position the entry-list starts at */
180 #define CURSOR_X 0
181 #define CURSOR_Y 0 /* not really used for players */
183 #endif /* HAVE_LCD_BITMAP */
185 /* talkbox hovering delay, to avoid immediate disk activity */
186 #define HOVER_DELAY (HZ/2)
188 static void showfileline(int line, char* name, int attr, bool scroll)
190 int xpos = LINE_X;
191 char* dotpos = NULL;
193 #ifdef HAVE_LCD_CHARCELLS
194 if (!global_settings.show_icons)
195 xpos--;
196 #endif
198 /* if any file filter is on, strip the extension */
199 if (*tc.dirfilter != SHOW_ID3DB &&
200 *tc.dirfilter != SHOW_ALL &&
201 !(attr & ATTR_DIRECTORY))
203 dotpos = strrchr(name, '.');
204 if (dotpos) {
205 *dotpos = 0;
209 if(scroll) {
210 #ifdef HAVE_LCD_BITMAP
211 lcd_setfont(FONT_UI);
212 if (global_settings.invert_cursor)
213 lcd_puts_scroll_style(xpos, line, name, STYLE_INVERT);
214 else
215 #endif
216 lcd_puts_scroll(xpos, line, name);
217 } else
218 lcd_puts(xpos, line, name);
220 /* Restore the dot before the extension if it was removed */
221 if (dotpos)
222 *dotpos = '.';
225 #ifdef HAVE_LCD_BITMAP
226 static int recalc_screen_height(void)
228 int fw, fh;
229 int height = LCD_HEIGHT;
231 lcd_setfont(FONT_UI);
232 lcd_getstringsize("A", &fw, &fh);
233 if(global_settings.statusbar)
234 height -= STATUSBAR_HEIGHT;
236 #if CONFIG_KEYPAD == RECORDER_PAD
237 if(global_settings.buttonbar)
238 height -= BUTTONBAR_HEIGHT;
239 #endif
241 return height / fh;
243 #endif
245 static int showdir(void)
247 struct entry *dircache = tc.dircache;
248 int i;
249 int tree_max_on_screen;
250 int start = tc.dirstart;
251 bool id3db = *tc.dirfilter == SHOW_ID3DB;
252 bool newdir = false;
253 #ifdef HAVE_LCD_BITMAP
254 const char* icon;
255 int line_height;
256 int fw, fh;
257 lcd_setfont(FONT_UI);
258 lcd_getstringsize("A", &fw, &fh);
259 tree_max_on_screen = recalc_screen_height();
260 line_height = fh;
261 #else
262 int icon;
263 tree_max_on_screen = TREE_MAX_ON_SCREEN;
264 #endif
266 /* new file dir? load it */
267 if (id3db) {
268 if (tc.currtable != lasttable ||
269 tc.currextra != lastextra ||
270 tc.firstpos != lastfirstpos)
272 if (db_load(&tc) < 0)
273 return -1;
274 lasttable = tc.currtable;
275 lastextra = tc.currextra;
276 lastfirstpos = tc.firstpos;
277 newdir = true;
280 else {
281 if (strncmp(tc.currdir, lastdir, sizeof(lastdir)) || reload_dir) {
282 if (ft_load(&tc, NULL) < 0)
283 return -1;
284 strcpy(lastdir, tc.currdir);
285 newdir = true;
289 if (newdir && !id3db &&
290 (tc.dirfull || tc.filesindir == global_settings.max_files_in_dir) )
292 #ifdef HAVE_LCD_CHARCELLS
293 lcd_double_height(false);
294 #endif
295 lcd_clear_display();
296 lcd_puts(0,0,str(LANG_SHOWDIR_ERROR_BUFFER));
297 lcd_puts(0,1,str(LANG_SHOWDIR_ERROR_FULL));
298 lcd_update();
299 sleep(HZ*2);
300 lcd_clear_display();
303 if (start == -1)
305 int diff_files;
307 /* use lastfile to determine start (default=0) */
308 start = 0;
310 for (i=0; i < tc.filesindir; i++)
312 struct entry *dircache = tc.dircache;
314 if (!strcasecmp(dircache[i].name, lastfile))
316 start = i;
317 break;
321 diff_files = tc.filesindir - start;
322 if (diff_files < tree_max_on_screen)
324 int oldstart = start;
326 start -= (tree_max_on_screen - diff_files);
327 if (start < 0)
328 start = 0;
330 tc.dircursor = oldstart - start;
333 tc.dirstart = start;
336 /* The cursor might point to an invalid line, for example if someone
337 deleted the last file in the dir */
338 if (tc.filesindir)
340 while (start + tc.dircursor >= tc.filesindir)
342 if (start)
343 start--;
344 else
345 if (tc.dircursor)
346 tc.dircursor--;
348 tc.dirstart = start;
351 #ifdef HAVE_LCD_CHARCELLS
352 lcd_stop_scroll();
353 lcd_double_height(false);
354 #endif
355 lcd_clear_display();
356 #ifdef HAVE_LCD_BITMAP
357 lcd_setmargins(MARGIN_X,MARGIN_Y); /* leave room for cursor and icon */
358 lcd_setfont(FONT_UI);
359 #endif
362 for ( i=start; i < start+tree_max_on_screen && i < tc.filesindir; i++ ) {
363 int line = i - start;
364 char* name;
365 int attr = 0;
367 if (id3db) {
368 name = ((char**)tc.dircache)[i * tc.dentry_size];
369 icon = db_get_icon(&tc);
371 else {
372 struct entry* dc = tc.dircache;
373 struct entry* e = &dc[i];
374 name = e->name;
375 attr = e->attr;
376 icon = filetype_get_icon(dircache[i].attr);
380 if (icon && global_settings.show_icons) {
381 #ifdef HAVE_LCD_BITMAP
382 int offset=0;
383 if ( line_height > 8 )
384 offset = (line_height - 8) / 2;
385 lcd_bitmap(icon,
386 CURSOR_X * 6 + CURSOR_WIDTH,
387 MARGIN_Y+(i-start)*line_height + offset,
388 6, 8, true);
389 #else
390 if (icon < 0 )
391 icon = Unknown;
392 lcd_putc(LINE_X-1, i-start, icon);
393 #endif
396 showfileline(line, name, attr, false); /* no scroll */
399 #ifdef HAVE_LCD_BITMAP
400 if (global_settings.scrollbar && (tc.dirlength > tree_max_on_screen))
401 scrollbar(SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH - 1,
402 tree_max_on_screen * line_height, tc.dirlength,
403 start + tc.firstpos,
404 start + tc.firstpos + tree_max_on_screen, VERTICAL);
406 #if CONFIG_KEYPAD == RECORDER_PAD
407 if(global_settings.buttonbar) {
408 buttonbar_set(*tc.dirfilter < NUM_FILTER_MODES ?
409 str(LANG_DIRBROWSE_F1) : (unsigned char *) "",
410 str(LANG_DIRBROWSE_F2),
411 str(LANG_DIRBROWSE_F3));
412 buttonbar_draw();
414 #endif
415 #endif
416 status_draw(true);
418 return tc.filesindir;
421 static bool ask_resume(bool ask_once)
423 int button;
424 bool stop = false;
425 static bool ignore_power = true;
427 #ifdef HAVE_LCD_CHARCELLS
428 lcd_double_height(false);
429 #endif
431 if (usb_detect()) {
432 default_event_handler(SYS_USB_CONNECTED);
433 return false;
436 /* always resume? */
437 if ( global_settings.resume == RESUME_ON )
438 return true;
440 lcd_clear_display();
441 lcd_puts(0,0,str(LANG_RESUME_ASK));
442 #ifdef HAVE_LCD_CHARCELLS
443 status_draw(false);
444 lcd_puts(0,1,str(LANG_RESUME_CONFIRM_PLAYER));
445 #else
446 lcd_puts(0,1,str(LANG_CONFIRM_WITH_PLAY_RECORDER));
447 lcd_puts(0,2,str(LANG_CANCEL_WITH_ANY_RECORDER));
448 #endif
449 lcd_update();
451 while (!stop) {
452 button = button_get(true);
453 switch (button) {
454 #ifdef TREE_RUN_PRE
455 case TREE_RUN_PRE: /* catch the press, not the release */
456 #else
457 case TREE_RUN:
458 #endif
459 #ifdef TREE_RC_RUN
460 case TREE_RC_RUN:
461 #endif
462 ignore_power = false;
463 /* Don't ignore the power button for subsequent calls */
464 return true;
466 #ifdef TREE_POWER_BTN
467 /* Initially ignore the button which powers on the box. It
468 might still be pressed since booting. */
469 case TREE_POWER_BTN:
470 case TREE_POWER_BTN | BUTTON_REPEAT:
471 if(!ignore_power)
472 stop = true;
473 break;
475 /* No longer ignore the power button after it was released */
476 case TREE_POWER_BTN | BUTTON_REL:
477 ignore_power = false;
478 break;
479 #endif
480 /* Handle sys events, ignore button releases */
481 default:
482 if(default_event_handler(button) || !(button & BUTTON_REL))
483 stop = true;
484 break;
488 if ( global_settings.resume == RESUME_ASK_ONCE && ask_once) {
489 global_settings.resume_index = -1;
490 settings_save();
493 ignore_power = false;
494 /* Don't ignore the power button for subsequent calls */
495 return false;
498 /* load tracks from specified directory to resume play */
499 void resume_directory(const char *dir)
501 if (ft_load(&tc, dir) < 0)
502 return;
503 lastdir[0] = 0;
505 ft_build_playlist(&tc, 0);
508 /* Returns the current working directory and also writes cwd to buf if
509 non-NULL. In case of error, returns NULL. */
510 char *getcwd(char *buf, int size)
512 if (!buf)
513 return tc.currdir;
514 else if (size > 0)
516 strncpy(buf, tc.currdir, size);
517 return buf;
519 else
520 return NULL;
523 /* Force a reload of the directory next time directory browser is called */
524 void reload_directory(void)
526 reload_dir = true;
529 static void start_resume(bool ask_once)
531 if ( global_settings.resume &&
532 global_settings.resume_index != -1 ) {
533 DEBUGF("Resume index %X offset %X\n",
534 global_settings.resume_index,
535 global_settings.resume_offset);
537 if (!ask_resume(ask_once))
538 return;
540 if (playlist_resume() != -1)
542 playlist_start(global_settings.resume_index,
543 global_settings.resume_offset);
545 start_wps = true;
547 else
548 return;
552 void set_current_file(char *path)
554 char *name;
555 unsigned int i;
557 /* separate directory from filename */
558 name = strrchr(path+1,'/');
559 if (name)
561 *name = 0;
562 strcpy(tc.currdir, path);
563 *name = '/';
564 name++;
566 else
568 strcpy(tc.currdir, "/");
569 name = path+1;
572 strcpy(lastfile, name);
574 tc.dircursor = 0;
575 tc.dirstart = -1;
577 if (strncmp(tc.currdir,lastdir,sizeof(lastdir)))
579 tc.dirlevel = 0;
580 tc.dirpos[tc.dirlevel] = -1;
581 tc.cursorpos[tc.dirlevel] = 0;
583 /* use '/' to calculate dirlevel */
584 for (i=1; i<strlen(path)+1; i++)
586 if (path[i] == '/')
588 tc.dirlevel++;
589 tc.dirpos[tc.dirlevel] = -1;
590 tc.cursorpos[tc.dirlevel] = 0;
596 static bool check_changed_id3mode(bool currmode)
598 if (currmode != (global_settings.dirfilter == SHOW_ID3DB)) {
599 currmode = global_settings.dirfilter == SHOW_ID3DB;
600 if (currmode) {
601 db_load(&tc);
603 else
604 ft_load(&tc, NULL);
606 return currmode;
609 static bool dirbrowse(void)
611 int numentries=0;
612 char buf[MAX_PATH];
613 int i;
614 int lasti=-1;
615 int button;
616 int tree_max_on_screen;
617 bool reload_root = false;
618 int lastfilter = *tc.dirfilter;
619 bool lastsortcase = global_settings.sort_case;
620 int lastdircursor=-1;
621 bool need_update = true;
622 bool exit_func = false;
623 long thumbnail_time = -1; /* for delaying a thumbnail */
624 bool update_all = false; /* set this to true when the whole file list
625 has been refreshed on screen */
626 int lastbutton = 0;
627 char* currdir = tc.currdir; /* just a shortcut */
628 bool id3db = *tc.dirfilter == SHOW_ID3DB;
630 #ifdef HAVE_LCD_BITMAP
631 tree_max_on_screen = recalc_screen_height();
632 #else
633 tree_max_on_screen = TREE_MAX_ON_SCREEN;
634 #endif
636 tc.dircursor=0;
637 tc.dirstart=0;
638 tc.dirlevel=0;
639 tc.firstpos=0;
640 lasttable = -1;
641 lastextra = -1;
642 lastfirstpos = 0;
644 if (*tc.dirfilter < NUM_FILTER_MODES)
645 start_resume(true);
647 if (!start_wps) {
648 numentries = showdir();
649 if (numentries == -1)
650 return false; /* currdir is not a directory */
652 if (*tc.dirfilter > NUM_FILTER_MODES && numentries==0)
654 splash(HZ*2, true, str(LANG_NO_FILES));
655 return false; /* No files found for rockbox_browser() */
657 update_all = true;
659 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
662 while(1) {
663 struct entry *dircache = tc.dircache;
665 bool restore = false;
667 button = button_get_w_tmo(HZ/5);
669 #ifndef SIMULATOR
670 if (boot_changed) {
671 bool stop = false;
672 int button;
674 lcd_clear_display();
675 lcd_puts(0,0,str(LANG_BOOT_CHANGED));
676 lcd_puts(0,1,str(LANG_REBOOT_NOW));
677 #ifdef HAVE_LCD_BITMAP
678 lcd_puts(0,3,str(LANG_CONFIRM_WITH_PLAY_RECORDER));
679 lcd_puts(0,4,str(LANG_CANCEL_WITH_ANY_RECORDER));
680 lcd_update();
681 #endif
682 while (!stop) {
683 button = button_get(true);
684 switch (button) {
685 case TREE_RUN:
686 rolo_load("/" BOOTFILE);
687 stop = true;
688 break;
690 default:
691 if(default_event_handler(button) ||
692 (button & BUTTON_REL))
693 stop = true;
694 break;
698 restore = true;
699 boot_changed = false;
701 #endif
703 switch ( button ) {
704 #ifdef TREE_ENTER
705 case TREE_ENTER:
706 case TREE_ENTER | BUTTON_REPEAT:
707 #endif
708 #ifdef TREE_RC_ENTER
709 case TREE_RC_ENTER:
710 #endif
711 case TREE_RUN:
712 #ifdef TREE_RUN_PRE
713 if ((button == TREE_RUN) &&
714 (lastbutton != TREE_RUN_PRE))
715 break;
716 #endif
717 if ( !numentries )
718 break;
720 if (id3db)
721 i = db_enter(&tc);
722 else
723 i = ft_enter(&tc);
725 switch (i)
727 case 1: reload_dir = true; break;
728 case 2: reload_root = true; break;
729 case 3: start_wps = true; break;
730 case 4: exit_func = true; break;
731 default: break;
734 #ifdef HAVE_LCD_BITMAP
735 /* maybe we have a new font */
736 tree_max_on_screen = recalc_screen_height();
737 #endif
738 /* make sure cursor is on screen */
739 while ( tc.dircursor > tree_max_on_screen )
741 tc.dircursor--;
742 tc.dirstart++;
745 restore = true;
746 break;
748 case TREE_EXIT:
749 case TREE_EXIT | BUTTON_REPEAT:
750 #ifdef TREE_RC_EXIT
751 case TREE_RC_EXIT:
752 #endif
753 if (*tc.dirfilter > NUM_FILTER_MODES && tc.dirlevel < 1) {
754 exit_func = true;
755 break;
758 if (!tc.dirlevel)
759 break;
761 if (id3db)
762 db_exit(&tc);
763 else
764 if (ft_exit(&tc) == 4)
765 exit_func = true;
767 restore = true;
768 break;
770 #ifdef TREE_OFF
771 #ifndef HAVE_SW_POWEROFF
772 case TREE_OFF:
773 if (*tc.dirfilter < NUM_FILTER_MODES)
775 /* Stop the music if it is playing, else show the shutdown
776 screen */
777 if(mpeg_status())
778 mpeg_stop();
779 else {
780 if (!charger_inserted()) {
781 shutdown_screen();
782 } else {
783 charging_splash();
785 restore = true;
788 break;
789 #endif
790 case TREE_OFF | BUTTON_REPEAT:
791 if (charger_inserted()) {
792 charging_splash();
793 restore = true;
795 break;
796 #endif
798 case TREE_PREV:
799 case TREE_PREV | BUTTON_REPEAT:
800 #ifdef TREE_RC_PREV
801 case TREE_RC_PREV:
802 case TREE_RC_PREV | BUTTON_REPEAT:
803 #endif
804 if (!tc.filesindir)
805 break;
807 if (tc.dircursor) {
808 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
809 tc.dircursor--;
810 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
812 else {
813 if (tc.dirstart || tc.firstpos) {
814 if (tc.dirstart)
815 tc.dirstart--;
816 else {
817 if (tc.firstpos > max_files/2) {
818 tc.firstpos -= max_files/2;
819 tc.dirstart += max_files/2;
820 tc.dirstart--;
822 else {
823 tc.dirstart = tc.firstpos - 1;
824 tc.firstpos = 0;
827 restore = true;
829 else {
830 if (numentries < tree_max_on_screen) {
831 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
832 false);
833 tc.dircursor = numentries - 1;
834 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
835 true);
837 else if (id3db && tc.dirfull) {
838 /* load last dir segment */
839 /* use max_files/2 in case names are longer than
840 AVERAGE_FILE_LENGTH */
841 tc.firstpos = tc.dirlength - max_files/2;
842 tc.dirstart = tc.firstpos;
843 tc.dircursor = tree_max_on_screen - 1;
844 numentries = showdir();
845 update_all = true;
846 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
847 true);
849 else {
850 tc.dirstart = numentries - tree_max_on_screen;
851 tc.dircursor = tree_max_on_screen - 1;
852 restore = true;
856 need_update = true;
857 break;
859 case TREE_NEXT:
860 case TREE_NEXT | BUTTON_REPEAT:
861 #ifdef TREE_RC_NEXT
862 case TREE_RC_NEXT:
863 case TREE_RC_NEXT | BUTTON_REPEAT:
864 #endif
865 if (!tc.filesindir)
866 break;
868 if (tc.dircursor + tc.dirstart + 1 < numentries ) {
869 if(tc.dircursor+1 < 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 tc.firstpos = tc.dirstart = tc.dircursor = 0;
884 else {
885 /* load next dir segment */
886 tc.firstpos += tc.dirstart;
887 tc.dirstart = 0;
889 restore = true;
891 else {
892 if(numentries < tree_max_on_screen) {
893 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
894 tc.dirstart = tc.dircursor = 0;
895 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
897 else {
898 tc.dirstart = tc.dircursor = 0;
899 numentries = showdir();
900 update_all=true;
901 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
904 need_update = true;
905 break;
907 #ifdef TREE_PGUP
908 case TREE_PGUP:
909 case TREE_PGUP | BUTTON_REPEAT:
910 if (tc.dirstart) {
911 tc.dirstart -= tree_max_on_screen;
912 if ( tc.dirstart < 0 )
913 tc.dirstart = 0;
915 else if (tc.firstpos) {
916 if (tc.firstpos > max_files/2) {
917 tc.firstpos -= max_files/2;
918 tc.dirstart += max_files/2;
919 tc.dirstart -= tree_max_on_screen;
921 else {
922 tc.dirstart = tc.firstpos - tree_max_on_screen;
923 tc.firstpos = 0;
926 else
927 tc.dircursor = 0;
928 restore = true;
929 break;
931 case TREE_PGDN:
932 case TREE_PGDN | BUTTON_REPEAT:
933 if ( tc.dirstart < numentries - tree_max_on_screen ) {
934 tc.dirstart += tree_max_on_screen;
935 if ( tc.dirstart > numentries - tree_max_on_screen )
936 tc.dirstart = numentries - tree_max_on_screen;
938 else if (id3db && tc.dirfull) {
939 /* load next dir segment */
940 tc.firstpos += tc.dirstart;
941 tc.dirstart = 0;
943 else
944 tc.dircursor = numentries - tc.dirstart - 1;
945 restore = true;
946 break;
947 #endif
949 case TREE_MENU:
950 #ifdef TREE_MENU_PRE
951 if (lastbutton != TREE_MENU_PRE)
952 break;
953 #endif
954 /* don't enter menu from plugin browser */
955 if (*tc.dirfilter < NUM_FILTER_MODES)
957 lcd_stop_scroll();
958 if (main_menu())
959 reload_root = true;
960 restore = true;
962 id3db = check_changed_id3mode(id3db);
964 break;
966 case TREE_WPS:
967 #ifdef TREE_WPS_PRE
968 if (lastbutton != TREE_WPS_PRE)
969 break;
970 #endif
971 /* don't enter wps from plugin browser etc */
972 if (*tc.dirfilter < NUM_FILTER_MODES)
974 if (mpeg_status() & MPEG_STATUS_PLAY)
976 start_wps=true;
978 else
980 start_resume(false);
981 restore = true;
984 break;
986 #ifdef BUTTON_F2
987 case BUTTON_F2:
988 /* don't enter f2 from plugin browser */
989 if (*tc.dirfilter < NUM_FILTER_MODES)
991 if (quick_screen(CONTEXT_TREE, BUTTON_F2))
992 reload_root = true;
993 restore = true;
995 id3db = check_changed_id3mode(id3db);
996 break;
999 case BUTTON_F3:
1000 /* don't enter f3 from plugin browser */
1001 if (*tc.dirfilter < NUM_FILTER_MODES)
1003 if (quick_screen(CONTEXT_TREE, BUTTON_F3))
1004 reload_root = true;
1005 tree_max_on_screen = recalc_screen_height();
1006 restore = true;
1008 break;
1009 #endif
1011 case TREE_CONTEXT:
1012 #ifdef TREE_CONTEXT2
1013 case TREE_CONTEXT2:
1014 #endif
1016 int onplay_result;
1017 int attr = 0;
1019 if(!numentries)
1020 onplay_result = onplay(NULL, 0);
1021 else {
1022 if (currdir[1])
1023 snprintf(buf, sizeof buf, "%s/%s",
1024 currdir, dircache[tc.dircursor+tc.dirstart].name);
1025 else
1026 snprintf(buf, sizeof buf, "/%s",
1027 dircache[tc.dircursor+tc.dirstart].name);
1028 if (!id3db)
1029 attr = dircache[tc.dircursor+tc.dirstart].attr;
1030 onplay_result = onplay(buf, attr);
1033 switch (onplay_result)
1035 case ONPLAY_OK:
1036 restore = true;
1037 break;
1039 case ONPLAY_RELOAD_DIR:
1040 reload_dir = true;
1041 break;
1043 case ONPLAY_START_PLAY:
1044 start_wps = true;
1045 break;
1047 break;
1050 case BUTTON_NONE:
1051 if (thumbnail_time != -1 &&
1052 TIME_AFTER(current_tick, thumbnail_time))
1053 { /* a delayed hovering thumbnail is due now */
1054 int res;
1055 if (dircache[lasti].attr & ATTR_DIRECTORY)
1057 DEBUGF("Playing directory thumbnail: %s", currdir);
1058 res = ft_play_dirname(lasti);
1059 if (res < 0) /* failed, not existing */
1060 { /* say the number instead, as a fallback */
1061 talk_id(VOICE_DIR, false);
1062 talk_number(lasti+1, true);
1065 else
1067 DEBUGF("Playing file thumbnail: %s/%s%s\n",
1068 currdir, dircache[lasti].name, file_thumbnail_ext);
1069 /* no fallback necessary, we knew in advance
1070 that the file exists */
1071 ft_play_filename(currdir, dircache[lasti].name);
1073 thumbnail_time = -1; /* job done */
1075 status_draw(false);
1076 break;
1078 default:
1079 if(default_event_handler(button) == SYS_USB_CONNECTED)
1081 if(*tc.dirfilter > NUM_FILTER_MODES)
1082 /* leave sub-browsers after usb, doing otherwise
1083 might be confusing to the user */
1084 exit_func = true;
1085 else
1086 reload_root = true;
1088 break;
1091 if ( button )
1093 ata_spin();
1094 lastbutton = button;
1097 if (start_wps)
1099 lcd_stop_scroll();
1100 if (wps_show() == SYS_USB_CONNECTED)
1101 reload_root = true;
1102 #ifdef HAVE_LCD_BITMAP
1103 tree_max_on_screen = recalc_screen_height();
1104 #endif
1105 id3db = check_changed_id3mode(id3db);
1106 restore = true;
1107 start_wps=false;
1110 /* do we need to rescan dir? */
1111 if (reload_dir || reload_root ||
1112 lastfilter != *tc.dirfilter ||
1113 lastsortcase != global_settings.sort_case)
1115 if ( reload_root ) {
1116 strcpy(currdir, "/");
1117 tc.dirlevel = 0;
1118 reload_root = false;
1120 if (! reload_dir )
1122 tc.dircursor = 0;
1123 tc.dirstart = 0;
1124 lastdir[0] = 0;
1127 lastfilter = *tc.dirfilter;
1128 lastsortcase = global_settings.sort_case;
1129 restore = true;
1130 while (button_get(false)); /* clear button queue */
1133 if (exit_func)
1134 break;
1136 if (restore || reload_dir) {
1137 /* restore display */
1139 #ifdef HAVE_LCD_BITMAP
1140 tree_max_on_screen = recalc_screen_height();
1141 #endif
1143 /* We need to adjust if the number of lines on screen have
1144 changed because of a status bar change */
1145 if(CURSOR_Y+LINE_Y+tc.dircursor>tree_max_on_screen) {
1146 tc.dirstart++;
1147 tc.dircursor--;
1149 #ifdef HAVE_LCD_BITMAP
1150 /* the sub-screen might've ruined the margins */
1151 lcd_setmargins(MARGIN_X,MARGIN_Y); /* leave room for cursor and
1152 icon */
1153 lcd_setfont(FONT_UI);
1154 #endif
1155 numentries = showdir();
1156 update_all = true;
1157 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
1159 need_update = true;
1160 reload_dir = false;
1163 if ( numentries && need_update) {
1164 i = tc.dirstart+tc.dircursor;
1166 /* if MP3 filter is on, cut off the extension */
1167 if(lasti!=i || restore) {
1168 char* name;
1169 int attr = 0;
1171 if (id3db)
1172 name = ((char**)tc.dircache)[lasti * tc.dentry_size];
1173 else {
1174 struct entry* dc = tc.dircache;
1175 struct entry* e = &dc[lasti];
1176 name = e->name;
1177 attr = e->attr;
1180 lcd_stop_scroll();
1182 /* So if lastdircursor and dircursor differ, and then full
1183 screen was not refreshed, restore the previous line */
1184 if ((lastdircursor != tc.dircursor) && !update_all ) {
1185 showfileline(lastdircursor, name, attr, false); /* no scroll */
1187 lasti=i;
1188 lastdircursor=tc.dircursor;
1189 thumbnail_time = -1; /* cancel whatever we were about to say */
1191 if (id3db)
1192 name = ((char**)tc.dircache)[lasti * tc.dentry_size];
1193 else {
1194 struct entry* dc = tc.dircache;
1195 struct entry* e = &dc[lasti];
1196 name = e->name;
1197 attr = e->attr;
1199 showfileline(tc.dircursor, name, attr, true); /* scroll please */
1200 need_update = true;
1202 if (dircache[i].attr & ATTR_DIRECTORY) /* directory? */
1204 /* play directory thumbnail */
1205 switch (global_settings.talk_dir) {
1206 case 1: /* dirs as numbers */
1207 talk_id(VOICE_DIR, false);
1208 talk_number(i+1, true);
1209 break;
1211 case 2: /* dirs spelled */
1212 talk_spell(dircache[i].name, false);
1213 break;
1215 case 3: /* thumbnail clip */
1216 /* "schedule" a thumbnail, to have a little dalay */
1217 thumbnail_time = current_tick + HOVER_DELAY;
1218 break;
1220 default:
1221 break;
1224 else /* file */
1226 switch (global_settings.talk_file) {
1227 case 1: /* files as numbers */
1228 ft_play_filenumber(i-tc.dirsindir+1,
1229 dircache[i].attr & TREE_ATTR_MASK);
1230 break;
1232 case 2: /* files spelled */
1233 talk_spell(dircache[i].name, false);
1234 break;
1236 case 3: /* thumbnail clip */
1237 /* "schedule" a thumbnail, to have a little delay */
1238 if (dircache[i].attr & TREE_ATTR_THUMBNAIL)
1239 thumbnail_time = current_tick + HOVER_DELAY;
1240 else
1241 /* spell the number as fallback */
1242 talk_spell(dircache[i].name, false);
1243 break;
1245 default:
1246 break;
1252 if(need_update) {
1253 lcd_update();
1255 need_update = false;
1256 update_all = false;
1260 return true;
1263 static int plsize = 0;
1264 static bool add_dir(char* dirname, int len, int fd)
1266 bool abort = false;
1267 DIR* dir;
1269 /* check for user abort */
1270 #ifdef BUTTON_STOP
1271 if (button_get(false) == BUTTON_STOP)
1272 #else
1273 if (button_get(false) == BUTTON_OFF)
1274 #endif
1275 return true;
1277 dir = opendir(dirname);
1278 if(!dir)
1279 return true;
1281 while (true) {
1282 struct dirent *entry;
1284 entry = readdir(dir);
1285 if (!entry)
1286 break;
1287 if (entry->attribute & ATTR_DIRECTORY) {
1288 int dirlen = strlen(dirname);
1289 bool result;
1291 if (!strcmp(entry->d_name, ".") ||
1292 !strcmp(entry->d_name, ".."))
1293 continue;
1295 if (dirname[1])
1296 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
1297 else
1298 snprintf(dirname, len, "/%s", entry->d_name);
1300 result = add_dir(dirname, len, fd);
1301 dirname[dirlen] = '\0';
1302 if (result) {
1303 abort = true;
1304 break;
1307 else {
1308 int x = strlen(entry->d_name);
1309 if ((!strcasecmp(&entry->d_name[x-4], ".mp3")) ||
1310 (!strcasecmp(&entry->d_name[x-4], ".mp2")) ||
1311 (!strcasecmp(&entry->d_name[x-4], ".mpa")))
1313 char buf[8];
1314 write(fd, dirname, strlen(dirname));
1315 write(fd, "/", 1);
1316 write(fd, entry->d_name, x);
1317 write(fd, "\n", 1);
1319 plsize++;
1320 snprintf(buf, sizeof buf, "%d", plsize);
1321 #ifdef HAVE_LCD_BITMAP
1322 lcd_puts(0,4,buf);
1323 lcd_update();
1324 #else
1325 x = 10;
1326 if (plsize > 999)
1327 x=7;
1328 else {
1329 if (plsize > 99)
1330 x=8;
1331 else {
1332 if (plsize > 9)
1333 x=9;
1336 lcd_puts(x,0,buf);
1337 #endif
1341 closedir(dir);
1343 return abort;
1346 bool create_playlist(void)
1348 int fd;
1349 char filename[MAX_PATH];
1351 snprintf(filename, sizeof filename, "%s.m3u",
1352 tc.currdir[1] ? tc.currdir : "/root");
1354 lcd_clear_display();
1355 lcd_puts(0,0,str(LANG_CREATING));
1356 lcd_puts_scroll(0,1,filename);
1357 lcd_update();
1359 fd = creat(filename,0);
1360 if (fd < 0)
1361 return false;
1363 snprintf(filename, sizeof(filename), "%s",
1364 tc.currdir[1] ? tc.currdir : "/");
1365 plsize = 0;
1366 add_dir(filename, sizeof(filename), fd);
1367 close(fd);
1368 sleep(HZ);
1370 return true;
1373 bool rockbox_browse(const char *root, int dirfilter)
1375 static struct tree_context backup;
1377 backup = tc;
1378 reload_dir = true;
1379 memcpy(tc.currdir, root, sizeof(tc.currdir));
1380 start_wps = false;
1381 tc.dirfilter = &dirfilter;
1383 dirbrowse();
1385 tc = backup;
1386 reload_dir = true;
1388 return false;
1391 void tree_init(void)
1393 /* We copy the settings value in case it is changed by the user. We can't
1394 use it until the next reboot. */
1395 max_files = global_settings.max_files_in_dir;
1397 /* initialize tree context struct */
1398 memset(&tc, 0, sizeof(tc));
1399 tc.dirfilter = &global_settings.dirfilter;
1401 db_init();
1403 tc.name_buffer_size = AVERAGE_FILENAME_LENGTH * max_files;
1404 tc.name_buffer = buffer_alloc(tc.name_buffer_size);
1406 tc.dircache_size = max_files * sizeof(struct entry);
1407 tc.dircache = buffer_alloc(tc.dircache_size);
1410 void bookmark_play(char *resume_file, int index, int offset, int seed,
1411 char *filename)
1413 int i;
1414 int len=strlen(resume_file);
1416 if (!strcasecmp(&resume_file[len-4], ".m3u"))
1418 /* Playlist playback */
1419 char* slash;
1420 // check that the file exists
1421 int fd = open(resume_file, O_RDONLY);
1422 if(fd<0)
1423 return;
1424 close(fd);
1426 slash = strrchr(resume_file,'/');
1427 if (slash)
1429 char* cp;
1430 *slash=0;
1432 cp=resume_file;
1433 if (!cp[0])
1434 cp="/";
1436 if (playlist_create(cp, slash+1) != -1)
1438 if (global_settings.playlist_shuffle)
1439 playlist_shuffle(seed, -1);
1440 playlist_start(index,offset);
1442 *slash='/';
1445 else
1447 /* Directory playback */
1448 lastdir[0]='\0';
1449 if (playlist_create(resume_file, NULL) != -1)
1451 resume_directory(resume_file);
1452 if (global_settings.playlist_shuffle)
1453 playlist_shuffle(seed, -1);
1455 /* Check if the file is at the same spot in the directory,
1456 else search for it */
1457 if ((strcmp(strrchr(playlist_peek(index) + 1,'/') + 1,
1458 filename)))
1460 for ( i=0; i < playlist_amount(); i++ )
1462 if ((strcmp(strrchr(playlist_peek(i) + 1,'/') + 1,
1463 filename)) == 0)
1464 break;
1466 if (i < playlist_amount())
1467 index = i;
1468 else
1469 return;
1471 playlist_start(index,offset);
1475 start_wps=true;
1478 int ft_play_filenumber(int pos, int attr)
1480 /* try to find a voice ID for the extension, if known */
1481 unsigned int j;
1482 int ext_id = -1; /* default to none */
1483 for (j=0; j<sizeof(filetypes)/sizeof(*filetypes); j++)
1485 if (attr == filetypes[j].tree_attr)
1487 ext_id = filetypes[j].voiceclip;
1488 break;
1492 talk_id(VOICE_FILE, false);
1493 talk_number(pos, true);
1494 talk_id(ext_id, true);
1495 return 1;
1498 int ft_play_dirname(int start_index)
1500 int fd;
1501 char dirname_mp3_filename[MAX_PATH+1];
1502 struct entry *dircache = tc.dircache;
1504 if (mpeg_status() & MPEG_STATUS_PLAY)
1505 return 0;
1507 snprintf(dirname_mp3_filename, sizeof(dirname_mp3_filename), "%s/%s/%s",
1508 tc.currdir, dircache[start_index].name, dir_thumbnail_name);
1510 DEBUGF("Checking for %s\n", dirname_mp3_filename);
1512 fd = open(dirname_mp3_filename, O_RDONLY);
1513 if (fd < 0)
1515 DEBUGF("Failed to find: %s\n", dirname_mp3_filename);
1516 return -1;
1519 close(fd);
1521 DEBUGF("Found: %s\n", dirname_mp3_filename);
1523 talk_file(dirname_mp3_filename, false);
1524 return 1;
1527 void ft_play_filename(char *dir, char *file)
1529 char name_mp3_filename[MAX_PATH+1];
1531 if (mpeg_status() & MPEG_STATUS_PLAY)
1532 return;
1534 if (strcasecmp(&file[strlen(file) - strlen(file_thumbnail_ext)],
1535 file_thumbnail_ext))
1536 { /* file has no .talk extension */
1537 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1538 "%s/%s%s", dir, file, file_thumbnail_ext);
1540 talk_file(name_mp3_filename, false);
1542 else
1543 { /* it already is a .talk file, play this directly */
1544 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1545 "%s/%s", dir, file);
1546 talk_id(LANG_VOICE_DIR_HOVER, false); /* prefix it */
1547 talk_file(name_mp3_filename, true);