Don't die on opendir() failure. Index .mp2 files too.
[kugel-rb.git] / apps / tree.c
blobb844060322398815d258b9f8e7d1ffd2d182f6e8
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 char lastdir[MAX_PATH];
97 static bool reload_dir = false;
99 static bool start_wps = false;
100 static bool dirbrowse(void);
102 bool check_rockboxdir(void)
104 DIR *dir = opendir(ROCKBOX_DIR);
105 if(!dir)
107 lcd_clear_display();
108 splash(HZ*2, true, str(LANG_NO_ROCKBOX_DIR));
109 lcd_clear_display();
110 splash(HZ*2, true, str(LANG_INSTALLATION_INCOMPLETE));
111 return false;
113 closedir(dir);
114 return true;
117 void browse_root(void)
119 filetype_init();
120 check_rockboxdir();
122 strcpy(tc.currdir, "/");
124 #ifndef SIMULATOR
125 dirbrowse();
127 #else
128 if (!dirbrowse()) {
129 DEBUGF("No filesystem found. Have you forgotten to create it?\n");
131 #endif
134 void tree_get_filetypes(const struct filetype** types, int* count)
136 *types = filetypes;
137 *count = sizeof(filetypes) / sizeof(*filetypes);
140 struct tree_context* tree_get_context(void)
142 return &tc;
145 #ifdef HAVE_LCD_BITMAP
147 /* pixel margins */
148 #define MARGIN_X (global_settings.scrollbar && \
149 tc.filesindir > tree_max_on_screen ? SCROLLBAR_WIDTH : 0) + \
150 CURSOR_WIDTH + (global_settings.show_icons && ICON_WIDTH > 0 ? ICON_WIDTH :0)
151 #define MARGIN_Y (global_settings.statusbar ? STATUSBAR_HEIGHT : 0)
153 /* position the entry-list starts at */
154 #define LINE_X 0
155 #define LINE_Y (global_settings.statusbar ? 1 : 0)
157 #define CURSOR_X (global_settings.scrollbar && \
158 tc.filesindir > tree_max_on_screen ? 1 : 0)
159 #define CURSOR_Y 0 /* the cursor is not positioned in regard to
160 the margins, so this is the amount of lines
161 we add to the cursor Y position to position
162 it on a line */
163 #define CURSOR_WIDTH (global_settings.invert_cursor ? 0 : 4)
165 #define ICON_WIDTH 6
167 #define SCROLLBAR_X 0
168 #define SCROLLBAR_Y lcd_getymargin()
169 #define SCROLLBAR_WIDTH 6
171 #else /* HAVE_LCD_BITMAP */
173 #define TREE_MAX_ON_SCREEN 2
174 #define TREE_MAX_LEN_DISPLAY 11 /* max length that fits on screen */
175 #define LINE_X 2 /* X position the entry-list starts at */
176 #define LINE_Y 0 /* Y position the entry-list starts at */
178 #define CURSOR_X 0
179 #define CURSOR_Y 0 /* not really used for players */
181 #endif /* HAVE_LCD_BITMAP */
183 /* talkbox hovering delay, to avoid immediate disk activity */
184 #define HOVER_DELAY (HZ/2)
186 static void showfileline(int line, char* name, int attr, bool scroll)
188 int xpos = LINE_X;
189 char* dotpos = NULL;
191 #ifdef HAVE_LCD_CHARCELLS
192 if (!global_settings.show_icons)
193 xpos--;
194 #endif
196 /* if any file filter is on, strip the extension */
197 if (*tc.dirfilter != SHOW_ID3DB &&
198 *tc.dirfilter != SHOW_ALL &&
199 !(attr & ATTR_DIRECTORY))
201 dotpos = strrchr(name, '.');
202 if (dotpos) {
203 *dotpos = 0;
207 if(scroll) {
208 #ifdef HAVE_LCD_BITMAP
209 lcd_setfont(FONT_UI);
210 if (global_settings.invert_cursor)
211 lcd_puts_scroll_style(xpos, line, name, STYLE_INVERT);
212 else
213 #endif
214 lcd_puts_scroll(xpos, line, name);
215 } else
216 lcd_puts(xpos, line, name);
218 /* Restore the dot before the extension if it was removed */
219 if (dotpos)
220 *dotpos = '.';
223 #ifdef HAVE_LCD_BITMAP
224 static int recalc_screen_height(void)
226 int fw, fh;
227 int height = LCD_HEIGHT;
229 lcd_setfont(FONT_UI);
230 lcd_getstringsize("A", &fw, &fh);
231 if(global_settings.statusbar)
232 height -= STATUSBAR_HEIGHT;
234 #if CONFIG_KEYPAD == RECORDER_PAD
235 if(global_settings.buttonbar)
236 height -= BUTTONBAR_HEIGHT;
237 #endif
239 return height / fh;
241 #endif
243 static int showdir(void)
245 struct entry *dircache = tc.dircache;
246 int i;
247 int tree_max_on_screen;
248 bool dir_buffer_full = false;
249 int start = tc.dirstart;
250 bool id3db = global_settings.dirfilter == SHOW_ID3DB;
252 #ifdef HAVE_LCD_BITMAP
253 const char* icon;
254 int line_height;
255 int fw, fh;
256 lcd_setfont(FONT_UI);
257 lcd_getstringsize("A", &fw, &fh);
258 tree_max_on_screen = recalc_screen_height();
259 line_height = fh;
260 #else
261 int icon;
262 tree_max_on_screen = TREE_MAX_ON_SCREEN;
263 #endif
265 /* new file dir? load it */
266 if (id3db) {
267 if (db_load(&tc, &dir_buffer_full) < 0)
268 return -1;
270 else {
271 if (strncmp(tc.currdir, lastdir, sizeof(lastdir)) || reload_dir) {
272 if (ft_load(&tc, &dir_buffer_full) < 0)
273 return -1;
277 if ( dir_buffer_full || tc.filesindir == global_settings.max_files_in_dir ) {
278 #ifdef HAVE_LCD_CHARCELLS
279 lcd_double_height(false);
280 #endif
281 lcd_clear_display();
282 lcd_puts(0,0,str(LANG_SHOWDIR_ERROR_BUFFER));
283 lcd_puts(0,1,str(LANG_SHOWDIR_ERROR_FULL));
284 lcd_update();
285 sleep(HZ*2);
286 lcd_clear_display();
289 if (start == -1)
291 int diff_files;
293 /* use lastfile to determine start (default=0) */
294 start = 0;
296 for (i=0; i < tc.filesindir; i++)
298 struct entry *dircache = tc.dircache;
300 if (!strcasecmp(dircache[i].name, lastfile))
302 start = i;
303 break;
307 diff_files = tc.filesindir - start;
308 if (diff_files < tree_max_on_screen)
310 int oldstart = start;
312 start -= (tree_max_on_screen - diff_files);
313 if (start < 0)
314 start = 0;
316 tc.dircursor = oldstart - start;
319 tc.dirstart = start;
322 /* The cursor might point to an invalid line, for example if someone
323 deleted the last file in the dir */
324 if (tc.filesindir)
326 while (start + tc.dircursor >= tc.filesindir)
328 if (start)
329 start--;
330 else
331 if (tc.dircursor)
332 tc.dircursor--;
334 tc.dirstart = start;
337 #ifdef HAVE_LCD_CHARCELLS
338 lcd_stop_scroll();
339 lcd_double_height(false);
340 #endif
341 lcd_clear_display();
342 #ifdef HAVE_LCD_BITMAP
343 lcd_setmargins(MARGIN_X,MARGIN_Y); /* leave room for cursor and icon */
344 lcd_setfont(FONT_UI);
345 #endif
348 for ( i=start; i < start+tree_max_on_screen && i < tc.filesindir; i++ ) {
349 int line = i - start;
350 char* name;
351 int attr = 0;
353 if (id3db) {
354 name = ((char**)tc.dircache)[i * 2];
355 icon = db_get_icon(&tc);
357 else {
358 struct entry* dc = tc.dircache;
359 struct entry* e = &dc[i];
360 name = e->name;
361 attr = e->attr;
362 icon = filetype_get_icon(dircache[i].attr);
366 if (icon && global_settings.show_icons) {
367 #ifdef HAVE_LCD_BITMAP
368 int offset=0;
369 if ( line_height > 8 )
370 offset = (line_height - 8) / 2;
371 lcd_bitmap(icon,
372 CURSOR_X * 6 + CURSOR_WIDTH,
373 MARGIN_Y+(i-start)*line_height + offset,
374 6, 8, true);
375 #else
376 if (icon < 0 )
377 icon = Unknown;
378 lcd_putc(LINE_X-1, i-start, icon);
379 #endif
382 showfileline(line, name, attr, false); /* no scroll */
385 #ifdef HAVE_LCD_BITMAP
386 if (global_settings.scrollbar && (tc.filesindir > tree_max_on_screen))
387 scrollbar(SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH - 1,
388 tree_max_on_screen * line_height, tc.filesindir, start,
389 start + tree_max_on_screen, VERTICAL);
391 #if CONFIG_KEYPAD == RECORDER_PAD
392 if(global_settings.buttonbar) {
393 buttonbar_set(*tc.dirfilter < NUM_FILTER_MODES ?
394 str(LANG_DIRBROWSE_F1) : (unsigned char *) "",
395 str(LANG_DIRBROWSE_F2),
396 str(LANG_DIRBROWSE_F3));
397 buttonbar_draw();
399 #endif
400 #endif
401 status_draw(true);
403 return tc.filesindir;
406 static bool ask_resume(bool ask_once)
408 int button;
409 bool stop = false;
410 static bool ignore_power = true;
412 #ifdef HAVE_LCD_CHARCELLS
413 lcd_double_height(false);
414 #endif
416 if (usb_detect()) {
417 default_event_handler(SYS_USB_CONNECTED);
418 return false;
421 /* always resume? */
422 if ( global_settings.resume == RESUME_ON )
423 return true;
425 lcd_clear_display();
426 lcd_puts(0,0,str(LANG_RESUME_ASK));
427 #ifdef HAVE_LCD_CHARCELLS
428 status_draw(false);
429 lcd_puts(0,1,str(LANG_RESUME_CONFIRM_PLAYER));
430 #else
431 lcd_puts(0,1,str(LANG_CONFIRM_WITH_PLAY_RECORDER));
432 lcd_puts(0,2,str(LANG_CANCEL_WITH_ANY_RECORDER));
433 #endif
434 lcd_update();
436 while (!stop) {
437 button = button_get(true);
438 switch (button) {
439 #ifdef TREE_RUN_PRE
440 case TREE_RUN_PRE: /* catch the press, not the release */
441 #else
442 case TREE_RUN:
443 #endif
444 #ifdef TREE_RC_RUN
445 case TREE_RC_RUN:
446 #endif
447 ignore_power = false;
448 /* Don't ignore the power button for subsequent calls */
449 return true;
451 #ifdef TREE_POWER_BTN
452 /* Initially ignore the button which powers on the box. It
453 might still be pressed since booting. */
454 case TREE_POWER_BTN:
455 case TREE_POWER_BTN | BUTTON_REPEAT:
456 if(!ignore_power)
457 stop = true;
458 break;
460 /* No longer ignore the power button after it was released */
461 case TREE_POWER_BTN | BUTTON_REL:
462 ignore_power = false;
463 break;
464 #endif
465 /* Handle sys events, ignore button releases */
466 default:
467 if(default_event_handler(button) || !(button & BUTTON_REL))
468 stop = true;
469 break;
473 if ( global_settings.resume == RESUME_ASK_ONCE && ask_once) {
474 global_settings.resume_index = -1;
475 settings_save();
478 ignore_power = false;
479 /* Don't ignore the power button for subsequent calls */
480 return false;
483 /* load tracks from specified directory to resume play */
484 void resume_directory(const char *dir)
486 bool buffer_full;
488 strcpy(tc.currdir, dir);
489 if (!ft_load(&tc, &buffer_full))
490 return;
491 lastdir[0] = 0;
493 ft_build_playlist(&tc, 0);
496 /* Returns the current working directory and also writes cwd to buf if
497 non-NULL. In case of error, returns NULL. */
498 char *getcwd(char *buf, int size)
500 if (!buf)
501 return tc.currdir;
502 else if (size > 0)
504 strncpy(buf, tc.currdir, size);
505 return buf;
507 else
508 return NULL;
511 /* Force a reload of the directory next time directory browser is called */
512 void reload_directory(void)
514 reload_dir = true;
517 static void start_resume(bool ask_once)
519 if ( global_settings.resume &&
520 global_settings.resume_index != -1 ) {
521 DEBUGF("Resume index %X offset %X\n",
522 global_settings.resume_index,
523 global_settings.resume_offset);
525 if (!ask_resume(ask_once))
526 return;
528 if (playlist_resume() != -1)
530 playlist_start(global_settings.resume_index,
531 global_settings.resume_offset);
533 start_wps = true;
535 else
536 return;
540 void set_current_file(char *path)
542 char *name;
543 unsigned int i;
545 /* separate directory from filename */
546 name = strrchr(path+1,'/');
547 if (name)
549 *name = 0;
550 strcpy(tc.currdir, path);
551 *name = '/';
552 name++;
554 else
556 strcpy(tc.currdir, "/");
557 name = path+1;
560 strcpy(lastfile, name);
562 tc.dircursor = 0;
563 tc.dirstart = -1;
565 if (strncmp(tc.currdir,lastdir,sizeof(lastdir)))
567 tc.dirlevel = 0;
568 tc.dirpos[tc.dirlevel] = -1;
569 tc.cursorpos[tc.dirlevel] = 0;
571 /* use '/' to calculate dirlevel */
572 for (i=1; i<strlen(path)+1; i++)
574 if (path[i] == '/')
576 tc.dirlevel++;
577 tc.dirpos[tc.dirlevel] = -1;
578 tc.cursorpos[tc.dirlevel] = 0;
584 static bool check_changed_id3mode(bool currmode)
586 if (currmode != (global_settings.dirfilter == SHOW_ID3DB)) {
587 currmode = global_settings.dirfilter == SHOW_ID3DB;
588 if (currmode) {
589 db_load(&tc, NULL);
591 else
592 ft_load(&tc, NULL);
594 return currmode;
597 static bool dirbrowse(void)
599 int numentries=0;
600 char buf[MAX_PATH];
601 int i;
602 int lasti=-1;
603 int button;
604 int tree_max_on_screen;
605 bool reload_root = false;
606 int lastfilter = *tc.dirfilter;
607 bool lastsortcase = global_settings.sort_case;
608 int lastdircursor=-1;
609 bool need_update = true;
610 bool exit_func = false;
611 long thumbnail_time = -1; /* for delaying a thumbnail */
612 bool update_all = false; /* set this to true when the whole file list
613 has been refreshed on screen */
614 int lastbutton = 0;
615 char* currdir = tc.currdir; /* just a shortcut */
616 bool id3db = global_settings.dirfilter == SHOW_ID3DB;
618 #ifdef HAVE_LCD_BITMAP
619 tree_max_on_screen = recalc_screen_height();
620 #else
621 tree_max_on_screen = TREE_MAX_ON_SCREEN;
622 #endif
624 tc.dircursor=0;
625 tc.dirstart=0;
626 tc.dirlevel=0;
628 if (*tc.dirfilter < NUM_FILTER_MODES)
629 start_resume(true);
631 numentries = showdir();
632 if (numentries == -1)
633 return false; /* currdir is not a directory */
635 if (*tc.dirfilter > NUM_FILTER_MODES && numentries==0)
637 splash(HZ*2, true, str(LANG_NO_FILES));
638 return false; /* No files found for rockbox_browser() */
640 update_all = true;
642 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
644 while(1) {
645 struct entry *dircache = tc.dircache;
647 bool restore = false;
649 button = button_get_w_tmo(HZ/5);
651 #ifndef SIMULATOR
652 if (boot_changed) {
653 bool stop = false;
654 int button;
656 lcd_clear_display();
657 lcd_puts(0,0,str(LANG_BOOT_CHANGED));
658 lcd_puts(0,1,str(LANG_REBOOT_NOW));
659 #ifdef HAVE_LCD_BITMAP
660 lcd_puts(0,3,str(LANG_CONFIRM_WITH_PLAY_RECORDER));
661 lcd_puts(0,4,str(LANG_CANCEL_WITH_ANY_RECORDER));
662 lcd_update();
663 #endif
664 while (!stop) {
665 button = button_get(true);
666 switch (button) {
667 case TREE_RUN:
668 rolo_load("/" BOOTFILE);
669 stop = true;
670 break;
672 default:
673 if(default_event_handler(button) ||
674 (button & BUTTON_REL))
675 stop = true;
676 break;
680 restore = true;
681 boot_changed = false;
683 #endif
685 switch ( button ) {
686 #ifdef TREE_ENTER
687 case TREE_ENTER:
688 case TREE_ENTER | BUTTON_REPEAT:
689 #endif
690 #ifdef TREE_RC_ENTER
691 case TREE_RC_ENTER:
692 #endif
693 case TREE_RUN:
694 #ifdef TREE_RUN_PRE
695 if ((button == TREE_RUN) &&
696 (lastbutton != TREE_RUN_PRE))
697 break;
698 #endif
699 if ( !numentries )
700 break;
702 if (id3db)
703 db_enter(&tc);
704 else {
705 switch (ft_enter(&tc))
707 case 1: reload_dir = true; break;
708 case 2: reload_root = true; break;
709 case 3: start_wps = true; break;
710 case 4: exit_func = true; break;
711 default: break;
714 #ifdef HAVE_LCD_BITMAP
715 /* maybe we have a new font */
716 tree_max_on_screen = recalc_screen_height();
717 #endif
720 /* make sure cursor is on screen */
721 while ( tc.dircursor > tree_max_on_screen )
723 tc.dircursor--;
724 tc.dirstart++;
727 restore = true;
728 break;
730 case TREE_EXIT:
731 case TREE_EXIT | BUTTON_REPEAT:
732 #ifdef TREE_RC_EXIT
733 case TREE_RC_EXIT:
734 #endif
735 if (!tc.dirlevel)
736 break;
738 if (id3db)
739 db_exit(&tc);
740 else
741 if (ft_exit(&tc) == 4)
742 exit_func = true;
744 restore = true;
745 break;
747 #ifdef TREE_OFF
748 #ifndef HAVE_SW_POWEROFF
749 case TREE_OFF:
750 if (*tc.dirfilter < NUM_FILTER_MODES)
752 /* Stop the music if it is playing, else show the shutdown
753 screen */
754 if(mpeg_status())
755 mpeg_stop();
756 else {
757 if (!charger_inserted()) {
758 shutdown_screen();
759 } else {
760 charging_splash();
762 restore = true;
765 break;
766 #endif
767 case TREE_OFF | BUTTON_REPEAT:
768 if (charger_inserted()) {
769 charging_splash();
770 restore = true;
772 break;
773 #endif
775 case TREE_PREV:
776 case TREE_PREV | BUTTON_REPEAT:
777 #ifdef TREE_RC_PREV
778 case TREE_RC_PREV:
779 case TREE_RC_PREV | BUTTON_REPEAT:
780 #endif
781 if(tc.filesindir) {
782 if(tc.dircursor) {
783 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
784 tc.dircursor--;
785 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
787 else {
788 if (tc.dirstart) {
789 tc.dirstart--;
790 numentries = showdir();
791 update_all=true;
792 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
794 else {
795 if (numentries < tree_max_on_screen) {
796 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
797 false);
798 tc.dircursor = numentries - 1;
799 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor,
800 true);
802 else {
803 tc.dirstart = numentries - tree_max_on_screen;
804 tc.dircursor = tree_max_on_screen - 1;
805 numentries = showdir();
806 update_all = true;
807 put_cursorxy(CURSOR_X, CURSOR_Y +
808 tree_max_on_screen - 1, true);
812 need_update = true;
814 break;
816 case TREE_NEXT:
817 case TREE_NEXT | BUTTON_REPEAT:
818 #ifdef TREE_RC_NEXT
819 case TREE_RC_NEXT:
820 case TREE_RC_NEXT | BUTTON_REPEAT:
821 #endif
822 if(tc.filesindir)
824 if (tc.dircursor + tc.dirstart + 1 < numentries ) {
825 if(tc.dircursor+1 < tree_max_on_screen) {
826 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
827 tc.dircursor++;
828 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
830 else {
831 tc.dirstart++;
832 numentries = showdir();
833 update_all = true;
834 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
837 else {
838 if(numentries < tree_max_on_screen) {
839 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, false);
840 tc.dirstart = tc.dircursor = 0;
841 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
843 else {
844 tc.dirstart = tc.dircursor = 0;
845 numentries = showdir();
846 update_all=true;
847 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
850 need_update = true;
852 break;
854 #ifdef TREE_PGUP
855 case TREE_PGUP:
856 case TREE_PGUP | BUTTON_REPEAT:
857 if ( tc.dirstart ) {
858 tc.dirstart -= tree_max_on_screen;
859 if ( tc.dirstart < 0 )
860 tc.dirstart = 0;
862 else
863 tc.dircursor = 0;
864 restore = true;
865 break;
867 case TREE_PGDN:
868 case TREE_PGDN | BUTTON_REPEAT:
869 if ( tc.dirstart < numentries - tree_max_on_screen ) {
870 tc.dirstart += tree_max_on_screen;
871 if ( tc.dirstart >
872 numentries - tree_max_on_screen )
873 tc.dirstart = numentries - tree_max_on_screen;
875 else
876 tc.dircursor = numentries - tc.dirstart - 1;
877 restore = true;
878 break;
879 #endif
881 case TREE_MENU:
882 #ifdef TREE_MENU_PRE
883 if (lastbutton != TREE_MENU_PRE)
884 break;
885 #endif
886 /* don't enter menu from plugin browser */
887 if (*tc.dirfilter < NUM_FILTER_MODES)
889 lcd_stop_scroll();
890 if (main_menu())
891 reload_root = true;
892 restore = true;
894 id3db = check_changed_id3mode(id3db);
896 break;
898 case TREE_WPS:
899 #ifdef TREE_WPS_PRE
900 if (lastbutton != TREE_WPS_PRE)
901 break;
902 #endif
903 /* don't enter wps from plugin browser etc */
904 if (*tc.dirfilter < NUM_FILTER_MODES)
906 if (mpeg_status() & MPEG_STATUS_PLAY)
908 start_wps=true;
910 else
912 start_resume(false);
913 restore = true;
916 break;
918 #ifdef BUTTON_F2
919 case BUTTON_F2:
920 /* don't enter f2 from plugin browser */
921 if (*tc.dirfilter < NUM_FILTER_MODES)
923 if (quick_screen(CONTEXT_TREE, BUTTON_F2))
924 reload_root = true;
925 restore = true;
927 id3db = check_changed_id3mode(id3db);
928 break;
931 case BUTTON_F3:
932 /* don't enter f3 from plugin browser */
933 if (*tc.dirfilter < NUM_FILTER_MODES)
935 if (quick_screen(CONTEXT_TREE, BUTTON_F3))
936 reload_root = true;
937 tree_max_on_screen = recalc_screen_height();
938 restore = true;
940 break;
941 #endif
943 case TREE_CONTEXT:
944 #ifdef TREE_CONTEXT2
945 case TREE_CONTEXT2:
946 #endif
948 int onplay_result;
950 if(!numentries)
951 onplay_result = onplay(NULL, 0);
952 else {
953 if (currdir[1])
954 snprintf(buf, sizeof buf, "%s/%s",
955 currdir, dircache[tc.dircursor+tc.dirstart].name);
956 else
957 snprintf(buf, sizeof buf, "/%s",
958 dircache[tc.dircursor+tc.dirstart].name);
959 onplay_result = onplay(buf,
960 dircache[tc.dircursor+tc.dirstart].attr);
963 switch (onplay_result)
965 case ONPLAY_OK:
966 restore = true;
967 break;
969 case ONPLAY_RELOAD_DIR:
970 reload_dir = true;
971 break;
973 case ONPLAY_START_PLAY:
974 start_wps = true;
975 break;
977 break;
980 case BUTTON_NONE:
981 if (thumbnail_time != -1 &&
982 TIME_AFTER(current_tick, thumbnail_time))
983 { /* a delayed hovering thumbnail is due now */
984 int res;
985 if (dircache[lasti].attr & ATTR_DIRECTORY)
987 DEBUGF("Playing directory thumbnail: %s", currdir);
988 res = ft_play_dirname(lasti);
989 if (res < 0) /* failed, not existing */
990 { /* say the number instead, as a fallback */
991 talk_id(VOICE_DIR, false);
992 talk_number(lasti+1, true);
995 else
997 DEBUGF("Playing file thumbnail: %s/%s%s\n",
998 currdir, dircache[lasti].name, file_thumbnail_ext);
999 /* no fallback necessary, we knew in advance
1000 that the file exists */
1001 ft_play_filename(currdir, dircache[lasti].name);
1003 thumbnail_time = -1; /* job done */
1005 status_draw(false);
1006 break;
1008 default:
1009 if(default_event_handler(button) == SYS_USB_CONNECTED)
1011 if(*tc.dirfilter > NUM_FILTER_MODES)
1012 /* leave sub-browsers after usb, doing otherwise
1013 might be confusing to the user */
1014 exit_func = true;
1015 else
1016 reload_root = true;
1018 break;
1021 if ( button )
1023 ata_spin();
1024 lastbutton = button;
1027 if (start_wps)
1029 lcd_stop_scroll();
1030 if (wps_show() == SYS_USB_CONNECTED)
1031 reload_root = true;
1032 #ifdef HAVE_LCD_BITMAP
1033 tree_max_on_screen = recalc_screen_height();
1034 #endif
1035 restore = true;
1036 start_wps=false;
1039 /* do we need to rescan dir? */
1040 if (reload_dir || reload_root ||
1041 lastfilter != *tc.dirfilter ||
1042 lastsortcase != global_settings.sort_case)
1044 if ( reload_root ) {
1045 strcpy(currdir, "/");
1046 tc.dirlevel = 0;
1047 reload_root = false;
1049 if (! reload_dir )
1051 tc.dircursor = 0;
1052 tc.dirstart = 0;
1053 lastdir[0] = 0;
1056 lastfilter = *tc.dirfilter;
1057 lastsortcase = global_settings.sort_case;
1058 restore = true;
1059 while (button_get(false)); /* clear button queue */
1062 if (exit_func)
1063 break;
1065 if (restore || reload_dir) {
1066 /* restore display */
1068 #ifdef HAVE_LCD_BITMAP
1069 tree_max_on_screen = recalc_screen_height();
1070 #endif
1072 /* We need to adjust if the number of lines on screen have
1073 changed because of a status bar change */
1074 if(CURSOR_Y+LINE_Y+tc.dircursor>tree_max_on_screen) {
1075 tc.dirstart++;
1076 tc.dircursor--;
1078 #ifdef HAVE_LCD_BITMAP
1079 /* the sub-screen might've ruined the margins */
1080 lcd_setmargins(MARGIN_X,MARGIN_Y); /* leave room for cursor and
1081 icon */
1082 lcd_setfont(FONT_UI);
1083 #endif
1084 numentries = showdir();
1085 update_all = true;
1086 put_cursorxy(CURSOR_X, CURSOR_Y + tc.dircursor, true);
1088 need_update = true;
1089 reload_dir = false;
1092 if ( numentries && need_update) {
1093 i = tc.dirstart+tc.dircursor;
1095 /* if MP3 filter is on, cut off the extension */
1096 if(lasti!=i || restore) {
1097 char* name;
1098 int attr = 0;
1100 if (id3db)
1101 name = ((char**)tc.dircache)[lasti * 2];
1102 else {
1103 struct entry* dc = tc.dircache;
1104 struct entry* e = &dc[lasti];
1105 name = e->name;
1106 attr = e->attr;
1109 lcd_stop_scroll();
1111 /* So if lastdircursor and dircursor differ, and then full
1112 screen was not refreshed, restore the previous line */
1113 if ((lastdircursor != tc.dircursor) && !update_all ) {
1114 showfileline(lastdircursor, name, attr, false); /* no scroll */
1116 lasti=i;
1117 lastdircursor=tc.dircursor;
1118 thumbnail_time = -1; /* cancel whatever we were about to say */
1120 if (id3db)
1121 name = ((char**)tc.dircache)[lasti * 2];
1122 else {
1123 struct entry* dc = tc.dircache;
1124 struct entry* e = &dc[lasti];
1125 name = e->name;
1126 attr = e->attr;
1128 showfileline(tc.dircursor, name, attr, true); /* scroll please */
1129 need_update = true;
1131 if (dircache[i].attr & ATTR_DIRECTORY) /* directory? */
1133 /* play directory thumbnail */
1134 switch (global_settings.talk_dir) {
1135 case 1: /* dirs as numbers */
1136 talk_id(VOICE_DIR, false);
1137 talk_number(i+1, true);
1138 break;
1140 case 2: /* dirs spelled */
1141 talk_spell(dircache[i].name, false);
1142 break;
1144 case 3: /* thumbnail clip */
1145 /* "schedule" a thumbnail, to have a little dalay */
1146 thumbnail_time = current_tick + HOVER_DELAY;
1147 break;
1149 default:
1150 break;
1153 else /* file */
1155 switch (global_settings.talk_file) {
1156 case 1: /* files as numbers */
1157 ft_play_filenumber(i-tc.dirsindir+1,
1158 dircache[i].attr & TREE_ATTR_MASK);
1159 break;
1161 case 2: /* files spelled */
1162 talk_spell(dircache[i].name, false);
1163 break;
1165 case 3: /* thumbnail clip */
1166 /* "schedule" a thumbnail, to have a little delay */
1167 if (dircache[i].attr & TREE_ATTR_THUMBNAIL)
1168 thumbnail_time = current_tick + HOVER_DELAY;
1169 else
1170 /* spell the number as fallback */
1171 talk_spell(dircache[i].name, false);
1172 break;
1174 default:
1175 break;
1181 if(need_update) {
1182 lcd_update();
1184 need_update = false;
1185 update_all = false;
1189 return true;
1192 static int plsize = 0;
1193 static bool add_dir(char* dirname, int len, int fd)
1195 bool abort = false;
1196 DIR* dir;
1198 /* check for user abort */
1199 #ifdef BUTTON_STOP
1200 if (button_get(false) == BUTTON_STOP)
1201 #else
1202 if (button_get(false) == BUTTON_OFF)
1203 #endif
1204 return true;
1206 dir = opendir(dirname);
1207 if(!dir)
1208 return true;
1210 while (true) {
1211 struct dirent *entry;
1213 entry = readdir(dir);
1214 if (!entry)
1215 break;
1216 if (entry->attribute & ATTR_DIRECTORY) {
1217 int dirlen = strlen(dirname);
1218 bool result;
1220 if (!strcmp(entry->d_name, ".") ||
1221 !strcmp(entry->d_name, ".."))
1222 continue;
1224 if (dirname[1])
1225 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
1226 else
1227 snprintf(dirname, len, "/%s", entry->d_name);
1229 result = add_dir(dirname, len, fd);
1230 dirname[dirlen] = '\0';
1231 if (result) {
1232 abort = true;
1233 break;
1236 else {
1237 int x = strlen(entry->d_name);
1238 if ((!strcasecmp(&entry->d_name[x-4], ".mp3")) ||
1239 (!strcasecmp(&entry->d_name[x-4], ".mp2")) ||
1240 (!strcasecmp(&entry->d_name[x-4], ".mpa")))
1242 char buf[8];
1243 write(fd, dirname, strlen(dirname));
1244 write(fd, "/", 1);
1245 write(fd, entry->d_name, x);
1246 write(fd, "\n", 1);
1248 plsize++;
1249 snprintf(buf, sizeof buf, "%d", plsize);
1250 #ifdef HAVE_LCD_BITMAP
1251 lcd_puts(0,4,buf);
1252 lcd_update();
1253 #else
1254 x = 10;
1255 if (plsize > 999)
1256 x=7;
1257 else {
1258 if (plsize > 99)
1259 x=8;
1260 else {
1261 if (plsize > 9)
1262 x=9;
1265 lcd_puts(x,0,buf);
1266 #endif
1270 closedir(dir);
1272 return abort;
1275 bool create_playlist(void)
1277 int fd;
1278 char filename[MAX_PATH];
1280 snprintf(filename, sizeof filename, "%s.m3u",
1281 tc.currdir[1] ? tc.currdir : "/root");
1283 lcd_clear_display();
1284 lcd_puts(0,0,str(LANG_CREATING));
1285 lcd_puts_scroll(0,1,filename);
1286 lcd_update();
1288 fd = creat(filename,0);
1289 if (fd < 0)
1290 return false;
1292 snprintf(filename, sizeof(filename), "%s",
1293 tc.currdir[1] ? tc.currdir : "/");
1294 plsize = 0;
1295 add_dir(filename, sizeof(filename), fd);
1296 close(fd);
1297 sleep(HZ);
1299 return true;
1302 bool rockbox_browse(const char *root, int dirfilter)
1304 bool rc;
1305 int dircursor_save = tc.dircursor;
1306 int dirstart_save = tc.dirstart;
1307 int dirlevel_save = tc.dirlevel;
1308 int dirpos_save = tc.dirpos[0];
1309 int cursorpos_save = tc.cursorpos[0];
1310 int* dirfilter_save = tc.dirfilter;
1311 static char currdir_save[MAX_PATH];
1313 memcpy(currdir_save, tc.currdir, sizeof(tc.currdir));
1314 reload_dir = true;
1315 memcpy(tc.currdir, root, sizeof(tc.currdir));
1316 start_wps = false;
1317 tc.dirfilter = &dirfilter;
1319 rc = dirbrowse();
1321 memcpy(tc.currdir, currdir_save, sizeof(tc.currdir));
1322 reload_dir = true;
1323 tc.dirstart = dirstart_save;
1324 tc.cursorpos[0] = cursorpos_save;
1325 tc.dirlevel = dirlevel_save;
1326 tc.dircursor = dircursor_save;
1327 tc.dirpos[0] = dirpos_save;
1328 tc.dirfilter = dirfilter_save;
1330 return false;
1333 void tree_init(void)
1335 /* We copy the settings value in case it is changed by the user. We can't
1336 use it until the next reboot. */
1337 int max_files = global_settings.max_files_in_dir;
1339 /* initialize tree context struct */
1340 memset(&tc, 0, sizeof(tc));
1341 tc.dirfilter = &global_settings.dirfilter;
1343 db_init();
1345 tc.name_buffer_size = AVERAGE_FILENAME_LENGTH * max_files;
1346 tc.name_buffer = buffer_alloc(tc.name_buffer_size);
1348 tc.dircache_size = max_files * sizeof(struct entry);
1349 tc.dircache = buffer_alloc(tc.dircache_size);
1352 void bookmark_play(char *resume_file, int index, int offset, int seed,
1353 char *filename)
1355 int i;
1356 int len=strlen(resume_file);
1358 if (!strcasecmp(&resume_file[len-4], ".m3u"))
1360 /* Playlist playback */
1361 char* slash;
1362 // check that the file exists
1363 int fd = open(resume_file, O_RDONLY);
1364 if(fd<0)
1365 return;
1366 close(fd);
1368 slash = strrchr(resume_file,'/');
1369 if (slash)
1371 char* cp;
1372 *slash=0;
1374 cp=resume_file;
1375 if (!cp[0])
1376 cp="/";
1378 if (playlist_create(cp, slash+1) != -1)
1380 if (global_settings.playlist_shuffle)
1381 playlist_shuffle(seed, -1);
1382 playlist_start(index,offset);
1384 *slash='/';
1387 else
1389 /* Directory playback */
1390 lastdir[0]='\0';
1391 if (playlist_create(resume_file, NULL) != -1)
1393 resume_directory(resume_file);
1394 if (global_settings.playlist_shuffle)
1395 playlist_shuffle(seed, -1);
1397 /* Check if the file is at the same spot in the directory,
1398 else search for it */
1399 if ((strcmp(strrchr(playlist_peek(index) + 1,'/') + 1,
1400 filename)))
1402 for ( i=0; i < playlist_amount(); i++ )
1404 if ((strcmp(strrchr(playlist_peek(i) + 1,'/') + 1,
1405 filename)) == 0)
1406 break;
1408 if (i < playlist_amount())
1409 index = i;
1410 else
1411 return;
1413 playlist_start(index,offset);
1417 start_wps=true;
1420 int ft_play_filenumber(int pos, int attr)
1422 /* try to find a voice ID for the extension, if known */
1423 unsigned int j;
1424 int ext_id = -1; /* default to none */
1425 for (j=0; j<sizeof(filetypes)/sizeof(*filetypes); j++)
1427 if (attr == filetypes[j].tree_attr)
1429 ext_id = filetypes[j].voiceclip;
1430 break;
1434 talk_id(VOICE_FILE, false);
1435 talk_number(pos, true);
1436 talk_id(ext_id, true);
1437 return 1;
1440 int ft_play_dirname(int start_index)
1442 int fd;
1443 char dirname_mp3_filename[MAX_PATH+1];
1444 struct entry *dircache = tc.dircache;
1446 if (mpeg_status() & MPEG_STATUS_PLAY)
1447 return 0;
1449 snprintf(dirname_mp3_filename, sizeof(dirname_mp3_filename), "%s/%s/%s",
1450 tc.currdir, dircache[start_index].name, dir_thumbnail_name);
1452 DEBUGF("Checking for %s\n", dirname_mp3_filename);
1454 fd = open(dirname_mp3_filename, O_RDONLY);
1455 if (fd < 0)
1457 DEBUGF("Failed to find: %s\n", dirname_mp3_filename);
1458 return -1;
1461 close(fd);
1463 DEBUGF("Found: %s\n", dirname_mp3_filename);
1465 talk_file(dirname_mp3_filename, false);
1466 return 1;
1469 void ft_play_filename(char *dir, char *file)
1471 char name_mp3_filename[MAX_PATH+1];
1473 if (mpeg_status() & MPEG_STATUS_PLAY)
1474 return;
1476 if (strcasecmp(&file[strlen(file) - strlen(file_thumbnail_ext)],
1477 file_thumbnail_ext))
1478 { /* file has no .talk extension */
1479 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1480 "%s/%s%s", dir, file, file_thumbnail_ext);
1482 talk_file(name_mp3_filename, false);
1484 else
1485 { /* it already is a .talk file, play this directly */
1486 snprintf(name_mp3_filename, sizeof(name_mp3_filename),
1487 "%s/%s", dir, file);
1488 talk_id(LANG_VOICE_DIR_HOVER, false); /* prefix it */
1489 talk_file(name_mp3_filename, true);