Repaired broken exit from plugin/font/language browser
[kugel-rb.git] / apps / tree.c
blob06aabb7d360e9d43ef4ecb34bf71a475eeb5d3d2
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 = global_settings.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 * 2];
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 strcpy(tc.currdir, dir);
502 if (ft_load(&tc, NULL) < 0)
503 return;
504 lastdir[0] = 0;
506 ft_build_playlist(&tc, 0);
509 /* Returns the current working directory and also writes cwd to buf if
510 non-NULL. In case of error, returns NULL. */
511 char *getcwd(char *buf, int size)
513 if (!buf)
514 return tc.currdir;
515 else if (size > 0)
517 strncpy(buf, tc.currdir, size);
518 return buf;
520 else
521 return NULL;
524 /* Force a reload of the directory next time directory browser is called */
525 void reload_directory(void)
527 reload_dir = true;
530 static void start_resume(bool ask_once)
532 if ( global_settings.resume &&
533 global_settings.resume_index != -1 ) {
534 DEBUGF("Resume index %X offset %X\n",
535 global_settings.resume_index,
536 global_settings.resume_offset);
538 if (!ask_resume(ask_once))
539 return;
541 if (playlist_resume() != -1)
543 playlist_start(global_settings.resume_index,
544 global_settings.resume_offset);
546 start_wps = true;
548 else
549 return;
553 void set_current_file(char *path)
555 char *name;
556 unsigned int i;
558 /* separate directory from filename */
559 name = strrchr(path+1,'/');
560 if (name)
562 *name = 0;
563 strcpy(tc.currdir, path);
564 *name = '/';
565 name++;
567 else
569 strcpy(tc.currdir, "/");
570 name = path+1;
573 strcpy(lastfile, name);
575 tc.dircursor = 0;
576 tc.dirstart = -1;
578 if (strncmp(tc.currdir,lastdir,sizeof(lastdir)))
580 tc.dirlevel = 0;
581 tc.dirpos[tc.dirlevel] = -1;
582 tc.cursorpos[tc.dirlevel] = 0;
584 /* use '/' to calculate dirlevel */
585 for (i=1; i<strlen(path)+1; i++)
587 if (path[i] == '/')
589 tc.dirlevel++;
590 tc.dirpos[tc.dirlevel] = -1;
591 tc.cursorpos[tc.dirlevel] = 0;
597 static bool check_changed_id3mode(bool currmode)
599 if (currmode != (global_settings.dirfilter == SHOW_ID3DB)) {
600 currmode = global_settings.dirfilter == SHOW_ID3DB;
601 if (currmode) {
602 db_load(&tc);
604 else
605 ft_load(&tc, NULL);
607 return currmode;
610 static bool dirbrowse(void)
612 int numentries=0;
613 char buf[MAX_PATH];
614 int i;
615 int lasti=-1;
616 int button;
617 int tree_max_on_screen;
618 bool reload_root = false;
619 int lastfilter = *tc.dirfilter;
620 bool lastsortcase = global_settings.sort_case;
621 int lastdircursor=-1;
622 bool need_update = true;
623 bool exit_func = false;
624 long thumbnail_time = -1; /* for delaying a thumbnail */
625 bool update_all = false; /* set this to true when the whole file list
626 has been refreshed on screen */
627 int lastbutton = 0;
628 char* currdir = tc.currdir; /* just a shortcut */
629 bool id3db = global_settings.dirfilter == SHOW_ID3DB;
631 #ifdef HAVE_LCD_BITMAP
632 tree_max_on_screen = recalc_screen_height();
633 #else
634 tree_max_on_screen = TREE_MAX_ON_SCREEN;
635 #endif
637 tc.dircursor=0;
638 tc.dirstart=0;
639 tc.dirlevel=0;
640 tc.firstpos=0;
641 lasttable = -1;
642 lastextra = -1;
643 lastfirstpos = 0;
645 if (*tc.dirfilter < NUM_FILTER_MODES)
646 start_resume(true);
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);
661 while(1) {
662 struct entry *dircache = tc.dircache;
664 bool restore = false;
666 button = button_get_w_tmo(HZ/5);
668 #ifndef SIMULATOR
669 if (boot_changed) {
670 bool stop = false;
671 int button;
673 lcd_clear_display();
674 lcd_puts(0,0,str(LANG_BOOT_CHANGED));
675 lcd_puts(0,1,str(LANG_REBOOT_NOW));
676 #ifdef HAVE_LCD_BITMAP
677 lcd_puts(0,3,str(LANG_CONFIRM_WITH_PLAY_RECORDER));
678 lcd_puts(0,4,str(LANG_CANCEL_WITH_ANY_RECORDER));
679 lcd_update();
680 #endif
681 while (!stop) {
682 button = button_get(true);
683 switch (button) {
684 case TREE_RUN:
685 rolo_load("/" BOOTFILE);
686 stop = true;
687 break;
689 default:
690 if(default_event_handler(button) ||
691 (button & BUTTON_REL))
692 stop = true;
693 break;
697 restore = true;
698 boot_changed = false;
700 #endif
702 switch ( button ) {
703 #ifdef TREE_ENTER
704 case TREE_ENTER:
705 case TREE_ENTER | BUTTON_REPEAT:
706 #endif
707 #ifdef TREE_RC_ENTER
708 case TREE_RC_ENTER:
709 #endif
710 case TREE_RUN:
711 #ifdef TREE_RUN_PRE
712 if ((button == TREE_RUN) &&
713 (lastbutton != TREE_RUN_PRE))
714 break;
715 #endif
716 if ( !numentries )
717 break;
719 if (id3db)
720 db_enter(&tc);
721 else {
722 switch (ft_enter(&tc))
724 case 1: reload_dir = true; break;
725 case 2: reload_root = true; break;
726 case 3: start_wps = true; break;
727 case 4: exit_func = true; break;
728 default: break;
731 #ifdef HAVE_LCD_BITMAP
732 /* maybe we have a new font */
733 tree_max_on_screen = recalc_screen_height();
734 #endif
737 /* make sure cursor is on screen */
738 while ( tc.dircursor > tree_max_on_screen )
740 tc.dircursor--;
741 tc.dirstart++;
744 restore = true;
745 break;
747 case TREE_EXIT:
748 case TREE_EXIT | BUTTON_REPEAT:
749 #ifdef TREE_RC_EXIT
750 case TREE_RC_EXIT:
751 #endif
752 if (*tc.dirfilter > NUM_FILTER_MODES && tc.dirlevel < 1) {
753 exit_func = true;
754 break;
757 if (!tc.dirlevel)
758 break;
760 if (id3db)
761 db_exit(&tc);
762 else
763 if (ft_exit(&tc) == 4)
764 exit_func = true;
766 restore = true;
767 break;
769 #ifdef TREE_OFF
770 #ifndef HAVE_SW_POWEROFF
771 case TREE_OFF:
772 if (*tc.dirfilter < NUM_FILTER_MODES)
774 /* Stop the music if it is playing, else show the shutdown
775 screen */
776 if(mpeg_status())
777 mpeg_stop();
778 else {
779 if (!charger_inserted()) {
780 shutdown_screen();
781 } else {
782 charging_splash();
784 restore = true;
787 break;
788 #endif
789 case TREE_OFF | BUTTON_REPEAT:
790 if (charger_inserted()) {
791 charging_splash();
792 restore = true;
794 break;
795 #endif
797 case TREE_PREV:
798 case TREE_PREV | BUTTON_REPEAT:
799 #ifdef TREE_RC_PREV
800 case TREE_RC_PREV:
801 case TREE_RC_PREV | BUTTON_REPEAT:
802 #endif
803 if (!tc.filesindir)
804 break;
806 if (tc.dircursor) {
807 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
808 tc.dircursor--;
809 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
811 else {
812 if (tc.dirstart || tc.firstpos) {
813 if (tc.dirstart)
814 tc.dirstart--;
815 else {
816 if (tc.firstpos > max_files/2) {
817 tc.firstpos -= max_files/2;
818 tc.dirstart += max_files/2;
819 tc.dirstart--;
821 else {
822 tc.dirstart = tc.firstpos - 1;
823 tc.firstpos = 0;
826 restore = true;
828 else {
829 if (numentries < tree_max_on_screen) {
830 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
831 false);
832 tc.dircursor = numentries - 1;
833 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
834 true);
836 else if (id3db && tc.dirfull) {
837 /* load last dir segment */
838 /* use max_files/2 in case names are longer than
839 AVERAGE_FILE_LENGTH */
840 tc.firstpos = tc.dirlength - max_files/2;
841 tc.dirstart = tc.firstpos;
842 tc.dircursor = tree_max_on_screen - 1;
843 numentries = showdir();
844 update_all = true;
845 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
846 true);
848 else {
849 tc.dirstart = numentries - tree_max_on_screen;
850 tc.dircursor = tree_max_on_screen - 1;
851 restore = true;
855 need_update = true;
856 break;
858 case TREE_NEXT:
859 case TREE_NEXT | BUTTON_REPEAT:
860 #ifdef TREE_RC_NEXT
861 case TREE_RC_NEXT:
862 case TREE_RC_NEXT | BUTTON_REPEAT:
863 #endif
864 if (!tc.filesindir)
865 break;
867 if (tc.dircursor + tc.dirstart + 1 < numentries ) {
868 if(tc.dircursor+1 < tree_max_on_screen) {
869 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
870 tc.dircursor++;
871 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
873 else {
874 tc.dirstart++;
875 restore = true;
878 else if (id3db && (tc.firstpos || tc.dirfull)) {
879 if (tc.dircursor + tc.dirstart + tc.firstpos + 1 >= tc.dirlength) {
880 /* wrap and load first dir segment */
881 tc.firstpos = tc.dirstart = tc.dircursor = 0;
883 else {
884 /* load next dir segment */
885 tc.firstpos += tc.dirstart;
886 tc.dirstart = 0;
888 restore = true;
890 else {
891 if(numentries < tree_max_on_screen) {
892 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
893 tc.dirstart = tc.dircursor = 0;
894 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
896 else {
897 tc.dirstart = tc.dircursor = 0;
898 numentries = showdir();
899 update_all=true;
900 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
903 need_update = true;
904 break;
906 #ifdef TREE_PGUP
907 case TREE_PGUP:
908 case TREE_PGUP | BUTTON_REPEAT:
909 if (tc.dirstart) {
910 tc.dirstart -= tree_max_on_screen;
911 if ( tc.dirstart < 0 )
912 tc.dirstart = 0;
914 else if (tc.firstpos) {
915 if (tc.firstpos > max_files/2) {
916 tc.firstpos -= max_files/2;
917 tc.dirstart += max_files/2;
918 tc.dirstart -= tree_max_on_screen;
920 else {
921 tc.dirstart = tc.firstpos - tree_max_on_screen;
922 tc.firstpos = 0;
925 else
926 tc.dircursor = 0;
927 restore = true;
928 break;
930 case TREE_PGDN:
931 case TREE_PGDN | BUTTON_REPEAT:
932 if ( tc.dirstart < numentries - tree_max_on_screen ) {
933 tc.dirstart += tree_max_on_screen;
934 if ( tc.dirstart > numentries - tree_max_on_screen )
935 tc.dirstart = numentries - tree_max_on_screen;
937 else if (id3db && tc.dirfull) {
938 /* load next dir segment */
939 tc.firstpos += tc.dirstart;
940 tc.dirstart = 0;
942 else
943 tc.dircursor = numentries - tc.dirstart - 1;
944 restore = true;
945 break;
946 #endif
948 case TREE_MENU:
949 #ifdef TREE_MENU_PRE
950 if (lastbutton != TREE_MENU_PRE)
951 break;
952 #endif
953 /* don't enter menu from plugin browser */
954 if (*tc.dirfilter < NUM_FILTER_MODES)
956 lcd_stop_scroll();
957 if (main_menu())
958 reload_root = true;
959 restore = true;
961 id3db = check_changed_id3mode(id3db);
963 break;
965 case TREE_WPS:
966 #ifdef TREE_WPS_PRE
967 if (lastbutton != TREE_WPS_PRE)
968 break;
969 #endif
970 /* don't enter wps from plugin browser etc */
971 if (*tc.dirfilter < NUM_FILTER_MODES)
973 if (mpeg_status() & MPEG_STATUS_PLAY)
975 start_wps=true;
977 else
979 start_resume(false);
980 restore = true;
983 break;
985 #ifdef BUTTON_F2
986 case BUTTON_F2:
987 /* don't enter f2 from plugin browser */
988 if (*tc.dirfilter < NUM_FILTER_MODES)
990 if (quick_screen(CONTEXT_TREE, BUTTON_F2))
991 reload_root = true;
992 restore = true;
994 id3db = check_changed_id3mode(id3db);
995 break;
998 case BUTTON_F3:
999 /* don't enter f3 from plugin browser */
1000 if (*tc.dirfilter < NUM_FILTER_MODES)
1002 if (quick_screen(CONTEXT_TREE, BUTTON_F3))
1003 reload_root = true;
1004 tree_max_on_screen = recalc_screen_height();
1005 restore = true;
1007 break;
1008 #endif
1010 case TREE_CONTEXT:
1011 #ifdef TREE_CONTEXT2
1012 case TREE_CONTEXT2:
1013 #endif
1015 int onplay_result;
1017 if(!numentries)
1018 onplay_result = onplay(NULL, 0);
1019 else {
1020 if (currdir[1])
1021 snprintf(buf, sizeof buf, "%s/%s",
1022 currdir, dircache[tc.dircursor+tc.dirstart].name);
1023 else
1024 snprintf(buf, sizeof buf, "/%s",
1025 dircache[tc.dircursor+tc.dirstart].name);
1026 onplay_result = onplay(buf,
1027 dircache[tc.dircursor+tc.dirstart].attr);
1030 switch (onplay_result)
1032 case ONPLAY_OK:
1033 restore = true;
1034 break;
1036 case ONPLAY_RELOAD_DIR:
1037 reload_dir = true;
1038 break;
1040 case ONPLAY_START_PLAY:
1041 start_wps = true;
1042 break;
1044 break;
1047 case BUTTON_NONE:
1048 if (thumbnail_time != -1 &&
1049 TIME_AFTER(current_tick, thumbnail_time))
1050 { /* a delayed hovering thumbnail is due now */
1051 int res;
1052 if (dircache[lasti].attr & ATTR_DIRECTORY)
1054 DEBUGF("Playing directory thumbnail: %s", currdir);
1055 res = ft_play_dirname(lasti);
1056 if (res < 0) /* failed, not existing */
1057 { /* say the number instead, as a fallback */
1058 talk_id(VOICE_DIR, false);
1059 talk_number(lasti+1, true);
1062 else
1064 DEBUGF("Playing file thumbnail: %s/%s%s\n",
1065 currdir, dircache[lasti].name, file_thumbnail_ext);
1066 /* no fallback necessary, we knew in advance
1067 that the file exists */
1068 ft_play_filename(currdir, dircache[lasti].name);
1070 thumbnail_time = -1; /* job done */
1072 status_draw(false);
1073 break;
1075 default:
1076 if(default_event_handler(button) == SYS_USB_CONNECTED)
1078 if(*tc.dirfilter > NUM_FILTER_MODES)
1079 /* leave sub-browsers after usb, doing otherwise
1080 might be confusing to the user */
1081 exit_func = true;
1082 else
1083 reload_root = true;
1085 break;
1088 if ( button )
1090 ata_spin();
1091 lastbutton = button;
1094 if (start_wps)
1096 lcd_stop_scroll();
1097 if (wps_show() == SYS_USB_CONNECTED)
1098 reload_root = true;
1099 #ifdef HAVE_LCD_BITMAP
1100 tree_max_on_screen = recalc_screen_height();
1101 #endif
1102 restore = true;
1103 start_wps=false;
1106 /* do we need to rescan dir? */
1107 if (reload_dir || reload_root ||
1108 lastfilter != *tc.dirfilter ||
1109 lastsortcase != global_settings.sort_case)
1111 if ( reload_root ) {
1112 strcpy(currdir, "/");
1113 tc.dirlevel = 0;
1114 reload_root = false;
1116 if (! reload_dir )
1118 tc.dircursor = 0;
1119 tc.dirstart = 0;
1120 lastdir[0] = 0;
1123 lastfilter = *tc.dirfilter;
1124 lastsortcase = global_settings.sort_case;
1125 restore = true;
1126 while (button_get(false)); /* clear button queue */
1129 if (exit_func)
1130 break;
1132 if (restore || reload_dir) {
1133 /* restore display */
1135 #ifdef HAVE_LCD_BITMAP
1136 tree_max_on_screen = recalc_screen_height();
1137 #endif
1139 /* We need to adjust if the number of lines on screen have
1140 changed because of a status bar change */
1141 if(CURSOR_Y+LINE_Y+tc.dircursor>tree_max_on_screen) {
1142 tc.dirstart++;
1143 tc.dircursor--;
1145 #ifdef HAVE_LCD_BITMAP
1146 /* the sub-screen might've ruined the margins */
1147 lcd_setmargins(MARGIN_X,MARGIN_Y); /* leave room for cursor and
1148 icon */
1149 lcd_setfont(FONT_UI);
1150 #endif
1151 numentries = showdir();
1152 update_all = true;
1153 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
1155 need_update = true;
1156 reload_dir = false;
1159 if ( numentries && need_update) {
1160 i = tc.dirstart+tc.dircursor;
1162 /* if MP3 filter is on, cut off the extension */
1163 if(lasti!=i || restore) {
1164 char* name;
1165 int attr = 0;
1167 if (id3db)
1168 name = ((char**)tc.dircache)[lasti * 2];
1169 else {
1170 struct entry* dc = tc.dircache;
1171 struct entry* e = &dc[lasti];
1172 name = e->name;
1173 attr = e->attr;
1176 lcd_stop_scroll();
1178 /* So if lastdircursor and dircursor differ, and then full
1179 screen was not refreshed, restore the previous line */
1180 if ((lastdircursor != tc.dircursor) && !update_all ) {
1181 showfileline(lastdircursor, name, attr, false); /* no scroll */
1183 lasti=i;
1184 lastdircursor=tc.dircursor;
1185 thumbnail_time = -1; /* cancel whatever we were about to say */
1187 if (id3db)
1188 name = ((char**)tc.dircache)[lasti * 2];
1189 else {
1190 struct entry* dc = tc.dircache;
1191 struct entry* e = &dc[lasti];
1192 name = e->name;
1193 attr = e->attr;
1195 showfileline(tc.dircursor, name, attr, true); /* scroll please */
1196 need_update = true;
1198 if (dircache[i].attr & ATTR_DIRECTORY) /* directory? */
1200 /* play directory thumbnail */
1201 switch (global_settings.talk_dir) {
1202 case 1: /* dirs as numbers */
1203 talk_id(VOICE_DIR, false);
1204 talk_number(i+1, true);
1205 break;
1207 case 2: /* dirs spelled */
1208 talk_spell(dircache[i].name, false);
1209 break;
1211 case 3: /* thumbnail clip */
1212 /* "schedule" a thumbnail, to have a little dalay */
1213 thumbnail_time = current_tick + HOVER_DELAY;
1214 break;
1216 default:
1217 break;
1220 else /* file */
1222 switch (global_settings.talk_file) {
1223 case 1: /* files as numbers */
1224 ft_play_filenumber(i-tc.dirsindir+1,
1225 dircache[i].attr & TREE_ATTR_MASK);
1226 break;
1228 case 2: /* files spelled */
1229 talk_spell(dircache[i].name, false);
1230 break;
1232 case 3: /* thumbnail clip */
1233 /* "schedule" a thumbnail, to have a little delay */
1234 if (dircache[i].attr & TREE_ATTR_THUMBNAIL)
1235 thumbnail_time = current_tick + HOVER_DELAY;
1236 else
1237 /* spell the number as fallback */
1238 talk_spell(dircache[i].name, false);
1239 break;
1241 default:
1242 break;
1248 if(need_update) {
1249 lcd_update();
1251 need_update = false;
1252 update_all = false;
1256 return true;
1259 static int plsize = 0;
1260 static bool add_dir(char* dirname, int len, int fd)
1262 bool abort = false;
1263 DIR* dir;
1265 /* check for user abort */
1266 #ifdef BUTTON_STOP
1267 if (button_get(false) == BUTTON_STOP)
1268 #else
1269 if (button_get(false) == BUTTON_OFF)
1270 #endif
1271 return true;
1273 dir = opendir(dirname);
1274 if(!dir)
1275 return true;
1277 while (true) {
1278 struct dirent *entry;
1280 entry = readdir(dir);
1281 if (!entry)
1282 break;
1283 if (entry->attribute & ATTR_DIRECTORY) {
1284 int dirlen = strlen(dirname);
1285 bool result;
1287 if (!strcmp(entry->d_name, ".") ||
1288 !strcmp(entry->d_name, ".."))
1289 continue;
1291 if (dirname[1])
1292 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
1293 else
1294 snprintf(dirname, len, "/%s", entry->d_name);
1296 result = add_dir(dirname, len, fd);
1297 dirname[dirlen] = '\0';
1298 if (result) {
1299 abort = true;
1300 break;
1303 else {
1304 int x = strlen(entry->d_name);
1305 if ((!strcasecmp(&entry->d_name[x-4], ".mp3")) ||
1306 (!strcasecmp(&entry->d_name[x-4], ".mp2")) ||
1307 (!strcasecmp(&entry->d_name[x-4], ".mpa")))
1309 char buf[8];
1310 write(fd, dirname, strlen(dirname));
1311 write(fd, "/", 1);
1312 write(fd, entry->d_name, x);
1313 write(fd, "\n", 1);
1315 plsize++;
1316 snprintf(buf, sizeof buf, "%d", plsize);
1317 #ifdef HAVE_LCD_BITMAP
1318 lcd_puts(0,4,buf);
1319 lcd_update();
1320 #else
1321 x = 10;
1322 if (plsize > 999)
1323 x=7;
1324 else {
1325 if (plsize > 99)
1326 x=8;
1327 else {
1328 if (plsize > 9)
1329 x=9;
1332 lcd_puts(x,0,buf);
1333 #endif
1337 closedir(dir);
1339 return abort;
1342 bool create_playlist(void)
1344 int fd;
1345 char filename[MAX_PATH];
1347 snprintf(filename, sizeof filename, "%s.m3u",
1348 tc.currdir[1] ? tc.currdir : "/root");
1350 lcd_clear_display();
1351 lcd_puts(0,0,str(LANG_CREATING));
1352 lcd_puts_scroll(0,1,filename);
1353 lcd_update();
1355 fd = creat(filename,0);
1356 if (fd < 0)
1357 return false;
1359 snprintf(filename, sizeof(filename), "%s",
1360 tc.currdir[1] ? tc.currdir : "/");
1361 plsize = 0;
1362 add_dir(filename, sizeof(filename), fd);
1363 close(fd);
1364 sleep(HZ);
1366 return true;
1369 bool rockbox_browse(const char *root, int dirfilter)
1371 static struct tree_context backup;
1373 backup = tc;
1374 reload_dir = true;
1375 memcpy(tc.currdir, root, sizeof(tc.currdir));
1376 start_wps = false;
1377 tc.dirfilter = &dirfilter;
1379 dirbrowse();
1381 tc = backup;
1382 reload_dir = true;
1384 return false;
1387 void tree_init(void)
1389 /* We copy the settings value in case it is changed by the user. We can't
1390 use it until the next reboot. */
1391 max_files = global_settings.max_files_in_dir;
1393 /* initialize tree context struct */
1394 memset(&tc, 0, sizeof(tc));
1395 tc.dirfilter = &global_settings.dirfilter;
1397 db_init();
1399 tc.name_buffer_size = AVERAGE_FILENAME_LENGTH * max_files;
1400 tc.name_buffer = buffer_alloc(tc.name_buffer_size);
1402 tc.dircache_size = max_files * sizeof(struct entry);
1403 tc.dircache = buffer_alloc(tc.dircache_size);
1406 void bookmark_play(char *resume_file, int index, int offset, int seed,
1407 char *filename)
1409 int i;
1410 int len=strlen(resume_file);
1412 if (!strcasecmp(&resume_file[len-4], ".m3u"))
1414 /* Playlist playback */
1415 char* slash;
1416 // check that the file exists
1417 int fd = open(resume_file, O_RDONLY);
1418 if(fd<0)
1419 return;
1420 close(fd);
1422 slash = strrchr(resume_file,'/');
1423 if (slash)
1425 char* cp;
1426 *slash=0;
1428 cp=resume_file;
1429 if (!cp[0])
1430 cp="/";
1432 if (playlist_create(cp, slash+1) != -1)
1434 if (global_settings.playlist_shuffle)
1435 playlist_shuffle(seed, -1);
1436 playlist_start(index,offset);
1438 *slash='/';
1441 else
1443 /* Directory playback */
1444 lastdir[0]='\0';
1445 if (playlist_create(resume_file, NULL) != -1)
1447 resume_directory(resume_file);
1448 if (global_settings.playlist_shuffle)
1449 playlist_shuffle(seed, -1);
1451 /* Check if the file is at the same spot in the directory,
1452 else search for it */
1453 if ((strcmp(strrchr(playlist_peek(index) + 1,'/') + 1,
1454 filename)))
1456 for ( i=0; i < playlist_amount(); i++ )
1458 if ((strcmp(strrchr(playlist_peek(i) + 1,'/') + 1,
1459 filename)) == 0)
1460 break;
1462 if (i < playlist_amount())
1463 index = i;
1464 else
1465 return;
1467 playlist_start(index,offset);
1471 start_wps=true;
1474 int ft_play_filenumber(int pos, int attr)
1476 /* try to find a voice ID for the extension, if known */
1477 unsigned int j;
1478 int ext_id = -1; /* default to none */
1479 for (j=0; j<sizeof(filetypes)/sizeof(*filetypes); j++)
1481 if (attr == filetypes[j].tree_attr)
1483 ext_id = filetypes[j].voiceclip;
1484 break;
1488 talk_id(VOICE_FILE, false);
1489 talk_number(pos, true);
1490 talk_id(ext_id, true);
1491 return 1;
1494 int ft_play_dirname(int start_index)
1496 int fd;
1497 char dirname_mp3_filename[MAX_PATH+1];
1498 struct entry *dircache = tc.dircache;
1500 if (mpeg_status() & MPEG_STATUS_PLAY)
1501 return 0;
1503 snprintf(dirname_mp3_filename, sizeof(dirname_mp3_filename), "%s/%s/%s",
1504 tc.currdir, dircache[start_index].name, dir_thumbnail_name);
1506 DEBUGF("Checking for %s\n", dirname_mp3_filename);
1508 fd = open(dirname_mp3_filename, O_RDONLY);
1509 if (fd < 0)
1511 DEBUGF("Failed to find: %s\n", dirname_mp3_filename);
1512 return -1;
1515 close(fd);
1517 DEBUGF("Found: %s\n", dirname_mp3_filename);
1519 talk_file(dirname_mp3_filename, false);
1520 return 1;
1523 void ft_play_filename(char *dir, char *file)
1525 char name_mp3_filename[MAX_PATH+1];
1527 if (mpeg_status() & MPEG_STATUS_PLAY)
1528 return;
1530 if (strcasecmp(&file[strlen(file) - strlen(file_thumbnail_ext)],
1531 file_thumbnail_ext))
1532 { /* file has no .talk extension */
1533 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1534 "%s/%s%s", dir, file, file_thumbnail_ext);
1536 talk_file(name_mp3_filename, false);
1538 else
1539 { /* it already is a .talk file, play this directly */
1540 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1541 "%s/%s", dir, file);
1542 talk_id(LANG_VOICE_DIR_HOVER, false); /* prefix it */
1543 talk_file(name_mp3_filename, true);