implement smooth seeking acceleration for audio playback and mpegplayer
[Rockbox.git] / apps / gui / gwps-common.c
blob3fd77a2211729ea682fe602e81628502f695f5cc
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002-2007 Björn Stenberg
11 * Copyright (C) 2007-2008 Nicolas Pennequin
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
19 ****************************************************************************/
20 #include "gwps-common.h"
21 #include "font.h"
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include "system.h"
26 #include "settings.h"
27 #include "rbunicode.h"
28 #include "rtc.h"
29 #include "audio.h"
30 #include "status.h"
31 #include "power.h"
32 #include "powermgmt.h"
33 #include "sound.h"
34 #include "debug.h"
35 #ifdef HAVE_LCD_CHARCELLS
36 #include "hwcompat.h"
37 #endif
38 #include "abrepeat.h"
39 #include "mp3_playback.h"
40 #include "backlight.h"
41 #include "lang.h"
42 #include "misc.h"
43 #include "splash.h"
44 #include "scrollbar.h"
45 #include "led.h"
46 #include "lcd.h"
47 #ifdef HAVE_LCD_BITMAP
48 #include "peakmeter.h"
49 /* Image stuff */
50 #include "bmp.h"
51 #include "albumart.h"
52 #endif
53 #include "dsp.h"
54 #include "action.h"
55 #include "cuesheet.h"
56 #include "playlist.h"
57 #if CONFIG_CODEC == SWCODEC
58 #include "playback.h"
59 #endif
60 #include "backdrop.h"
62 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
63 /* 3% of 30min file == 54s step size */
64 #define MIN_FF_REWIND_STEP 500
66 /* draws the statusbar on the given wps-screen */
67 #ifdef HAVE_LCD_BITMAP
68 static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
70 bool draw = global_settings.statusbar;
72 if (wps->data->wps_sb_tag)
73 draw = wps->data->show_sb_on_wps;
75 if (draw)
76 gui_statusbar_draw(wps->statusbar, force);
78 #else
79 #define gui_wps_statusbar_draw(wps, force) \
80 gui_statusbar_draw((wps)->statusbar, (force))
81 #endif
82 #include "pcmbuf.h"
84 /* fades the volume */
85 bool wps_fading_out = false;
86 void fade(bool fade_in, bool updatewps)
88 int fp_global_vol = global_settings.volume << 8;
89 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
90 int fp_step = (fp_global_vol - fp_min_vol) / 30;
91 int i;
92 wps_fading_out = !fade_in;
93 if (fade_in) {
94 /* fade in */
95 int fp_volume = fp_min_vol;
97 /* zero out the sound */
98 sound_set_volume(fp_min_vol >> 8);
100 sleep(HZ/10); /* let audio thread run */
101 audio_resume();
103 while (fp_volume < fp_global_vol - fp_step) {
104 fp_volume += fp_step;
105 sound_set_volume(fp_volume >> 8);
106 if (updatewps)
108 FOR_NB_SCREENS(i)
109 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
111 sleep(1);
113 sound_set_volume(global_settings.volume);
115 else {
116 /* fade out */
117 int fp_volume = fp_global_vol;
119 while (fp_volume > fp_min_vol + fp_step) {
120 fp_volume -= fp_step;
121 sound_set_volume(fp_volume >> 8);
122 if (updatewps)
124 FOR_NB_SCREENS(i)
125 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
127 sleep(1);
129 audio_pause();
130 wps_fading_out = false;
131 #if CONFIG_CODEC != SWCODEC
132 #ifndef SIMULATOR
133 /* let audio thread run and wait for the mas to run out of data */
134 while (!mp3_pause_done())
135 #endif
136 sleep(HZ/10);
137 #endif
139 /* reset volume to what it was before the fade */
140 sound_set_volume(global_settings.volume);
144 /* return true if screen restore is needed
145 return false otherwise
147 bool update_onvol_change(struct gui_wps * gwps)
149 gui_wps_statusbar_draw(gwps, false);
150 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
152 #ifdef HAVE_LCD_CHARCELLS
153 gui_splash(gwps->display, 0, "Vol: %3d dB",
154 sound_val2phys(SOUND_VOLUME, global_settings.volume));
155 return true;
156 #endif
157 return false;
160 void play_hop(int direction)
162 if(!wps_state.id3 || !wps_state.id3->length
163 || global_settings.study_hop_step == 0)
164 return;
165 #define STEP ((unsigned)global_settings.study_hop_step *1000)
166 if(direction == 1
167 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
168 #if CONFIG_CODEC == SWCODEC
169 if(global_settings.beep)
170 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
171 #endif
172 return;
174 if((direction == -1 && wps_state.id3->elapsed < STEP))
175 wps_state.id3->elapsed = 0;
176 else
177 wps_state.id3->elapsed += STEP *direction;
178 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
179 #if (CONFIG_CODEC == SWCODEC)
180 audio_pre_ff_rewind();
181 #else
182 audio_pause();
183 #endif
185 audio_ff_rewind(wps_state.id3->elapsed);
186 #if (CONFIG_CODEC != SWCODEC)
187 if (!wps_state.paused)
188 audio_resume();
189 #endif
190 #undef STEP
193 bool ffwd_rew(int button)
195 unsigned int step = 0; /* current ff/rewind step */
196 unsigned int max_step = 0; /* maximum ff/rewind step */
197 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
198 int direction = -1; /* forward=1 or backward=-1 */
199 bool exit = false;
200 bool usb = false;
201 int i = 0;
202 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
204 if (button == ACTION_NONE)
206 status_set_ffmode(0);
207 return usb;
209 while (!exit)
211 switch ( button )
213 case ACTION_WPS_SEEKFWD:
214 direction = 1;
215 case ACTION_WPS_SEEKBACK:
216 if (wps_state.ff_rewind)
218 if (direction == 1)
220 /* fast forwarding, calc max step relative to end */
221 max_step = (wps_state.id3->length -
222 (wps_state.id3->elapsed +
223 ff_rewind_count)) *
224 FF_REWIND_MAX_PERCENT / 100;
226 else
228 /* rewinding, calc max step relative to start */
229 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
230 FF_REWIND_MAX_PERCENT / 100;
233 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
235 if (step > max_step)
236 step = max_step;
238 ff_rewind_count += step * direction;
240 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
241 step += step >> ff_rw_accel;
243 else
245 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
246 wps_state.id3 && wps_state.id3->length )
248 if (!wps_state.paused)
249 #if (CONFIG_CODEC == SWCODEC)
250 audio_pre_ff_rewind();
251 #else
252 audio_pause();
253 #endif
254 #if CONFIG_KEYPAD == PLAYER_PAD
255 FOR_NB_SCREENS(i)
256 gui_wps[i].display->stop_scroll();
257 #endif
258 if (direction > 0)
259 status_set_ffmode(STATUS_FASTFORWARD);
260 else
261 status_set_ffmode(STATUS_FASTBACKWARD);
263 wps_state.ff_rewind = true;
265 step = 1000 * global_settings.ff_rewind_min_step;
267 else
268 break;
271 if (direction > 0) {
272 if ((wps_state.id3->elapsed + ff_rewind_count) >
273 wps_state.id3->length)
274 ff_rewind_count = wps_state.id3->length -
275 wps_state.id3->elapsed;
277 else {
278 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
279 ff_rewind_count = -wps_state.id3->elapsed;
282 FOR_NB_SCREENS(i)
283 gui_wps_refresh(&gui_wps[i],
284 (wps_state.wps_time_countup == false)?
285 ff_rewind_count:-ff_rewind_count,
286 WPS_REFRESH_PLAYER_PROGRESS |
287 WPS_REFRESH_DYNAMIC);
289 break;
291 case ACTION_WPS_STOPSEEK:
292 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
293 audio_ff_rewind(wps_state.id3->elapsed);
294 ff_rewind_count = 0;
295 wps_state.ff_rewind = false;
296 status_set_ffmode(0);
297 #if (CONFIG_CODEC != SWCODEC)
298 if (!wps_state.paused)
299 audio_resume();
300 #endif
301 #ifdef HAVE_LCD_CHARCELLS
302 gui_wps_display();
303 #endif
304 exit = true;
305 break;
307 default:
308 if(default_event_handler(button) == SYS_USB_CONNECTED) {
309 status_set_ffmode(0);
310 usb = true;
311 exit = true;
313 break;
315 if (!exit)
316 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
318 return usb;
321 bool gui_wps_display(void)
323 int i;
324 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
326 global_status.resume_index = -1;
327 #ifdef HAVE_LCD_BITMAP
328 gui_syncstatusbar_draw(&statusbars, true);
329 #endif
330 gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST));
331 return true;
333 else
335 FOR_NB_SCREENS(i)
337 /* Update the values in the first (default) viewport - in case the user
338 has modified the statusbar or colour settings */
339 #ifdef HAVE_LCD_BITMAP
340 #if LCD_DEPTH > 1
341 if (gui_wps[i].display->depth > 1)
343 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
344 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
346 #endif
347 #endif
349 gui_wps[i].display->clear_display();
350 if (!gui_wps[i].data->wps_loaded) {
351 if ( !gui_wps[i].data->num_tokens ) {
352 /* set the default wps for the main-screen */
353 if(i == 0)
355 #ifdef HAVE_LCD_BITMAP
356 #if LCD_DEPTH > 1
357 unload_wps_backdrop();
358 #endif
359 wps_data_load(gui_wps[i].data,
360 gui_wps[i].display,
361 "%s%?it<%?in<%in. |>%it|%fn>\n"
362 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
363 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
364 "\n"
365 "%al%pc/%pt%ar[%pp:%pe]\n"
366 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
367 "%pb\n"
368 "%pm\n", false);
369 #else
370 wps_data_load(gui_wps[i].data,
371 gui_wps[i].display,
372 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
373 "%pc%?ps<*|/>%pt\n", false);
374 #endif
376 #if NB_SCREENS == 2
377 /* set the default wps for the remote-screen */
378 else if(i == 1)
380 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
381 unload_remote_wps_backdrop();
382 #endif
383 wps_data_load(gui_wps[i].data,
384 gui_wps[i].display,
385 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
386 "%s%?it<%?in<%in. |>%it|%fn>\n"
387 "%al%pc/%pt%ar[%pp:%pe]\n"
388 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
389 "%pb\n", false);
391 #endif
396 yield();
397 FOR_NB_SCREENS(i)
399 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
401 return false;
404 bool update(struct gui_wps *gwps)
406 bool track_changed = audio_has_changed_track();
407 bool retcode = false;
409 gwps->state->nid3 = audio_next_track();
410 if (track_changed)
412 gwps->display->stop_scroll();
413 gwps->state->id3 = audio_current_track();
415 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
416 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
418 /* the current cuesheet isn't the right one any more */
420 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
421 /* We have the new cuesheet in memory (temp_cue),
422 let's make it the current one ! */
423 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
425 else {
426 /* We need to parse the new cuesheet */
428 char cuepath[MAX_PATH];
430 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
431 parse_cuesheet(cuepath, curr_cue))
433 gwps->state->id3->cuesheet_type = 1;
434 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
438 cue_spoof_id3(curr_cue, gwps->state->id3);
441 if (gui_wps_display())
442 retcode = true;
443 else{
444 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
447 if (gwps->state->id3)
449 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
450 sizeof(gwps->state->current_track_path));
451 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
455 if (gwps->state->id3)
457 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
458 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
459 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
460 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
462 /* We've changed tracks within the cuesheet :
463 we need to update the ID3 info and refresh the WPS */
465 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
466 cue_spoof_id3(curr_cue, gwps->state->id3);
468 gwps->display->stop_scroll();
469 if (gui_wps_display())
470 retcode = true;
471 else
472 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
474 else
475 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
478 gui_wps_statusbar_draw(gwps, false);
480 return retcode;
484 void display_keylock_text(bool locked)
486 char* s;
487 int i;
488 FOR_NB_SCREENS(i)
489 gui_wps[i].display->stop_scroll();
491 if(locked)
492 s = str(LANG_KEYLOCK_ON);
493 else
494 s = str(LANG_KEYLOCK_OFF);
495 gui_syncsplash(HZ, s);
498 #ifdef HAVE_LCD_BITMAP
500 static void draw_progressbar(struct gui_wps *gwps,
501 struct progressbar *pb)
503 struct screen *display = gwps->display;
504 struct wps_state *state = gwps->state;
505 if (pb->have_bitmap_pb)
506 gui_bitmap_scrollbar_draw(display, pb->bm,
507 pb->x, pb->y, pb->width, pb->bm.height,
508 state->id3->length ? state->id3->length : 1, 0,
509 state->id3->length ? state->id3->elapsed
510 + state->ff_rewind_count : 0,
511 HORIZONTAL);
512 else
513 gui_scrollbar_draw(display, pb->x, pb->y, pb->width, pb->height,
514 state->id3->length ? state->id3->length : 1, 0,
515 state->id3->length ? state->id3->elapsed
516 + state->ff_rewind_count : 0,
517 HORIZONTAL);
518 #ifdef AB_REPEAT_ENABLE
519 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
520 ab_draw_markers(display, state->id3->length,
521 pb->x, pb->x + pb->width, pb->y, pb->height);
522 #endif
524 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
525 cue_draw_markers(display, state->id3->length,
526 pb->x, pb->x + pb->width, pb->y+1, pb->height-2);
529 /* clears the area where the image was shown */
530 static void clear_image_pos(struct gui_wps *gwps, int n)
532 if(!gwps)
533 return;
534 struct wps_data *data = gwps->data;
535 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
536 gwps->display->fillrect(data->img[n].x, data->img[n].y,
537 data->img[n].bm.width, data->img[n].subimage_height);
538 gwps->display->set_drawmode(DRMODE_SOLID);
541 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
543 struct screen *display = gwps->display;
544 struct wps_data *data = gwps->data;
545 if(data->img[n].always_display)
546 display->set_drawmode(DRMODE_FG);
547 else
548 display->set_drawmode(DRMODE_SOLID);
550 #if LCD_DEPTH > 1
551 if(data->img[n].bm.format == FORMAT_MONO) {
552 #endif
553 display->mono_bitmap_part(data->img[n].bm.data,
554 0, data->img[n].subimage_height * subimage,
555 data->img[n].bm.width, data->img[n].x,
556 data->img[n].y, data->img[n].bm.width,
557 data->img[n].subimage_height);
558 #if LCD_DEPTH > 1
559 } else {
560 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
561 0, data->img[n].subimage_height * subimage,
562 data->img[n].bm.width, data->img[n].x,
563 data->img[n].y, data->img[n].bm.width,
564 data->img[n].subimage_height);
566 #endif
569 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
571 if(!gwps || !gwps->data || !gwps->display)
572 return;
574 int n;
575 struct wps_data *data = gwps->data;
576 struct screen *display = gwps->display;
578 for (n = 0; n < MAX_IMAGES; n++)
580 if (data->img[n].loaded)
582 if (data->img[n].display >= 0)
584 wps_draw_image(gwps, n, data->img[n].display);
585 } else if (data->img[n].always_display && data->img[n].vp == vp)
587 wps_draw_image(gwps, n, 0);
591 display->set_drawmode(DRMODE_SOLID);
594 #else /* HAVE_LCD_CHARCELL */
596 static bool draw_player_progress(struct gui_wps *gwps)
598 struct wps_state *state = gwps->state;
599 struct screen *display = gwps->display;
600 unsigned char progress_pattern[7];
601 int pos = 0;
602 int i;
604 if (!state->id3)
605 return false;
607 if (state->id3->length)
608 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
609 / state->id3->length;
611 for (i = 0; i < 7; i++, pos -= 5)
613 if (pos <= 0)
614 progress_pattern[i] = 0x1f;
615 else if (pos >= 5)
616 progress_pattern[i] = 0x00;
617 else
618 progress_pattern[i] = 0x1f >> pos;
621 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
622 return true;
625 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
627 static const unsigned char numbers[10][4] = {
628 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
629 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
630 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
631 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
632 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
633 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
634 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
635 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
636 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
637 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
640 struct wps_state *state = gwps->state;
641 struct screen *display = gwps->display;
642 struct wps_data *data = gwps->data;
643 unsigned char progress_pattern[7];
644 char timestr[10];
645 int time;
646 int time_idx = 0;
647 int pos = 0;
648 int pat_idx = 1;
649 int digit, i, j;
650 bool softchar;
652 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
653 return;
655 time = state->id3->elapsed + state->ff_rewind_count;
656 if (state->id3->length)
657 pos = 55 * time / state->id3->length;
659 memset(timestr, 0, sizeof(timestr));
660 format_time(timestr, sizeof(timestr)-2, time);
661 timestr[strlen(timestr)] = ':'; /* always safe */
663 for (i = 0; i < 11; i++, pos -= 5)
665 softchar = false;
666 memset(progress_pattern, 0, sizeof(progress_pattern));
668 if ((digit = timestr[time_idx]))
670 softchar = true;
671 digit -= '0';
673 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
675 memcpy(progress_pattern, numbers[digit], 4);
676 time_idx += 2;
678 else /* tens, shifted right */
680 for (j = 0; j < 4; j++)
681 progress_pattern[j] = numbers[digit][j] >> 1;
683 if (time_idx > 0) /* not the first group, add colon in front */
685 progress_pattern[1] |= 0x10;
686 progress_pattern[3] |= 0x10;
688 time_idx++;
691 if (pos >= 5)
692 progress_pattern[5] = progress_pattern[6] = 0x1f;
695 if (pos > 0 && pos < 5)
697 softchar = true;
698 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
701 if (softchar && pat_idx < 8)
703 display->define_pattern(data->wps_progress_pat[pat_idx],
704 progress_pattern);
705 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
706 pat_idx++;
708 else if (pos <= 0)
709 buf = utf8encode(' ', buf);
710 else
711 buf = utf8encode(0xe115, buf); /* 2/7 _ */
713 *buf = '\0';
716 #endif /* HAVE_LCD_CHARCELL */
718 static char* get_codectype(const struct mp3entry* id3)
720 if (id3->codectype < AFMT_NUM_CODECS) {
721 return (char*)audio_formats[id3->codectype].label;
722 } else {
723 return NULL;
727 /* Extract a part from a path.
729 * buf - buffer extract part to.
730 * buf_size - size of buffer.
731 * path - path to extract from.
732 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
733 * parent of parent, etc.
735 * Returns buf if the desired level was found, NULL otherwise.
737 static char* get_dir(char* buf, int buf_size, const char* path, int level)
739 const char* sep;
740 const char* last_sep;
741 int len;
743 sep = path + strlen(path);
744 last_sep = sep;
746 while (sep > path)
748 if ('/' == *(--sep))
750 if (!level)
751 break;
753 level--;
754 last_sep = sep - 1;
758 if (level || (last_sep <= sep))
759 return NULL;
761 len = MIN(last_sep - sep, buf_size - 1);
762 strncpy(buf, sep + 1, len);
763 buf[len] = 0;
764 return buf;
767 /* Return the tag found at index i and write its value in buf.
768 The return value is buf if the tag had a value, or NULL if not.
770 intval is used with conditionals/enums: when this function is called,
771 intval should contain the number of options in the conditional/enum.
772 When this function returns, intval is -1 if the tag is non numeric or,
773 if the tag is numeric, intval is the enum case we want to go to.
774 When not treating a conditional/enum, intval should be NULL.
776 static char *get_token_value(struct gui_wps *gwps,
777 struct wps_token *token,
778 char *buf, int buf_size,
779 int *intval)
781 if (!gwps)
782 return NULL;
784 struct wps_data *data = gwps->data;
785 struct wps_state *state = gwps->state;
787 if (!data || !state)
788 return NULL;
790 struct mp3entry *id3;
792 if (token->next)
793 id3 = state->nid3;
794 else
795 id3 = state->id3;
797 if (!id3)
798 return NULL;
800 #if CONFIG_RTC
801 struct tm* tm = NULL;
803 /* if the token is an RTC one, update the time
804 and do the necessary checks */
805 if (token->type >= WPS_TOKENS_RTC_BEGIN
806 && token->type <= WPS_TOKENS_RTC_END)
808 tm = get_time();
810 if (!valid_time(tm))
811 return NULL;
813 #endif
815 int limit = 1;
816 if (intval)
818 limit = *intval;
819 *intval = -1;
822 switch (token->type)
824 case WPS_TOKEN_CHARACTER:
825 return &(token->value.c);
827 case WPS_TOKEN_STRING:
828 return data->strings[token->value.i];
830 case WPS_TOKEN_TRACK_TIME_ELAPSED:
831 format_time(buf, buf_size,
832 id3->elapsed + state->ff_rewind_count);
833 return buf;
835 case WPS_TOKEN_TRACK_TIME_REMAINING:
836 format_time(buf, buf_size,
837 id3->length - id3->elapsed -
838 state->ff_rewind_count);
839 return buf;
841 case WPS_TOKEN_TRACK_LENGTH:
842 format_time(buf, buf_size, id3->length);
843 return buf;
845 case WPS_TOKEN_PLAYLIST_ENTRIES:
846 snprintf(buf, buf_size, "%d", playlist_amount());
847 return buf;
849 case WPS_TOKEN_PLAYLIST_NAME:
850 return playlist_name(NULL, buf, buf_size);
852 case WPS_TOKEN_PLAYLIST_POSITION:
853 snprintf(buf, buf_size, "%d", playlist_get_display_index());
854 return buf;
856 case WPS_TOKEN_PLAYLIST_SHUFFLE:
857 if ( global_settings.playlist_shuffle )
858 return "s";
859 else
860 return NULL;
861 break;
863 case WPS_TOKEN_VOLUME:
864 snprintf(buf, buf_size, "%d", global_settings.volume);
865 if (intval)
867 if (global_settings.volume == sound_min(SOUND_VOLUME))
869 *intval = 1;
871 else if (global_settings.volume == 0)
873 *intval = limit - 1;
875 else if (global_settings.volume > 0)
877 *intval = limit;
879 else
881 *intval = (limit - 3) * (global_settings.volume
882 - sound_min(SOUND_VOLUME) - 1)
883 / (-1 - sound_min(SOUND_VOLUME)) + 2;
886 return buf;
888 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
889 if (id3->length <= 0)
890 return NULL;
892 if (intval)
894 *intval = limit * (id3->elapsed + state->ff_rewind_count)
895 / id3->length + 1;
897 snprintf(buf, buf_size, "%d",
898 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
899 return buf;
901 case WPS_TOKEN_METADATA_ARTIST:
902 return id3->artist;
904 case WPS_TOKEN_METADATA_COMPOSER:
905 return id3->composer;
907 case WPS_TOKEN_METADATA_ALBUM:
908 return id3->album;
910 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
911 return id3->albumartist;
913 case WPS_TOKEN_METADATA_GROUPING:
914 return id3->grouping;
916 case WPS_TOKEN_METADATA_GENRE:
917 return id3->genre_string;
919 case WPS_TOKEN_METADATA_DISC_NUMBER:
920 if (id3->disc_string)
921 return id3->disc_string;
922 if (id3->discnum) {
923 snprintf(buf, buf_size, "%d", id3->discnum);
924 return buf;
926 return NULL;
928 case WPS_TOKEN_METADATA_TRACK_NUMBER:
929 if (id3->track_string)
930 return id3->track_string;
932 if (id3->tracknum) {
933 snprintf(buf, buf_size, "%d", id3->tracknum);
934 return buf;
936 return NULL;
938 case WPS_TOKEN_METADATA_TRACK_TITLE:
939 return id3->title;
941 case WPS_TOKEN_METADATA_VERSION:
942 switch (id3->id3version)
944 case ID3_VER_1_0:
945 return "1";
947 case ID3_VER_1_1:
948 return "1.1";
950 case ID3_VER_2_2:
951 return "2.2";
953 case ID3_VER_2_3:
954 return "2.3";
956 case ID3_VER_2_4:
957 return "2.4";
959 default:
960 return NULL;
963 case WPS_TOKEN_METADATA_YEAR:
964 if( id3->year_string )
965 return id3->year_string;
967 if (id3->year) {
968 snprintf(buf, buf_size, "%d", id3->year);
969 return buf;
971 return NULL;
973 case WPS_TOKEN_METADATA_COMMENT:
974 return id3->comment;
976 #ifdef HAVE_ALBUMART
977 case WPS_TOKEN_ALBUMART_DISPLAY:
978 draw_album_art(gwps, audio_current_aa_hid(), false);
979 return NULL;
981 case WPS_TOKEN_ALBUMART_FOUND:
982 if (audio_current_aa_hid() >= 0) {
983 snprintf(buf, buf_size, "C");
984 return buf;
986 return NULL;
987 #endif
989 case WPS_TOKEN_FILE_BITRATE:
990 if(id3->bitrate)
991 snprintf(buf, buf_size, "%d", id3->bitrate);
992 else
993 snprintf(buf, buf_size, "?");
994 return buf;
996 case WPS_TOKEN_FILE_CODEC:
997 if (intval)
999 if(id3->codectype == AFMT_UNKNOWN)
1000 *intval = AFMT_NUM_CODECS;
1001 else
1002 *intval = id3->codectype;
1004 return get_codectype(id3);
1006 case WPS_TOKEN_FILE_FREQUENCY:
1007 snprintf(buf, buf_size, "%ld", id3->frequency);
1008 return buf;
1010 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
1011 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1012 if ((id3->frequency % 1000) < 100)
1013 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1014 else
1015 snprintf(buf, buf_size, "%ld.%d",
1016 id3->frequency / 1000,
1017 (id3->frequency % 1000) / 100);
1018 return buf;
1020 case WPS_TOKEN_FILE_NAME:
1021 if (get_dir(buf, buf_size, id3->path, 0)) {
1022 /* Remove extension */
1023 char* sep = strrchr(buf, '.');
1024 if (NULL != sep) {
1025 *sep = 0;
1027 return buf;
1029 else {
1030 return NULL;
1033 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1034 return get_dir(buf, buf_size, id3->path, 0);
1036 case WPS_TOKEN_FILE_PATH:
1037 return id3->path;
1039 case WPS_TOKEN_FILE_SIZE:
1040 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1041 return buf;
1043 case WPS_TOKEN_FILE_VBR:
1044 return id3->vbr ? "(avg)" : NULL;
1046 case WPS_TOKEN_FILE_DIRECTORY:
1047 return get_dir(buf, buf_size, id3->path, token->value.i);
1049 case WPS_TOKEN_BATTERY_PERCENT:
1051 int l = battery_level();
1053 if (intval)
1055 limit = MAX(limit, 2);
1056 if (l > -1) {
1057 /* First enum is used for "unknown level". */
1058 *intval = (limit - 1) * l / 100 + 2;
1059 } else {
1060 *intval = 1;
1064 if (l > -1) {
1065 snprintf(buf, buf_size, "%d", l);
1066 return buf;
1067 } else {
1068 return "?";
1072 case WPS_TOKEN_BATTERY_VOLTS:
1074 unsigned int v = battery_voltage();
1075 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1076 return buf;
1079 case WPS_TOKEN_BATTERY_TIME:
1081 int t = battery_time();
1082 if (t >= 0)
1083 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1084 else
1085 strncpy(buf, "?h ?m", buf_size);
1086 return buf;
1089 #if CONFIG_CHARGING
1090 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1092 if(charger_input_state==CHARGER)
1093 return "p";
1094 else
1095 return NULL;
1097 #endif
1098 #if CONFIG_CHARGING >= CHARGING_MONITOR
1099 case WPS_TOKEN_BATTERY_CHARGING:
1101 if (charge_state == CHARGING || charge_state == TOPOFF) {
1102 return "c";
1103 } else {
1104 return NULL;
1107 #endif
1108 case WPS_TOKEN_BATTERY_SLEEPTIME:
1110 if (get_sleep_timer() == 0)
1111 return NULL;
1112 else
1114 format_time(buf, buf_size, get_sleep_timer() * 1000);
1115 return buf;
1119 case WPS_TOKEN_PLAYBACK_STATUS:
1121 int status = audio_status();
1122 int mode = 1;
1123 if (status == AUDIO_STATUS_PLAY)
1124 mode = 2;
1125 if (wps_fading_out ||
1126 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1127 mode = 3;
1128 if (status_get_ffmode() == STATUS_FASTFORWARD)
1129 mode = 4;
1130 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1131 mode = 5;
1133 if (intval) {
1134 *intval = mode;
1137 snprintf(buf, buf_size, "%d", mode-1);
1138 return buf;
1141 case WPS_TOKEN_REPEAT_MODE:
1142 if (intval)
1143 *intval = global_settings.repeat_mode + 1;
1144 snprintf(buf, buf_size, "%d", *intval);
1145 return buf;
1146 case WPS_TOKEN_RTC_12HOUR_CFG:
1147 if (intval)
1148 *intval = global_settings.timeformat + 1;
1149 snprintf(buf, buf_size, "%d", *intval);
1150 return buf;
1151 #if CONFIG_RTC
1152 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1153 /* d: day of month (01..31) */
1154 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1155 return buf;
1157 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1158 /* e: day of month, blank padded ( 1..31) */
1159 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1160 return buf;
1162 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1163 /* H: hour (00..23) */
1164 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1165 return buf;
1167 case WPS_TOKEN_RTC_HOUR_24:
1168 /* k: hour ( 0..23) */
1169 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1170 return buf;
1172 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1173 /* I: hour (01..12) */
1174 snprintf(buf, buf_size, "%02d",
1175 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1176 return buf;
1178 case WPS_TOKEN_RTC_HOUR_12:
1179 /* l: hour ( 1..12) */
1180 snprintf(buf, buf_size, "%2d",
1181 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1182 return buf;
1184 case WPS_TOKEN_RTC_MONTH:
1185 /* m: month (01..12) */
1186 if (intval)
1187 *intval = tm->tm_mon + 1;
1188 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1189 return buf;
1191 case WPS_TOKEN_RTC_MINUTE:
1192 /* M: minute (00..59) */
1193 snprintf(buf, buf_size, "%02d", tm->tm_min);
1194 return buf;
1196 case WPS_TOKEN_RTC_SECOND:
1197 /* S: second (00..59) */
1198 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1199 return buf;
1201 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1202 /* y: last two digits of year (00..99) */
1203 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1204 return buf;
1206 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1207 /* Y: year (1970...) */
1208 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1209 return buf;
1211 case WPS_TOKEN_RTC_AM_PM_UPPER:
1212 /* p: upper case AM or PM indicator */
1213 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1214 return buf;
1216 case WPS_TOKEN_RTC_AM_PM_LOWER:
1217 /* P: lower case am or pm indicator */
1218 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1219 return buf;
1221 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1222 /* a: abbreviated weekday name (Sun..Sat) */
1223 snprintf(buf, buf_size, "%s",str(LANG_WEEKDAY_SUNDAY + tm->tm_wday));
1224 return buf;
1226 case WPS_TOKEN_RTC_MONTH_NAME:
1227 /* b: abbreviated month name (Jan..Dec) */
1228 snprintf(buf, buf_size, "%s",str(LANG_MONTH_JANUARY + tm->tm_mon));
1229 return buf;
1231 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1232 /* u: day of week (1..7); 1 is Monday */
1233 if (intval)
1234 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1235 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1236 return buf;
1238 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1239 /* w: day of week (0..6); 0 is Sunday */
1240 if (intval)
1241 *intval = tm->tm_wday + 1;
1242 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1243 return buf;
1244 #else
1245 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1246 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1247 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1248 case WPS_TOKEN_RTC_HOUR_24:
1249 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1250 case WPS_TOKEN_RTC_HOUR_12:
1251 case WPS_TOKEN_RTC_MONTH:
1252 case WPS_TOKEN_RTC_MINUTE:
1253 case WPS_TOKEN_RTC_SECOND:
1254 case WPS_TOKEN_RTC_AM_PM_UPPER:
1255 case WPS_TOKEN_RTC_AM_PM_LOWER:
1256 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1257 strncpy(buf, "--", buf_size);
1258 return buf;
1259 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1260 strncpy(buf, "----", buf_size);
1261 return buf;
1262 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1263 case WPS_TOKEN_RTC_MONTH_NAME:
1264 strncpy(buf, "---", buf_size);
1265 return buf;
1266 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1267 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1268 strncpy(buf, "-", buf_size);
1269 return buf;
1270 #endif
1272 #ifdef HAVE_LCD_CHARCELLS
1273 case WPS_TOKEN_PROGRESSBAR:
1275 char *end = utf8encode(data->wps_progress_pat[0], buf);
1276 *end = '\0';
1277 return buf;
1280 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1281 if(is_new_player())
1283 /* we need 11 characters (full line) for
1284 progress-bar */
1285 snprintf(buf, buf_size, " ");
1287 else
1289 /* Tell the user if we have an OldPlayer */
1290 snprintf(buf, buf_size, " <Old LCD> ");
1292 return buf;
1293 #endif
1295 #ifdef HAVE_TAGCACHE
1296 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1297 if (intval) {
1298 *intval = id3->playcount + 1;
1300 snprintf(buf, buf_size, "%ld", id3->playcount);
1301 return buf;
1303 case WPS_TOKEN_DATABASE_RATING:
1304 if (intval) {
1305 *intval = id3->rating + 1;
1307 snprintf(buf, buf_size, "%d", id3->rating);
1308 return buf;
1310 case WPS_TOKEN_DATABASE_AUTOSCORE:
1311 if (intval)
1312 *intval = id3->score + 1;
1314 snprintf(buf, buf_size, "%d", id3->score);
1315 return buf;
1316 #endif
1318 #if (CONFIG_CODEC == SWCODEC)
1319 case WPS_TOKEN_CROSSFADE:
1320 if (intval)
1321 *intval = global_settings.crossfade + 1;
1322 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1323 return buf;
1325 case WPS_TOKEN_REPLAYGAIN:
1327 int val;
1329 if (global_settings.replaygain == 0)
1330 val = 1; /* off */
1331 else
1333 int type =
1334 get_replaygain_mode(id3->track_gain_string != NULL,
1335 id3->album_gain_string != NULL);
1336 if (type < 0)
1337 val = 6; /* no tag */
1338 else
1339 val = type + 2;
1341 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1342 val += 2;
1345 if (intval)
1346 *intval = val;
1348 switch (val)
1350 case 1:
1351 case 6:
1352 return "+0.00 dB";
1353 break;
1354 case 2:
1355 case 4:
1356 strncpy(buf, id3->track_gain_string, buf_size);
1357 break;
1358 case 3:
1359 case 5:
1360 strncpy(buf, id3->album_gain_string, buf_size);
1361 break;
1363 return buf;
1365 #endif /* (CONFIG_CODEC == SWCODEC) */
1367 #if (CONFIG_CODEC != MAS3507D)
1368 case WPS_TOKEN_SOUND_PITCH:
1370 int val = sound_get_pitch();
1371 snprintf(buf, buf_size, "%d.%d",
1372 val / 10, val % 10);
1373 return buf;
1375 #endif
1377 case WPS_TOKEN_MAIN_HOLD:
1378 #ifdef HAS_BUTTON_HOLD
1379 if (button_hold())
1380 #else
1381 if (is_keys_locked())
1382 #endif /*hold switch or softlock*/
1383 return "h";
1384 else
1385 return NULL;
1387 #ifdef HAS_REMOTE_BUTTON_HOLD
1388 case WPS_TOKEN_REMOTE_HOLD:
1389 if (remote_button_hold())
1390 return "r";
1391 else
1392 return NULL;
1393 #endif
1395 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1396 case WPS_TOKEN_VLED_HDD:
1397 if(led_read(HZ/2))
1398 return "h";
1399 else
1400 return NULL;
1401 #endif
1402 default:
1403 return NULL;
1407 /* Return the index to the end token for the conditional token at index.
1408 The conditional token can be either a start token or a separator
1409 (i.e. option) token.
1411 static int find_conditional_end(struct wps_data *data, int index)
1413 int ret = index;
1414 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1415 ret = data->tokens[ret].value.i;
1417 /* ret now is the index to the end token for the conditional. */
1418 return ret;
1421 /* Return the index of the appropriate case for the conditional
1422 that starts at cond_index.
1424 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1426 if (!gwps)
1427 return false;
1429 struct wps_data *data = gwps->data;
1431 int i, cond_end;
1432 int cond_index = *token_index;
1433 char result[128], *value;
1434 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1435 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1437 /* treat ?xx<true> constructs as if they had 2 options. */
1438 if (num_options < 2)
1439 num_options = 2;
1441 int intval = num_options;
1442 /* get_token_value needs to know the number of options in the enum */
1443 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1444 result, sizeof(result), &intval);
1446 /* intval is now the number of the enum option we want to read,
1447 starting from 1. If intval is -1, we check if value is empty. */
1448 if (intval == -1)
1449 intval = (value && *value) ? 1 : num_options;
1450 else if (intval > num_options || intval < 1)
1451 intval = num_options;
1453 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1455 /* skip to the right enum case */
1456 int next = cond_index + 2;
1457 for (i = 1; i < intval; i++)
1459 next = data->tokens[next].value.i;
1461 *token_index = next;
1463 if (prev_val == intval)
1465 /* Same conditional case as previously. Return without clearing the
1466 pictures */
1467 return false;
1470 cond_end = find_conditional_end(data, cond_index + 2);
1471 for (i = cond_index + 3; i < cond_end; i++)
1473 #ifdef HAVE_LCD_BITMAP
1474 /* clear all pictures in the conditional and nested ones */
1475 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1476 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1477 #endif
1478 #ifdef HAVE_ALBUMART
1479 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1480 draw_album_art(gwps, audio_current_aa_hid(), true);
1481 #endif
1484 return true;
1487 /* Read a (sub)line to the given alignment format buffer.
1488 linebuf is the buffer where the data is actually stored.
1489 align is the alignment format that'll be used to display the text.
1490 The return value indicates whether the line needs to be updated.
1492 static bool get_line(struct gui_wps *gwps,
1493 int line, int subline,
1494 struct align_pos *align,
1495 char *linebuf,
1496 int linebuf_size)
1498 struct wps_data *data = gwps->data;
1500 char temp_buf[128];
1501 char *buf = linebuf; /* will always point to the writing position */
1502 char *linebuf_end = linebuf + linebuf_size - 1;
1503 int i, last_token_idx;
1504 bool update = false;
1506 /* alignment-related variables */
1507 int cur_align;
1508 char* cur_align_start;
1509 cur_align_start = buf;
1510 cur_align = WPS_ALIGN_LEFT;
1511 align->left = NULL;
1512 align->center = NULL;
1513 align->right = NULL;
1515 /* Process all tokens of the desired subline */
1516 last_token_idx = wps_last_token_index(data, line, subline);
1517 for (i = wps_first_token_index(data, line, subline);
1518 i <= last_token_idx; i++)
1520 switch(data->tokens[i].type)
1522 case WPS_TOKEN_CONDITIONAL:
1523 /* place ourselves in the right conditional case */
1524 update |= evaluate_conditional(gwps, &i);
1525 break;
1527 case WPS_TOKEN_CONDITIONAL_OPTION:
1528 /* we've finished in the curent conditional case,
1529 skip to the end of the conditional structure */
1530 i = find_conditional_end(data, i);
1531 break;
1533 #ifdef HAVE_LCD_BITMAP
1534 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1536 struct gui_img *img = data->img;
1537 int n = data->tokens[i].value.i & 0xFF;
1538 int subimage = data->tokens[i].value.i >> 8;
1540 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1541 img[n].display = subimage;
1542 break;
1544 #endif
1546 case WPS_TOKEN_ALIGN_LEFT:
1547 case WPS_TOKEN_ALIGN_CENTER:
1548 case WPS_TOKEN_ALIGN_RIGHT:
1549 /* remember where the current aligned text started */
1550 switch (cur_align)
1552 case WPS_ALIGN_LEFT:
1553 align->left = cur_align_start;
1554 break;
1556 case WPS_ALIGN_CENTER:
1557 align->center = cur_align_start;
1558 break;
1560 case WPS_ALIGN_RIGHT:
1561 align->right = cur_align_start;
1562 break;
1564 /* start a new alignment */
1565 switch (data->tokens[i].type)
1567 case WPS_TOKEN_ALIGN_LEFT:
1568 cur_align = WPS_ALIGN_LEFT;
1569 break;
1570 case WPS_TOKEN_ALIGN_CENTER:
1571 cur_align = WPS_ALIGN_CENTER;
1572 break;
1573 case WPS_TOKEN_ALIGN_RIGHT:
1574 cur_align = WPS_ALIGN_RIGHT;
1575 break;
1576 default:
1577 break;
1579 *buf++ = 0;
1580 cur_align_start = buf;
1581 break;
1582 case WPS_VIEWPORT_ENABLE:
1584 char label = data->tokens[i].value.i;
1585 int j;
1586 char temp = VP_DRAW_HIDEABLE;
1587 for(j=0;j<data->num_viewports;j++)
1589 temp = VP_DRAW_HIDEABLE;
1590 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1591 (data->viewports[j].label == label))
1593 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1594 temp |= VP_DRAW_WASHIDDEN;
1595 data->viewports[j].hidden_flags = temp;
1599 break;
1600 default:
1602 /* get the value of the tag and copy it to the buffer */
1603 char *value = get_token_value(gwps, &data->tokens[i],
1604 temp_buf, sizeof(temp_buf), NULL);
1605 if (value)
1607 update = true;
1608 while (*value && (buf < linebuf_end))
1609 *buf++ = *value++;
1611 break;
1616 /* close the current alignment */
1617 switch (cur_align)
1619 case WPS_ALIGN_LEFT:
1620 align->left = cur_align_start;
1621 break;
1623 case WPS_ALIGN_CENTER:
1624 align->center = cur_align_start;
1625 break;
1627 case WPS_ALIGN_RIGHT:
1628 align->right = cur_align_start;
1629 break;
1632 return update;
1635 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1637 struct wps_data *data = gwps->data;
1638 int i;
1639 int subline_idx = wps_subline_index(data, line, subline);
1640 int last_token_idx = wps_last_token_index(data, line, subline);
1642 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1644 for (i = wps_first_token_index(data, line, subline);
1645 i <= last_token_idx; i++)
1647 switch(data->tokens[i].type)
1649 case WPS_TOKEN_CONDITIONAL:
1650 /* place ourselves in the right conditional case */
1651 evaluate_conditional(gwps, &i);
1652 break;
1654 case WPS_TOKEN_CONDITIONAL_OPTION:
1655 /* we've finished in the curent conditional case,
1656 skip to the end of the conditional structure */
1657 i = find_conditional_end(data, i);
1658 break;
1660 case WPS_TOKEN_SUBLINE_TIMEOUT:
1661 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1662 break;
1664 default:
1665 break;
1670 /* Calculates which subline should be displayed for the specified line
1671 Returns true iff the subline must be refreshed */
1672 static bool update_curr_subline(struct gui_wps *gwps, int line)
1674 struct wps_data *data = gwps->data;
1676 int search, search_start, num_sublines;
1677 bool reset_subline;
1678 bool new_subline_refresh;
1679 bool only_one_subline;
1681 num_sublines = data->lines[line].num_sublines;
1682 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1683 new_subline_refresh = false;
1684 only_one_subline = false;
1686 /* if time to advance to next sub-line */
1687 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1688 reset_subline)
1690 /* search all sublines until the next subline with time > 0
1691 is found or we get back to the subline we started with */
1692 if (reset_subline)
1693 search_start = 0;
1694 else
1695 search_start = data->lines[line].curr_subline;
1697 for (search = 0; search < num_sublines; search++)
1699 data->lines[line].curr_subline++;
1701 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1702 if (data->lines[line].curr_subline == num_sublines)
1704 if (data->lines[line].curr_subline == 1)
1705 only_one_subline = true;
1706 data->lines[line].curr_subline = 0;
1709 /* if back where we started after search or
1710 only one subline is defined on the line */
1711 if (((search > 0) &&
1712 (data->lines[line].curr_subline == search_start)) ||
1713 only_one_subline)
1715 /* no other subline with a time > 0 exists */
1716 data->lines[line].subline_expire_time = (reset_subline ?
1717 current_tick :
1718 data->lines[line].subline_expire_time) + 100 * HZ;
1719 break;
1721 else
1723 /* get initial time multiplier for this subline */
1724 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1726 int subline_idx = wps_subline_index(data, line,
1727 data->lines[line].curr_subline);
1729 /* only use this subline if subline time > 0 */
1730 if (data->sublines[subline_idx].time_mult > 0)
1732 new_subline_refresh = true;
1733 data->lines[line].subline_expire_time = (reset_subline ?
1734 current_tick : data->lines[line].subline_expire_time) +
1735 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1736 break;
1742 return new_subline_refresh;
1745 /* Display a line appropriately according to its alignment format.
1746 format_align contains the text, separated between left, center and right.
1747 line is the index of the line on the screen.
1748 scroll indicates whether the line is a scrolling one or not.
1750 static void write_line(struct screen *display,
1751 struct align_pos *format_align,
1752 int line,
1753 bool scroll)
1756 int left_width = 0, left_xpos;
1757 int center_width = 0, center_xpos;
1758 int right_width = 0, right_xpos;
1759 int ypos;
1760 int space_width;
1761 int string_height;
1762 int scroll_width;
1764 /* calculate different string sizes and positions */
1765 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1766 if (format_align->left != 0) {
1767 display->getstringsize((unsigned char *)format_align->left,
1768 &left_width, &string_height);
1771 if (format_align->right != 0) {
1772 display->getstringsize((unsigned char *)format_align->right,
1773 &right_width, &string_height);
1776 if (format_align->center != 0) {
1777 display->getstringsize((unsigned char *)format_align->center,
1778 &center_width, &string_height);
1781 left_xpos = 0;
1782 right_xpos = (display->getwidth() - right_width);
1783 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1785 scroll_width = display->getwidth() - left_xpos;
1787 /* Checks for overlapping strings.
1788 If needed the overlapping strings will be merged, separated by a
1789 space */
1791 /* CASE 1: left and centered string overlap */
1792 /* there is a left string, need to merge left and center */
1793 if ((left_width != 0 && center_width != 0) &&
1794 (left_xpos + left_width + space_width > center_xpos)) {
1795 /* replace the former separator '\0' of left and
1796 center string with a space */
1797 *(--format_align->center) = ' ';
1798 /* calculate the new width and position of the merged string */
1799 left_width = left_width + space_width + center_width;
1800 /* there is no centered string anymore */
1801 center_width = 0;
1803 /* there is no left string, move center to left */
1804 if ((left_width == 0 && center_width != 0) &&
1805 (left_xpos + left_width > center_xpos)) {
1806 /* move the center string to the left string */
1807 format_align->left = format_align->center;
1808 /* calculate the new width and position of the string */
1809 left_width = center_width;
1810 /* there is no centered string anymore */
1811 center_width = 0;
1814 /* CASE 2: centered and right string overlap */
1815 /* there is a right string, need to merge center and right */
1816 if ((center_width != 0 && right_width != 0) &&
1817 (center_xpos + center_width + space_width > right_xpos)) {
1818 /* replace the former separator '\0' of center and
1819 right string with a space */
1820 *(--format_align->right) = ' ';
1821 /* move the center string to the right after merge */
1822 format_align->right = format_align->center;
1823 /* calculate the new width and position of the merged string */
1824 right_width = center_width + space_width + right_width;
1825 right_xpos = (display->getwidth() - right_width);
1826 /* there is no centered string anymore */
1827 center_width = 0;
1829 /* there is no right string, move center to right */
1830 if ((center_width != 0 && right_width == 0) &&
1831 (center_xpos + center_width > right_xpos)) {
1832 /* move the center string to the right string */
1833 format_align->right = format_align->center;
1834 /* calculate the new width and position of the string */
1835 right_width = center_width;
1836 right_xpos = (display->getwidth() - right_width);
1837 /* there is no centered string anymore */
1838 center_width = 0;
1841 /* CASE 3: left and right overlap
1842 There is no center string anymore, either there never
1843 was one or it has been merged in case 1 or 2 */
1844 /* there is a left string, need to merge left and right */
1845 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1846 (left_xpos + left_width + space_width > right_xpos)) {
1847 /* replace the former separator '\0' of left and
1848 right string with a space */
1849 *(--format_align->right) = ' ';
1850 /* calculate the new width and position of the string */
1851 left_width = left_width + space_width + right_width;
1852 /* there is no right string anymore */
1853 right_width = 0;
1855 /* there is no left string, move right to left */
1856 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1857 (left_width > right_xpos)) {
1858 /* move the right string to the left string */
1859 format_align->left = format_align->right;
1860 /* calculate the new width and position of the string */
1861 left_width = right_width;
1862 /* there is no right string anymore */
1863 right_width = 0;
1866 ypos = (line * string_height);
1869 if (scroll && ((left_width > scroll_width) ||
1870 (center_width > scroll_width) ||
1871 (right_width > scroll_width)))
1873 display->puts_scroll(0, line,
1874 (unsigned char *)format_align->left);
1876 else
1878 #ifdef HAVE_LCD_BITMAP
1879 /* clear the line first */
1880 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1881 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1882 display->set_drawmode(DRMODE_SOLID);
1883 #endif
1885 /* Nasty hack: we output an empty scrolling string,
1886 which will reset the scroller for that line */
1887 display->puts_scroll(0, line, (unsigned char *)"");
1889 /* print aligned strings */
1890 if (left_width != 0)
1892 display->putsxy(left_xpos, ypos,
1893 (unsigned char *)format_align->left);
1895 if (center_width != 0)
1897 display->putsxy(center_xpos, ypos,
1898 (unsigned char *)format_align->center);
1900 if (right_width != 0)
1902 display->putsxy(right_xpos, ypos,
1903 (unsigned char *)format_align->right);
1908 /* Refresh the WPS according to refresh_mode. */
1909 bool gui_wps_refresh(struct gui_wps *gwps,
1910 int ffwd_offset,
1911 unsigned char refresh_mode)
1913 struct wps_data *data = gwps->data;
1914 struct screen *display = gwps->display;
1915 struct wps_state *state = gwps->state;
1917 if(!gwps || !data || !state || !display)
1918 return false;
1920 int v, line, i, subline_idx;
1921 unsigned char flags;
1922 char linebuf[MAX_PATH];
1923 unsigned char vp_refresh_mode;
1925 struct align_pos align;
1926 align.left = NULL;
1927 align.center = NULL;
1928 align.right = NULL;
1930 bool update_line, new_subline_refresh;
1932 #ifdef HAVE_LCD_BITMAP
1933 gui_wps_statusbar_draw(gwps, true);
1935 /* to find out wether the peak meter is enabled we
1936 assume it wasn't until we find a line that contains
1937 the peak meter. We can't use peak_meter_enabled itself
1938 because that would mean to turn off the meter thread
1939 temporarily. (That shouldn't matter unless yield
1940 or sleep is called but who knows...)
1942 bool enable_pm = false;
1944 #endif
1946 /* reset to first subline if refresh all flag is set */
1947 if (refresh_mode == WPS_REFRESH_ALL)
1949 display->set_viewport(&data->viewports[0].vp);
1950 display->clear_viewport();
1952 for (i = 0; i <= data->num_lines; i++)
1954 data->lines[i].curr_subline = SUBLINE_RESET;
1958 #ifdef HAVE_LCD_CHARCELLS
1959 for (i = 0; i < 8; i++)
1961 if (data->wps_progress_pat[i] == 0)
1962 data->wps_progress_pat[i] = display->get_locked_pattern();
1964 #endif
1966 if (!state->id3)
1968 display->stop_scroll();
1969 return false;
1972 state->ff_rewind_count = ffwd_offset;
1974 /* disable any viewports which are conditionally displayed */
1975 for (v = 0; v < data->num_viewports; v++)
1977 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
1979 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
1980 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1981 else
1982 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
1985 for (v = 0; v < data->num_viewports; v++)
1987 display->set_viewport(&data->viewports[v].vp);
1988 vp_refresh_mode = refresh_mode;
1990 #ifdef HAVE_LCD_BITMAP
1991 /* Set images to not to be displayed */
1992 for (i = 0; i < MAX_IMAGES; i++)
1994 data->img[i].display = -1;
1996 #endif
1997 /* dont redraw the viewport if its disabled */
1998 if ((data->viewports[v].hidden_flags&VP_DRAW_HIDDEN))
2000 if (!(data->viewports[v].hidden_flags&VP_DRAW_WASHIDDEN))
2001 display->scroll_stop(&data->viewports[v].vp);
2002 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2003 continue;
2005 else if (((data->viewports[v].hidden_flags&
2006 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2007 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2009 vp_refresh_mode = WPS_REFRESH_ALL;
2010 data->viewports[v].hidden_flags = VP_DRAW_HIDEABLE;
2012 if (vp_refresh_mode == WPS_REFRESH_ALL)
2014 display->clear_viewport();
2017 for (line = data->viewports[v].first_line;
2018 line <= data->viewports[v].last_line; line++)
2020 memset(linebuf, 0, sizeof(linebuf));
2021 update_line = false;
2023 /* get current subline for the line */
2024 new_subline_refresh = update_curr_subline(gwps, line);
2026 subline_idx = wps_subline_index(data, line,
2027 data->lines[line].curr_subline);
2028 flags = data->sublines[subline_idx].line_type;
2030 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2031 || new_subline_refresh)
2033 /* get_line tells us if we need to update the line */
2034 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2035 &align, linebuf, sizeof(linebuf));
2037 #ifdef HAVE_LCD_BITMAP
2038 /* peakmeter */
2039 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2041 /* the peakmeter should be alone on its line */
2042 update_line = false;
2044 int h = font_get(data->viewports[v].vp.font)->height;
2045 int peak_meter_y = (line - data->viewports[v].first_line)* h;
2047 /* The user might decide to have the peak meter in the last
2048 line so that it is only displayed if no status bar is
2049 visible. If so we neither want do draw nor enable the
2050 peak meter. */
2051 if (peak_meter_y + h <= display->getheight()) {
2052 /* found a line with a peak meter -> remember that we must
2053 enable it later */
2054 enable_pm = true;
2055 peak_meter_screen(gwps->display, 0, peak_meter_y,
2056 MIN(h, display->getheight() - peak_meter_y));
2060 #else /* HAVE_LCD_CHARCELL */
2062 /* progressbar */
2063 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2065 if (data->full_line_progressbar)
2066 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2067 else
2068 draw_player_progress(gwps);
2070 #endif
2072 if (update_line &&
2073 /* conditionals clear the line which means if the %Vd is put into the default
2074 viewport there will be a blank line.
2075 To get around this we dont allow any actual drawing to happen in the
2076 deault vp if other vp's are defined */
2077 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2079 if (flags & WPS_REFRESH_SCROLL)
2081 /* if the line is a scrolling one we don't want to update
2082 too often, so that it has the time to scroll */
2083 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2084 write_line(display, &align, line - data->viewports[v].first_line, true);
2086 else
2087 write_line(display, &align, line - data->viewports[v].first_line, false);
2091 #ifdef HAVE_LCD_BITMAP
2092 /* progressbar */
2093 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2095 if (data->viewports[v].pb)
2096 draw_progressbar(gwps, data->viewports[v].pb);
2098 /* Now display any images in this viewport */
2099 wps_display_images(gwps, &data->viewports[v].vp);
2100 #endif
2103 #ifdef HAVE_LCD_BITMAP
2104 data->peak_meter_enabled = enable_pm;
2105 #endif
2107 /* Restore the default viewport */
2108 display->set_viewport(NULL);
2110 display->update();
2112 #ifdef HAVE_BACKLIGHT
2113 if (global_settings.caption_backlight && state->id3)
2115 /* turn on backlight n seconds before track ends, and turn it off n
2116 seconds into the new track. n == backlight_timeout, or 5s */
2117 int n = global_settings.backlight_timeout * 1000;
2119 if ( n < 1000 )
2120 n = 5000; /* use 5s if backlight is always on or off */
2122 if (((state->id3->elapsed < 1000) ||
2123 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2124 (state->paused == false))
2125 backlight_on();
2127 #endif
2128 #ifdef HAVE_REMOTE_LCD
2129 if (global_settings.remote_caption_backlight && state->id3)
2131 /* turn on remote backlight n seconds before track ends, and turn it
2132 off n seconds into the new track. n == remote_backlight_timeout,
2133 or 5s */
2134 int n = global_settings.remote_backlight_timeout * 1000;
2136 if ( n < 1000 )
2137 n = 5000; /* use 5s if backlight is always on or off */
2139 if (((state->id3->elapsed < 1000) ||
2140 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2141 (state->paused == false))
2142 remote_backlight_on();
2144 #endif
2146 return true;