Another wps rework:
[kugel-rb/myfork.git] / apps / gui / gwps.c
blob9057d9ead903e9be7ab39ebbace00df30bc216b1
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Jerome Kuptz
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include "config.h"
26 #include "system.h"
27 #include "file.h"
28 #include "lcd.h"
29 #include "font.h"
30 #include "backlight.h"
31 #include "action.h"
32 #include "kernel.h"
33 #include "filetypes.h"
34 #include "debug.h"
35 #include "sprintf.h"
36 #include "settings.h"
37 #include "gwps.h"
38 #include "gwps-common.h"
39 #include "audio.h"
40 #include "usb.h"
41 #include "status.h"
42 #include "storage.h"
43 #include "screens.h"
44 #include "playlist.h"
45 #ifdef HAVE_LCD_BITMAP
46 #include "icons.h"
47 #include "peakmeter.h"
48 #endif
49 #include "action.h"
50 #include "lang.h"
51 #include "bookmark.h"
52 #include "misc.h"
53 #include "sound.h"
54 #include "onplay.h"
55 #include "abrepeat.h"
56 #include "playback.h"
57 #include "splash.h"
58 #include "cuesheet.h"
59 #include "ata_idle_notify.h"
60 #include "root_menu.h"
61 #include "backdrop.h"
62 #include "quickscreen.h"
63 #include "pitchscreen.h"
64 #include "appevents.h"
65 #include "viewport.h"
66 #include "pcmbuf.h"
68 #define RESTORE_WPS_INSTANTLY 0l
69 #define RESTORE_WPS_NEXT_SECOND ((long)(HZ+current_tick))
71 static int wpsbars;
72 /* currently only one wps_state is needed */
73 struct wps_state wps_state;
74 struct gui_wps gui_wps[NB_SCREENS];
75 static struct wps_data wps_datas[NB_SCREENS];
77 /* initial setup of wps_data */
78 static void wps_state_init(void);
80 static void prev_track(unsigned skip_thresh)
82 if (!wps_state.id3 || (wps_state.id3->elapsed < skip_thresh*1000)) {
83 audio_prev();
85 else {
86 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
88 curr_cuesheet_skip(-1, wps_state.id3->elapsed);
89 return;
92 if (!wps_state.paused)
93 #if (CONFIG_CODEC == SWCODEC)
94 audio_pre_ff_rewind();
95 #else
96 audio_pause();
97 #endif
99 audio_ff_rewind(0);
101 #if (CONFIG_CODEC != SWCODEC)
102 if (!wps_state.paused)
103 audio_resume();
104 #endif
108 static void next_track(void)
110 /* take care of if we're playing a cuesheet */
111 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
113 if (curr_cuesheet_skip(1, wps_state.id3->elapsed))
115 /* if the result was false, then we really want
116 to skip to the next track */
117 return;
121 audio_next();
124 static void play_hop(int direction)
126 unsigned step = ((unsigned)global_settings.skip_length*1000);
127 unsigned long *elapsed = &(wps_state.id3->elapsed);
129 if (direction == 1 && wps_state.id3->length - *elapsed < step+1000) {
130 #if CONFIG_CODEC == SWCODEC
131 if(global_settings.beep)
132 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
133 #endif
134 return;
135 } else if ((direction == -1 && *elapsed < step)) {
136 *elapsed = 0;
137 } else {
138 *elapsed += step * direction;
140 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
141 #if (CONFIG_CODEC == SWCODEC)
142 audio_pre_ff_rewind();
143 #else
144 audio_pause();
145 #endif
147 audio_ff_rewind(*elapsed);
148 #if (CONFIG_CODEC != SWCODEC)
149 if (!wps_state.paused)
150 audio_resume();
151 #endif
154 static void gwps_fix_statusbars(void)
156 #ifdef HAVE_LCD_BITMAP
157 int i;
158 wpsbars = VP_SB_HIDE_ALL;
159 FOR_NB_SCREENS(i)
161 bool draw = false;
162 if (gui_wps[i].data->wps_sb_tag)
163 draw = gui_wps[i].data->show_sb_on_wps;
164 else if (global_settings.statusbar)
165 wpsbars |= VP_SB_ONSCREEN(i);
166 if (draw)
167 wpsbars |= (VP_SB_ONSCREEN(i) | VP_SB_IGNORE_SETTING(i));
169 #else
170 wpsbars = VP_SB_ALLSCREENS;
171 #endif
175 static void gwps_leave_wps(void)
177 int i, oldbars = VP_SB_HIDE_ALL;
179 FOR_NB_SCREENS(i)
180 gui_wps[i].display->stop_scroll();
181 if (global_settings.statusbar)
182 oldbars = VP_SB_ALLSCREENS;
184 #if LCD_DEPTH > 1
185 show_main_backdrop();
186 #endif
187 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
188 show_remote_main_backdrop();
189 #endif
190 viewportmanager_set_statusbar(oldbars);
193 void gwps_draw_statusbars(void)
195 viewportmanager_set_statusbar(wpsbars);
198 /* The WPS can be left in two ways:
199 * a) call a function, which draws over the wps. In this case, the wps
200 * will be still active (i.e. the below function didn't return)
201 * b) return with a value evaluated by root_menu.c, in this case the wps
202 * is really left, and root_menu will handle the next screen
204 * In either way, call gwps_leave_wps(), in order to restore the correct
205 * "main screen" backdrops and statusbars
207 long gui_wps_show(void)
209 long button = 0;
210 bool restore = false;
211 long restoretimer = RESTORE_WPS_INSTANTLY; /* timer to delay screen redraw temporarily */
212 bool exit = false;
213 bool bookmark = false;
214 bool update_track = false;
215 int i;
216 long last_left = 0, last_right = 0;
217 wps_state_init();
219 #ifdef HAVE_LCD_CHARCELLS
220 status_set_audio(true);
221 status_set_param(false);
222 #endif
224 gwps_fix_statusbars();
226 #ifdef AB_REPEAT_ENABLE
227 ab_repeat_init();
228 ab_reset_markers();
229 #endif
230 if(audio_status() & AUDIO_STATUS_PLAY)
232 wps_state.id3 = audio_current_track();
233 wps_state.nid3 = audio_next_track();
234 if (wps_state.id3) {
235 FOR_NB_SCREENS(i)
237 if (!gui_wps_display(&gui_wps[i]))
238 exit = true;
243 while ( 1 )
245 yield();
246 bool audio_paused = (audio_status() & AUDIO_STATUS_PAUSE)?true:false;
248 /* did someone else (i.e power thread) change audio pause mode? */
249 if (wps_state.paused != audio_paused) {
250 wps_state.paused = audio_paused;
252 /* if another thread paused audio, we are probably in car mode,
253 about to shut down. lets save the settings. */
254 if (wps_state.paused) {
255 settings_save();
256 #if !defined(HAVE_RTC_RAM) && !defined(HAVE_SW_POWEROFF)
257 call_storage_idle_notifys(true);
258 #endif
262 #ifdef HAVE_LCD_BITMAP
263 /* when the peak meter is enabled we want to have a
264 few extra updates to make it look smooth. On the
265 other hand we don't want to waste energy if it
266 isn't displayed */
267 bool pm=false;
268 FOR_NB_SCREENS(i)
270 if(gui_wps[i].data->peak_meter_enabled)
271 pm = true;
274 if (pm) {
275 long next_refresh = current_tick;
276 long next_big_refresh = current_tick + HZ / 5;
277 button = BUTTON_NONE;
278 while (TIME_BEFORE(current_tick, next_big_refresh)) {
279 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_NOBLOCK);
280 if (button != ACTION_NONE) {
281 break;
283 peak_meter_peek();
284 sleep(0); /* Sleep until end of current tick. */
286 if (TIME_AFTER(current_tick, next_refresh)) {
287 FOR_NB_SCREENS(i)
289 if(gui_wps[i].data->peak_meter_enabled)
290 gui_wps_redraw(&gui_wps[i], 0,
291 WPS_REFRESH_PEAK_METER);
292 next_refresh += HZ / PEAK_METER_FPS;
299 /* The peak meter is disabled
300 -> no additional screen updates needed */
301 else
302 #endif
304 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,HZ/5);
307 /* Exit if audio has stopped playing. This can happen if using the
308 sleep timer with the charger plugged or if starting a recording
309 from F1 */
310 if (!audio_status())
311 exit = true;
312 /* The iPods/X5/M5 use a single button for the A-B mode markers,
313 defined as ACTION_WPSAB_SINGLE in their config files. */
314 #ifdef ACTION_WPSAB_SINGLE
315 if (!global_settings.party_mode && ab_repeat_mode_enabled())
317 static int wps_ab_state = 0;
318 if (button == ACTION_WPSAB_SINGLE)
320 switch (wps_ab_state)
322 case 0: /* set the A spot */
323 button = ACTION_WPS_ABSETA_PREVDIR;
324 break;
325 case 1: /* set the B spot */
326 button = ACTION_WPS_ABSETB_NEXTDIR;
327 break;
328 case 2:
329 button = ACTION_WPS_ABRESET;
330 break;
332 wps_ab_state = (wps_ab_state+1) % 3;
335 #endif
336 switch(button)
338 case ACTION_WPS_CONTEXT:
340 gwps_leave_wps();
341 /* if music is stopped in the context menu we want to exit the wps */
342 if (onplay(wps_state.id3->path,
343 FILE_ATTR_AUDIO, CONTEXT_WPS) == ONPLAY_MAINMENU
344 || !audio_status())
345 return GO_TO_ROOT;
346 /* track might have changed */
347 update_track = true;
348 restore = true;
350 break;
352 case ACTION_WPS_BROWSE:
353 #ifdef HAVE_LCD_CHARCELLS
354 status_set_record(false);
355 status_set_audio(false);
356 #endif
357 gwps_leave_wps();
358 return GO_TO_PREVIOUS_BROWSER;
359 break;
361 /* play/pause */
362 case ACTION_WPS_PLAY:
363 if (global_settings.party_mode)
364 break;
365 if ( wps_state.paused )
367 wps_state.paused = false;
368 if ( global_settings.fade_on_stop )
369 fade(true, true);
370 else
371 audio_resume();
373 else
375 wps_state.paused = true;
376 if ( global_settings.fade_on_stop )
377 fade(false, true);
378 else
379 audio_pause();
380 settings_save();
381 #if !defined(HAVE_RTC_RAM) && !defined(HAVE_SW_POWEROFF)
382 call_storage_idle_notifys(true); /* make sure resume info is saved */
383 #endif
385 break;
387 case ACTION_WPS_VOLUP:
389 FOR_NB_SCREENS(i)
390 gui_wps[i].data->button_time_volume = current_tick;
391 global_settings.volume++;
392 bool res = false;
393 setvol();
394 FOR_NB_SCREENS(i)
396 if(update_onvol_change(&gui_wps[i]))
397 res = true;
399 if (res) {
400 restore = true;
401 restoretimer = RESTORE_WPS_NEXT_SECOND;
404 break;
405 case ACTION_WPS_VOLDOWN:
407 FOR_NB_SCREENS(i)
408 gui_wps[i].data->button_time_volume = current_tick;
409 global_settings.volume--;
410 setvol();
411 bool res = false;
412 FOR_NB_SCREENS(i)
414 if(update_onvol_change(&gui_wps[i]))
415 res = true;
417 if (res) {
418 restore = true;
419 restoretimer = RESTORE_WPS_NEXT_SECOND;
422 break;
423 /* fast forward
424 OR next dir if this is straight after ACTION_WPS_SKIPNEXT
425 OR if skip length set, next track if straight after SKIPPREV. */
426 case ACTION_WPS_SEEKFWD:
427 if (global_settings.party_mode)
428 break;
429 if (global_settings.skip_length == 0
430 && current_tick -last_right < HZ)
432 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
434 audio_next();
436 else
438 audio_next_dir();
441 else if (global_settings.skip_length > 0
442 && current_tick -last_left < HZ) {
443 next_track();
444 update_track = true;
446 else ffwd_rew(ACTION_WPS_SEEKFWD);
447 last_right = last_left = 0;
448 break;
449 /* fast rewind
450 OR prev dir if this is straight after ACTION_WPS_SKIPPREV,
451 OR if skip length set, beg of track or prev track if this is
452 straight after SKIPPREV */
453 case ACTION_WPS_SEEKBACK:
454 if (global_settings.party_mode)
455 break;
456 if (global_settings.skip_length == 0
457 && current_tick -last_left < HZ)
459 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
461 if (!wps_state.paused)
462 #if (CONFIG_CODEC == SWCODEC)
463 audio_pre_ff_rewind();
464 #else
465 audio_pause();
466 #endif
467 audio_ff_rewind(0);
469 else
471 audio_prev_dir();
474 else if (global_settings.skip_length > 0
475 && current_tick -last_right < HZ)
477 prev_track(3+global_settings.skip_length);
478 update_track = true;
480 else ffwd_rew(ACTION_WPS_SEEKBACK);
481 last_left = last_right = 0;
482 break;
484 /* prev / restart */
485 case ACTION_WPS_SKIPPREV:
486 if (global_settings.party_mode)
487 break;
488 last_left = current_tick;
489 update_track = true;
491 #ifdef AB_REPEAT_ENABLE
492 /* if we're in A/B repeat mode and the current position
493 is past the A marker, jump back to the A marker... */
494 if ( ab_repeat_mode_enabled() )
496 if ( ab_after_A_marker(wps_state.id3->elapsed) )
498 ab_jump_to_A_marker();
499 break;
500 #if (AB_REPEAT_ENABLE == 2)
501 } else {
502 ab_reset_markers();
503 #endif
506 /* ...otherwise, do it normally */
507 #endif
509 if (global_settings.skip_length > 0)
510 play_hop(-1);
511 else prev_track(3);
512 break;
514 /* next
515 OR if skip length set, hop by predetermined amount. */
516 case ACTION_WPS_SKIPNEXT:
517 if (global_settings.party_mode)
518 break;
519 last_right = current_tick;
520 update_track = true;
522 #ifdef AB_REPEAT_ENABLE
523 /* if we're in A/B repeat mode and the current position is
524 before the A marker, jump to the A marker... */
525 if ( ab_repeat_mode_enabled() )
527 if ( ab_before_A_marker(wps_state.id3->elapsed) )
529 ab_jump_to_A_marker();
530 break;
531 #if (AB_REPEAT_ENABLE == 2)
532 } else {
533 ab_reset_markers();
534 #endif
537 /* ...otherwise, do it normally */
538 #endif
540 if (global_settings.skip_length > 0)
541 play_hop(1);
542 else next_track();
543 break;
544 /* next / prev directories */
545 /* and set A-B markers if in a-b mode */
546 case ACTION_WPS_ABSETB_NEXTDIR:
547 if (global_settings.party_mode)
548 break;
549 #if defined(AB_REPEAT_ENABLE)
550 if (ab_repeat_mode_enabled())
552 ab_set_B_marker(wps_state.id3->elapsed);
553 ab_jump_to_A_marker();
554 update_track = true;
556 else
557 #endif
559 if (global_settings.skip_length > 0)
560 next_track();
561 else audio_next_dir();
563 break;
564 case ACTION_WPS_ABSETA_PREVDIR:
565 if (global_settings.party_mode)
566 break;
567 #if defined(AB_REPEAT_ENABLE)
568 if (ab_repeat_mode_enabled())
569 ab_set_A_marker(wps_state.id3->elapsed);
570 else
571 #endif
573 if (global_settings.skip_length > 0)
574 prev_track(3);
575 else audio_prev_dir();
577 break;
578 /* menu key functions */
579 case ACTION_WPS_MENU:
580 gwps_leave_wps();
581 return GO_TO_ROOT;
582 break;
585 #ifdef HAVE_QUICKSCREEN
586 case ACTION_WPS_QUICKSCREEN:
588 gwps_leave_wps();
589 if (quick_screen_quick(button))
590 return SYS_USB_CONNECTED;
591 restore = true;
593 break;
594 #endif /* HAVE_QUICKSCREEN */
596 /* screen settings */
597 #ifdef BUTTON_F3
598 case ACTION_F3:
600 gwps_leave_wps();
601 if (quick_screen_f3(BUTTON_F3))
602 return SYS_USB_CONNECTED;
603 restore = true;
605 break;
606 #endif /* BUTTON_F3 */
608 /* pitch screen */
609 #ifdef HAVE_PITCHSCREEN
610 case ACTION_WPS_PITCHSCREEN:
612 gwps_leave_wps();
613 if (1 == gui_syncpitchscreen_run())
614 return SYS_USB_CONNECTED;
615 restore = true;
617 break;
618 #endif /* HAVE_PITCHSCREEN */
620 #ifdef AB_REPEAT_ENABLE
621 /* reset A&B markers */
622 case ACTION_WPS_ABRESET:
623 if (ab_repeat_mode_enabled())
625 ab_reset_markers();
626 update_track = true;
628 break;
629 #endif /* AB_REPEAT_ENABLE */
631 /* stop and exit wps */
632 case ACTION_WPS_STOP:
633 if (global_settings.party_mode)
634 break;
635 bookmark = true;
636 exit = true;
637 break;
639 case ACTION_WPS_ID3SCREEN:
641 gwps_leave_wps();
642 browse_id3();
643 restore = true;
645 break;
647 case ACTION_REDRAW: /* yes are locked, just redraw */
648 restore = true;
649 break;
650 case ACTION_NONE: /* Timeout */
651 update_track = true;
652 ffwd_rew(button); /* hopefully fix the ffw/rwd bug */
653 break;
654 #ifdef HAVE_RECORDING
655 case ACTION_WPS_REC:
656 exit = true;
657 break;
658 #endif
659 case SYS_POWEROFF:
660 gwps_leave_wps();
661 default_event_handler(SYS_POWEROFF);
662 break;
664 default:
665 if(default_event_handler(button) == SYS_USB_CONNECTED)
666 return GO_TO_ROOT;
667 update_track = true;
668 break;
671 if (update_track)
673 FOR_NB_SCREENS(i)
675 if(!gui_wps_update(&gui_wps[i]))
676 exit = true;
678 update_track = false;
681 if (restore &&
682 ((restoretimer == RESTORE_WPS_INSTANTLY) ||
683 TIME_AFTER(current_tick, restoretimer)))
685 /* restore wps backrops and statusbars */
686 #if LCD_DEPTH > 1
687 show_wps_backdrop();
688 #endif
689 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
690 show_remote_wps_backdrop();
691 #endif
692 restore = false;
693 restoretimer = RESTORE_WPS_INSTANTLY;
694 FOR_NB_SCREENS(i)
696 screens[i].stop_scroll();
697 if (!gui_wps_redraw(&gui_wps[i], 0, WPS_REFRESH_ALL))
698 exit = true;
702 if (exit) {
703 #ifdef HAVE_LCD_CHARCELLS
704 status_set_record(false);
705 status_set_audio(false);
706 #endif
707 if (global_settings.fade_on_stop)
708 fade(false, true);
710 if (bookmark)
711 bookmark_autobookmark();
712 audio_stop();
713 #ifdef AB_REPEAT_ENABLE
714 ab_reset_markers();
715 #endif
716 gwps_leave_wps();
717 #ifdef HAVE_RECORDING
718 if (button == ACTION_WPS_REC)
719 return GO_TO_RECSCREEN;
720 #endif
721 if (global_settings.browse_current)
722 return GO_TO_PREVIOUS_BROWSER;
723 return GO_TO_PREVIOUS;
726 if (button && !IS_SYSEVENT(button) )
727 storage_spin();
729 return GO_TO_ROOT; /* unreachable - just to reduce compiler warnings */
732 /* needs checking if needed end*/
734 /* wps_state */
736 static void wps_state_init(void)
738 wps_state.ff_rewind = false;
739 wps_state.paused = false;
740 wps_state.id3 = NULL;
741 wps_state.nid3 = NULL;
744 /* wps_state end*/
746 #ifdef HAVE_LCD_BITMAP
747 static void statusbar_toggle_handler(void *data)
749 (void)data;
750 int i;
751 gwps_fix_statusbars();
753 FOR_NB_SCREENS(i)
755 struct viewport *vp = &gui_wps[i].data->viewports[0].vp;
756 bool draw = wpsbars & (VP_SB_ONSCREEN(i) | VP_SB_IGNORE_SETTING(i));
757 if (!draw)
759 vp->y = 0;
760 vp->height = screens[i].lcdheight;
762 else
764 vp->y = STATUSBAR_HEIGHT;
765 vp->height = screens[i].lcdheight - STATUSBAR_HEIGHT;
769 #endif
771 void gui_sync_wps_init(void)
773 int i;
774 FOR_NB_SCREENS(i)
776 wps_data_init(&wps_datas[i]);
777 #ifdef HAVE_ALBUMART
778 wps_datas[i].wps_uses_albumart = 0;
779 #endif
780 #ifdef HAVE_REMOTE_LCD
781 wps_datas[i].remote_wps = (i != 0);
782 #endif
783 gui_wps[i].data = &wps_datas[i];
784 gui_wps[i].display = &screens[i];
785 /* Currently no seperate wps_state needed/possible
786 so use the only aviable ( "global" ) one */
787 gui_wps[i].state = &wps_state;
789 #ifdef HAVE_LCD_BITMAP
790 add_event(GUI_EVENT_STATUSBAR_TOGGLE, false, statusbar_toggle_handler);
791 #endif
792 #if LCD_DEPTH > 1
793 unload_wps_backdrop();
794 #endif
795 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
796 unload_remote_wps_backdrop();
797 #endif
800 #ifdef HAVE_ALBUMART
801 /* Returns true if at least one of the gui_wps screens has an album art
802 tag in its wps structure */
803 bool gui_sync_wps_uses_albumart(void)
805 int i;
806 FOR_NB_SCREENS(i) {
807 struct gui_wps *gwps = &gui_wps[i];
808 if (gwps->data && (gwps->data->wps_uses_albumart != WPS_ALBUMART_NONE))
809 return true;
811 return false;
813 #endif