Colour targets: Revert an optimisation from almost 18 months ago that actually turned...
[Rockbox.git] / apps / gui / gwps-common.c
blobb6a7c893085119b84e47d6df3eda0d82dfc2f437
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 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "gwps-common.h"
23 #include "font.h"
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include "system.h"
28 #include "settings.h"
29 #include "rbunicode.h"
30 #include "rtc.h"
31 #include "audio.h"
32 #include "status.h"
33 #include "power.h"
34 #include "powermgmt.h"
35 #include "sound.h"
36 #include "debug.h"
37 #ifdef HAVE_LCD_CHARCELLS
38 #include "hwcompat.h"
39 #endif
40 #include "abrepeat.h"
41 #include "mp3_playback.h"
42 #include "backlight.h"
43 #include "lang.h"
44 #include "misc.h"
45 #include "splash.h"
46 #include "scrollbar.h"
47 #include "led.h"
48 #include "lcd.h"
49 #ifdef HAVE_LCD_BITMAP
50 #include "peakmeter.h"
51 /* Image stuff */
52 #include "bmp.h"
53 #include "albumart.h"
54 #endif
55 #include "dsp.h"
56 #include "action.h"
57 #include "cuesheet.h"
58 #include "playlist.h"
59 #if CONFIG_CODEC == SWCODEC
60 #include "playback.h"
61 #endif
62 #include "backdrop.h"
64 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
65 /* 3% of 30min file == 54s step size */
66 #define MIN_FF_REWIND_STEP 500
68 /* draws the statusbar on the given wps-screen */
69 #ifdef HAVE_LCD_BITMAP
70 static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
72 bool draw = global_settings.statusbar;
74 if (wps->data->wps_sb_tag)
75 draw = wps->data->show_sb_on_wps;
77 if (draw)
78 gui_statusbar_draw(wps->statusbar, force);
80 #else
81 #define gui_wps_statusbar_draw(wps, force) \
82 gui_statusbar_draw((wps)->statusbar, (force))
83 #endif
84 #include "pcmbuf.h"
86 /* fades the volume */
87 bool wps_fading_out = false;
88 void fade(bool fade_in, bool updatewps)
90 int fp_global_vol = global_settings.volume << 8;
91 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
92 int fp_step = (fp_global_vol - fp_min_vol) / 30;
93 int i;
94 wps_fading_out = !fade_in;
95 if (fade_in) {
96 /* fade in */
97 int fp_volume = fp_min_vol;
99 /* zero out the sound */
100 sound_set_volume(fp_min_vol >> 8);
102 sleep(HZ/10); /* let audio thread run */
103 audio_resume();
105 while (fp_volume < fp_global_vol - fp_step) {
106 fp_volume += fp_step;
107 sound_set_volume(fp_volume >> 8);
108 if (updatewps)
110 FOR_NB_SCREENS(i)
111 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
113 sleep(1);
115 sound_set_volume(global_settings.volume);
117 else {
118 /* fade out */
119 int fp_volume = fp_global_vol;
121 while (fp_volume > fp_min_vol + fp_step) {
122 fp_volume -= fp_step;
123 sound_set_volume(fp_volume >> 8);
124 if (updatewps)
126 FOR_NB_SCREENS(i)
127 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
129 sleep(1);
131 audio_pause();
132 wps_fading_out = false;
133 #if CONFIG_CODEC != SWCODEC
134 #ifndef SIMULATOR
135 /* let audio thread run and wait for the mas to run out of data */
136 while (!mp3_pause_done())
137 #endif
138 sleep(HZ/10);
139 #endif
141 /* reset volume to what it was before the fade */
142 sound_set_volume(global_settings.volume);
146 /* return true if screen restore is needed
147 return false otherwise
149 bool update_onvol_change(struct gui_wps * gwps)
151 gui_wps_statusbar_draw(gwps, false);
152 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
154 #ifdef HAVE_LCD_CHARCELLS
155 gui_splash(gwps->display, 0, "Vol: %3d dB",
156 sound_val2phys(SOUND_VOLUME, global_settings.volume));
157 return true;
158 #endif
159 return false;
162 void play_hop(int direction)
164 if(!wps_state.id3 || !wps_state.id3->length
165 || global_settings.study_hop_step == 0)
166 return;
167 #define STEP ((unsigned)global_settings.study_hop_step *1000)
168 if(direction == 1
169 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
170 #if CONFIG_CODEC == SWCODEC
171 if(global_settings.beep)
172 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
173 #endif
174 return;
176 if((direction == -1 && wps_state.id3->elapsed < STEP))
177 wps_state.id3->elapsed = 0;
178 else
179 wps_state.id3->elapsed += STEP *direction;
180 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
181 #if (CONFIG_CODEC == SWCODEC)
182 audio_pre_ff_rewind();
183 #else
184 audio_pause();
185 #endif
187 audio_ff_rewind(wps_state.id3->elapsed);
188 #if (CONFIG_CODEC != SWCODEC)
189 if (!wps_state.paused)
190 audio_resume();
191 #endif
192 #undef STEP
195 bool ffwd_rew(int button)
197 unsigned int step = 0; /* current ff/rewind step */
198 unsigned int max_step = 0; /* maximum ff/rewind step */
199 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
200 int direction = -1; /* forward=1 or backward=-1 */
201 bool exit = false;
202 bool usb = false;
203 int i = 0;
204 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
206 if (button == ACTION_NONE)
208 status_set_ffmode(0);
209 return usb;
211 while (!exit)
213 switch ( button )
215 case ACTION_WPS_SEEKFWD:
216 direction = 1;
217 case ACTION_WPS_SEEKBACK:
218 if (wps_state.ff_rewind)
220 if (direction == 1)
222 /* fast forwarding, calc max step relative to end */
223 max_step = (wps_state.id3->length -
224 (wps_state.id3->elapsed +
225 ff_rewind_count)) *
226 FF_REWIND_MAX_PERCENT / 100;
228 else
230 /* rewinding, calc max step relative to start */
231 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
232 FF_REWIND_MAX_PERCENT / 100;
235 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
237 if (step > max_step)
238 step = max_step;
240 ff_rewind_count += step * direction;
242 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
243 step += step >> ff_rw_accel;
245 else
247 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
248 wps_state.id3 && wps_state.id3->length )
250 if (!wps_state.paused)
251 #if (CONFIG_CODEC == SWCODEC)
252 audio_pre_ff_rewind();
253 #else
254 audio_pause();
255 #endif
256 #if CONFIG_KEYPAD == PLAYER_PAD
257 FOR_NB_SCREENS(i)
258 gui_wps[i].display->stop_scroll();
259 #endif
260 if (direction > 0)
261 status_set_ffmode(STATUS_FASTFORWARD);
262 else
263 status_set_ffmode(STATUS_FASTBACKWARD);
265 wps_state.ff_rewind = true;
267 step = 1000 * global_settings.ff_rewind_min_step;
269 else
270 break;
273 if (direction > 0) {
274 if ((wps_state.id3->elapsed + ff_rewind_count) >
275 wps_state.id3->length)
276 ff_rewind_count = wps_state.id3->length -
277 wps_state.id3->elapsed;
279 else {
280 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
281 ff_rewind_count = -wps_state.id3->elapsed;
284 FOR_NB_SCREENS(i)
285 gui_wps_refresh(&gui_wps[i],
286 (wps_state.wps_time_countup == false)?
287 ff_rewind_count:-ff_rewind_count,
288 WPS_REFRESH_PLAYER_PROGRESS |
289 WPS_REFRESH_DYNAMIC);
291 break;
293 case ACTION_WPS_STOPSEEK:
294 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
295 audio_ff_rewind(wps_state.id3->elapsed);
296 ff_rewind_count = 0;
297 wps_state.ff_rewind = false;
298 status_set_ffmode(0);
299 #if (CONFIG_CODEC != SWCODEC)
300 if (!wps_state.paused)
301 audio_resume();
302 #endif
303 #ifdef HAVE_LCD_CHARCELLS
304 gui_wps_display();
305 #endif
306 exit = true;
307 break;
309 default:
310 if(default_event_handler(button) == SYS_USB_CONNECTED) {
311 status_set_ffmode(0);
312 usb = true;
313 exit = true;
315 break;
317 if (!exit)
318 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
320 return usb;
323 bool gui_wps_display(void)
325 int i;
326 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
328 global_status.resume_index = -1;
329 #ifdef HAVE_LCD_BITMAP
330 gui_syncstatusbar_draw(&statusbars, true);
331 #endif
332 gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST));
333 return true;
335 else
337 FOR_NB_SCREENS(i)
339 /* Update the values in the first (default) viewport - in case the user
340 has modified the statusbar or colour settings */
341 #ifdef HAVE_LCD_BITMAP
342 #if LCD_DEPTH > 1
343 if (gui_wps[i].display->depth > 1)
345 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
346 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
348 #endif
349 #endif
351 gui_wps[i].display->clear_display();
352 if (!gui_wps[i].data->wps_loaded) {
353 if ( !gui_wps[i].data->num_tokens ) {
354 /* set the default wps for the main-screen */
355 if(i == 0)
357 #ifdef HAVE_LCD_BITMAP
358 #if LCD_DEPTH > 1
359 unload_wps_backdrop();
360 #endif
361 wps_data_load(gui_wps[i].data,
362 gui_wps[i].display,
363 "%s%?it<%?in<%in. |>%it|%fn>\n"
364 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
365 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
366 "\n"
367 "%al%pc/%pt%ar[%pp:%pe]\n"
368 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
369 "%pb\n"
370 "%pm\n", false);
371 #else
372 wps_data_load(gui_wps[i].data,
373 gui_wps[i].display,
374 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
375 "%pc%?ps<*|/>%pt\n", false);
376 #endif
378 #if NB_SCREENS == 2
379 /* set the default wps for the remote-screen */
380 else if(i == 1)
382 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
383 unload_remote_wps_backdrop();
384 #endif
385 wps_data_load(gui_wps[i].data,
386 gui_wps[i].display,
387 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
388 "%s%?it<%?in<%in. |>%it|%fn>\n"
389 "%al%pc/%pt%ar[%pp:%pe]\n"
390 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
391 "%pb\n", false);
393 #endif
398 yield();
399 FOR_NB_SCREENS(i)
401 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
403 return false;
406 bool update(struct gui_wps *gwps)
408 bool track_changed = audio_has_changed_track();
409 bool retcode = false;
411 gwps->state->nid3 = audio_next_track();
412 if (track_changed)
414 gwps->display->stop_scroll();
415 gwps->state->id3 = audio_current_track();
417 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
418 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
420 /* the current cuesheet isn't the right one any more */
422 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
423 /* We have the new cuesheet in memory (temp_cue),
424 let's make it the current one ! */
425 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
427 else {
428 /* We need to parse the new cuesheet */
430 char cuepath[MAX_PATH];
432 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
433 parse_cuesheet(cuepath, curr_cue))
435 gwps->state->id3->cuesheet_type = 1;
436 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
440 cue_spoof_id3(curr_cue, gwps->state->id3);
443 if (gui_wps_display())
444 retcode = true;
445 else{
446 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
449 if (gwps->state->id3)
451 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
452 sizeof(gwps->state->current_track_path));
453 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
457 if (gwps->state->id3)
459 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
460 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
461 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
462 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
464 /* We've changed tracks within the cuesheet :
465 we need to update the ID3 info and refresh the WPS */
467 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
468 cue_spoof_id3(curr_cue, gwps->state->id3);
470 gwps->display->stop_scroll();
471 if (gui_wps_display())
472 retcode = true;
473 else
474 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
476 else
477 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
480 gui_wps_statusbar_draw(gwps, false);
482 return retcode;
486 void display_keylock_text(bool locked)
488 char* s;
489 int i;
490 FOR_NB_SCREENS(i)
491 gui_wps[i].display->stop_scroll();
493 if(locked)
494 s = str(LANG_KEYLOCK_ON);
495 else
496 s = str(LANG_KEYLOCK_OFF);
497 gui_syncsplash(HZ, s);
500 #ifdef HAVE_LCD_BITMAP
502 static void draw_progressbar(struct gui_wps *gwps,
503 struct progressbar *pb)
505 struct screen *display = gwps->display;
506 struct wps_state *state = gwps->state;
507 if (pb->have_bitmap_pb)
508 gui_bitmap_scrollbar_draw(display, pb->bm,
509 pb->x, pb->y, pb->width, pb->bm.height,
510 state->id3->length ? state->id3->length : 1, 0,
511 state->id3->length ? state->id3->elapsed
512 + state->ff_rewind_count : 0,
513 HORIZONTAL);
514 else
515 gui_scrollbar_draw(display, pb->x, pb->y, pb->width, pb->height,
516 state->id3->length ? state->id3->length : 1, 0,
517 state->id3->length ? state->id3->elapsed
518 + state->ff_rewind_count : 0,
519 HORIZONTAL);
520 #ifdef AB_REPEAT_ENABLE
521 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
522 ab_draw_markers(display, state->id3->length,
523 pb->x, pb->x + pb->width, pb->y, pb->height);
524 #endif
526 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
527 cue_draw_markers(display, state->id3->length,
528 pb->x, pb->x + pb->width, pb->y+1, pb->height-2);
531 /* clears the area where the image was shown */
532 static void clear_image_pos(struct gui_wps *gwps, int n)
534 if(!gwps)
535 return;
536 struct wps_data *data = gwps->data;
537 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
538 gwps->display->fillrect(data->img[n].x, data->img[n].y,
539 data->img[n].bm.width, data->img[n].subimage_height);
540 gwps->display->set_drawmode(DRMODE_SOLID);
543 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
545 struct screen *display = gwps->display;
546 struct wps_data *data = gwps->data;
547 if(data->img[n].always_display)
548 display->set_drawmode(DRMODE_FG);
549 else
550 display->set_drawmode(DRMODE_SOLID);
552 #if LCD_DEPTH > 1
553 if(data->img[n].bm.format == FORMAT_MONO) {
554 #endif
555 display->mono_bitmap_part(data->img[n].bm.data,
556 0, data->img[n].subimage_height * subimage,
557 data->img[n].bm.width, data->img[n].x,
558 data->img[n].y, data->img[n].bm.width,
559 data->img[n].subimage_height);
560 #if LCD_DEPTH > 1
561 } else {
562 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
563 0, data->img[n].subimage_height * subimage,
564 data->img[n].bm.width, data->img[n].x,
565 data->img[n].y, data->img[n].bm.width,
566 data->img[n].subimage_height);
568 #endif
571 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
573 if(!gwps || !gwps->data || !gwps->display)
574 return;
576 int n;
577 struct wps_data *data = gwps->data;
578 struct screen *display = gwps->display;
580 for (n = 0; n < MAX_IMAGES; n++)
582 if (data->img[n].loaded)
584 if (data->img[n].display >= 0)
586 wps_draw_image(gwps, n, data->img[n].display);
587 } else if (data->img[n].always_display && data->img[n].vp == vp)
589 wps_draw_image(gwps, n, 0);
593 display->set_drawmode(DRMODE_SOLID);
596 #else /* HAVE_LCD_CHARCELL */
598 static bool draw_player_progress(struct gui_wps *gwps)
600 struct wps_state *state = gwps->state;
601 struct screen *display = gwps->display;
602 unsigned char progress_pattern[7];
603 int pos = 0;
604 int i;
606 if (!state->id3)
607 return false;
609 if (state->id3->length)
610 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
611 / state->id3->length;
613 for (i = 0; i < 7; i++, pos -= 5)
615 if (pos <= 0)
616 progress_pattern[i] = 0x1f;
617 else if (pos >= 5)
618 progress_pattern[i] = 0x00;
619 else
620 progress_pattern[i] = 0x1f >> pos;
623 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
624 return true;
627 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
629 static const unsigned char numbers[10][4] = {
630 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
631 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
632 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
633 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
634 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
635 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
636 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
637 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
638 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
639 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
642 struct wps_state *state = gwps->state;
643 struct screen *display = gwps->display;
644 struct wps_data *data = gwps->data;
645 unsigned char progress_pattern[7];
646 char timestr[10];
647 int time;
648 int time_idx = 0;
649 int pos = 0;
650 int pat_idx = 1;
651 int digit, i, j;
652 bool softchar;
654 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
655 return;
657 time = state->id3->elapsed + state->ff_rewind_count;
658 if (state->id3->length)
659 pos = 55 * time / state->id3->length;
661 memset(timestr, 0, sizeof(timestr));
662 format_time(timestr, sizeof(timestr)-2, time);
663 timestr[strlen(timestr)] = ':'; /* always safe */
665 for (i = 0; i < 11; i++, pos -= 5)
667 softchar = false;
668 memset(progress_pattern, 0, sizeof(progress_pattern));
670 if ((digit = timestr[time_idx]))
672 softchar = true;
673 digit -= '0';
675 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
677 memcpy(progress_pattern, numbers[digit], 4);
678 time_idx += 2;
680 else /* tens, shifted right */
682 for (j = 0; j < 4; j++)
683 progress_pattern[j] = numbers[digit][j] >> 1;
685 if (time_idx > 0) /* not the first group, add colon in front */
687 progress_pattern[1] |= 0x10;
688 progress_pattern[3] |= 0x10;
690 time_idx++;
693 if (pos >= 5)
694 progress_pattern[5] = progress_pattern[6] = 0x1f;
697 if (pos > 0 && pos < 5)
699 softchar = true;
700 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
703 if (softchar && pat_idx < 8)
705 display->define_pattern(data->wps_progress_pat[pat_idx],
706 progress_pattern);
707 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
708 pat_idx++;
710 else if (pos <= 0)
711 buf = utf8encode(' ', buf);
712 else
713 buf = utf8encode(0xe115, buf); /* 2/7 _ */
715 *buf = '\0';
718 #endif /* HAVE_LCD_CHARCELL */
720 static char* get_codectype(const struct mp3entry* id3)
722 if (id3->codectype < AFMT_NUM_CODECS) {
723 return (char*)audio_formats[id3->codectype].label;
724 } else {
725 return NULL;
729 /* Extract a part from a path.
731 * buf - buffer extract part to.
732 * buf_size - size of buffer.
733 * path - path to extract from.
734 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
735 * parent of parent, etc.
737 * Returns buf if the desired level was found, NULL otherwise.
739 static char* get_dir(char* buf, int buf_size, const char* path, int level)
741 const char* sep;
742 const char* last_sep;
743 int len;
745 sep = path + strlen(path);
746 last_sep = sep;
748 while (sep > path)
750 if ('/' == *(--sep))
752 if (!level)
753 break;
755 level--;
756 last_sep = sep - 1;
760 if (level || (last_sep <= sep))
761 return NULL;
763 len = MIN(last_sep - sep, buf_size - 1);
764 strncpy(buf, sep + 1, len);
765 buf[len] = 0;
766 return buf;
769 /* Return the tag found at index i and write its value in buf.
770 The return value is buf if the tag had a value, or NULL if not.
772 intval is used with conditionals/enums: when this function is called,
773 intval should contain the number of options in the conditional/enum.
774 When this function returns, intval is -1 if the tag is non numeric or,
775 if the tag is numeric, intval is the enum case we want to go to.
776 When not treating a conditional/enum, intval should be NULL.
778 static char *get_token_value(struct gui_wps *gwps,
779 struct wps_token *token,
780 char *buf, int buf_size,
781 int *intval)
783 if (!gwps)
784 return NULL;
786 struct wps_data *data = gwps->data;
787 struct wps_state *state = gwps->state;
789 if (!data || !state)
790 return NULL;
792 struct mp3entry *id3;
794 if (token->next)
795 id3 = state->nid3;
796 else
797 id3 = state->id3;
799 if (!id3)
800 return NULL;
802 #if CONFIG_RTC
803 struct tm* tm = NULL;
805 /* if the token is an RTC one, update the time
806 and do the necessary checks */
807 if (token->type >= WPS_TOKENS_RTC_BEGIN
808 && token->type <= WPS_TOKENS_RTC_END)
810 tm = get_time();
812 if (!valid_time(tm))
813 return NULL;
815 #endif
817 int limit = 1;
818 if (intval)
820 limit = *intval;
821 *intval = -1;
824 switch (token->type)
826 case WPS_TOKEN_CHARACTER:
827 return &(token->value.c);
829 case WPS_TOKEN_STRING:
830 return data->strings[token->value.i];
832 case WPS_TOKEN_TRACK_TIME_ELAPSED:
833 format_time(buf, buf_size,
834 id3->elapsed + state->ff_rewind_count);
835 return buf;
837 case WPS_TOKEN_TRACK_TIME_REMAINING:
838 format_time(buf, buf_size,
839 id3->length - id3->elapsed -
840 state->ff_rewind_count);
841 return buf;
843 case WPS_TOKEN_TRACK_LENGTH:
844 format_time(buf, buf_size, id3->length);
845 return buf;
847 case WPS_TOKEN_PLAYLIST_ENTRIES:
848 snprintf(buf, buf_size, "%d", playlist_amount());
849 return buf;
851 case WPS_TOKEN_PLAYLIST_NAME:
852 return playlist_name(NULL, buf, buf_size);
854 case WPS_TOKEN_PLAYLIST_POSITION:
855 snprintf(buf, buf_size, "%d", playlist_get_display_index());
856 return buf;
858 case WPS_TOKEN_PLAYLIST_SHUFFLE:
859 if ( global_settings.playlist_shuffle )
860 return "s";
861 else
862 return NULL;
863 break;
865 case WPS_TOKEN_VOLUME:
866 snprintf(buf, buf_size, "%d", global_settings.volume);
867 if (intval)
869 if (global_settings.volume == sound_min(SOUND_VOLUME))
871 *intval = 1;
873 else if (global_settings.volume == 0)
875 *intval = limit - 1;
877 else if (global_settings.volume > 0)
879 *intval = limit;
881 else
883 *intval = (limit - 3) * (global_settings.volume
884 - sound_min(SOUND_VOLUME) - 1)
885 / (-1 - sound_min(SOUND_VOLUME)) + 2;
888 return buf;
890 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
891 if (id3->length <= 0)
892 return NULL;
894 if (intval)
896 *intval = limit * (id3->elapsed + state->ff_rewind_count)
897 / id3->length + 1;
899 snprintf(buf, buf_size, "%d",
900 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
901 return buf;
903 case WPS_TOKEN_METADATA_ARTIST:
904 return id3->artist;
906 case WPS_TOKEN_METADATA_COMPOSER:
907 return id3->composer;
909 case WPS_TOKEN_METADATA_ALBUM:
910 return id3->album;
912 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
913 return id3->albumartist;
915 case WPS_TOKEN_METADATA_GROUPING:
916 return id3->grouping;
918 case WPS_TOKEN_METADATA_GENRE:
919 return id3->genre_string;
921 case WPS_TOKEN_METADATA_DISC_NUMBER:
922 if (id3->disc_string)
923 return id3->disc_string;
924 if (id3->discnum) {
925 snprintf(buf, buf_size, "%d", id3->discnum);
926 return buf;
928 return NULL;
930 case WPS_TOKEN_METADATA_TRACK_NUMBER:
931 if (id3->track_string)
932 return id3->track_string;
934 if (id3->tracknum) {
935 snprintf(buf, buf_size, "%d", id3->tracknum);
936 return buf;
938 return NULL;
940 case WPS_TOKEN_METADATA_TRACK_TITLE:
941 return id3->title;
943 case WPS_TOKEN_METADATA_VERSION:
944 switch (id3->id3version)
946 case ID3_VER_1_0:
947 return "1";
949 case ID3_VER_1_1:
950 return "1.1";
952 case ID3_VER_2_2:
953 return "2.2";
955 case ID3_VER_2_3:
956 return "2.3";
958 case ID3_VER_2_4:
959 return "2.4";
961 default:
962 return NULL;
965 case WPS_TOKEN_METADATA_YEAR:
966 if( id3->year_string )
967 return id3->year_string;
969 if (id3->year) {
970 snprintf(buf, buf_size, "%d", id3->year);
971 return buf;
973 return NULL;
975 case WPS_TOKEN_METADATA_COMMENT:
976 return id3->comment;
978 #ifdef HAVE_ALBUMART
979 case WPS_TOKEN_ALBUMART_DISPLAY:
980 draw_album_art(gwps, audio_current_aa_hid(), false);
981 return NULL;
983 case WPS_TOKEN_ALBUMART_FOUND:
984 if (audio_current_aa_hid() >= 0) {
985 snprintf(buf, buf_size, "C");
986 return buf;
988 return NULL;
989 #endif
991 case WPS_TOKEN_FILE_BITRATE:
992 if(id3->bitrate)
993 snprintf(buf, buf_size, "%d", id3->bitrate);
994 else
995 snprintf(buf, buf_size, "?");
996 return buf;
998 case WPS_TOKEN_FILE_CODEC:
999 if (intval)
1001 if(id3->codectype == AFMT_UNKNOWN)
1002 *intval = AFMT_NUM_CODECS;
1003 else
1004 *intval = id3->codectype;
1006 return get_codectype(id3);
1008 case WPS_TOKEN_FILE_FREQUENCY:
1009 snprintf(buf, buf_size, "%ld", id3->frequency);
1010 return buf;
1012 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
1013 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1014 if ((id3->frequency % 1000) < 100)
1015 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1016 else
1017 snprintf(buf, buf_size, "%ld.%d",
1018 id3->frequency / 1000,
1019 (id3->frequency % 1000) / 100);
1020 return buf;
1022 case WPS_TOKEN_FILE_NAME:
1023 if (get_dir(buf, buf_size, id3->path, 0)) {
1024 /* Remove extension */
1025 char* sep = strrchr(buf, '.');
1026 if (NULL != sep) {
1027 *sep = 0;
1029 return buf;
1031 else {
1032 return NULL;
1035 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1036 return get_dir(buf, buf_size, id3->path, 0);
1038 case WPS_TOKEN_FILE_PATH:
1039 return id3->path;
1041 case WPS_TOKEN_FILE_SIZE:
1042 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1043 return buf;
1045 case WPS_TOKEN_FILE_VBR:
1046 return id3->vbr ? "(avg)" : NULL;
1048 case WPS_TOKEN_FILE_DIRECTORY:
1049 return get_dir(buf, buf_size, id3->path, token->value.i);
1051 case WPS_TOKEN_BATTERY_PERCENT:
1053 int l = battery_level();
1055 if (intval)
1057 limit = MAX(limit, 2);
1058 if (l > -1) {
1059 /* First enum is used for "unknown level". */
1060 *intval = (limit - 1) * l / 100 + 2;
1061 } else {
1062 *intval = 1;
1066 if (l > -1) {
1067 snprintf(buf, buf_size, "%d", l);
1068 return buf;
1069 } else {
1070 return "?";
1074 case WPS_TOKEN_BATTERY_VOLTS:
1076 unsigned int v = battery_voltage();
1077 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1078 return buf;
1081 case WPS_TOKEN_BATTERY_TIME:
1083 int t = battery_time();
1084 if (t >= 0)
1085 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1086 else
1087 strncpy(buf, "?h ?m", buf_size);
1088 return buf;
1091 #if CONFIG_CHARGING
1092 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1094 if(charger_input_state==CHARGER)
1095 return "p";
1096 else
1097 return NULL;
1099 #endif
1100 #if CONFIG_CHARGING >= CHARGING_MONITOR
1101 case WPS_TOKEN_BATTERY_CHARGING:
1103 if (charge_state == CHARGING || charge_state == TOPOFF) {
1104 return "c";
1105 } else {
1106 return NULL;
1109 #endif
1110 case WPS_TOKEN_BATTERY_SLEEPTIME:
1112 if (get_sleep_timer() == 0)
1113 return NULL;
1114 else
1116 format_time(buf, buf_size, get_sleep_timer() * 1000);
1117 return buf;
1121 case WPS_TOKEN_PLAYBACK_STATUS:
1123 int status = audio_status();
1124 int mode = 1;
1125 if (status == AUDIO_STATUS_PLAY)
1126 mode = 2;
1127 if (wps_fading_out ||
1128 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1129 mode = 3;
1130 if (status_get_ffmode() == STATUS_FASTFORWARD)
1131 mode = 4;
1132 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1133 mode = 5;
1135 if (intval) {
1136 *intval = mode;
1139 snprintf(buf, buf_size, "%d", mode-1);
1140 return buf;
1143 case WPS_TOKEN_REPEAT_MODE:
1144 if (intval)
1145 *intval = global_settings.repeat_mode + 1;
1146 snprintf(buf, buf_size, "%d", *intval);
1147 return buf;
1148 case WPS_TOKEN_RTC_12HOUR_CFG:
1149 if (intval)
1150 *intval = global_settings.timeformat + 1;
1151 snprintf(buf, buf_size, "%d", *intval);
1152 return buf;
1153 #if CONFIG_RTC
1154 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1155 /* d: day of month (01..31) */
1156 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1157 return buf;
1159 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1160 /* e: day of month, blank padded ( 1..31) */
1161 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1162 return buf;
1164 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1165 /* H: hour (00..23) */
1166 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1167 return buf;
1169 case WPS_TOKEN_RTC_HOUR_24:
1170 /* k: hour ( 0..23) */
1171 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1172 return buf;
1174 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1175 /* I: hour (01..12) */
1176 snprintf(buf, buf_size, "%02d",
1177 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1178 return buf;
1180 case WPS_TOKEN_RTC_HOUR_12:
1181 /* l: hour ( 1..12) */
1182 snprintf(buf, buf_size, "%2d",
1183 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1184 return buf;
1186 case WPS_TOKEN_RTC_MONTH:
1187 /* m: month (01..12) */
1188 if (intval)
1189 *intval = tm->tm_mon + 1;
1190 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1191 return buf;
1193 case WPS_TOKEN_RTC_MINUTE:
1194 /* M: minute (00..59) */
1195 snprintf(buf, buf_size, "%02d", tm->tm_min);
1196 return buf;
1198 case WPS_TOKEN_RTC_SECOND:
1199 /* S: second (00..59) */
1200 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1201 return buf;
1203 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1204 /* y: last two digits of year (00..99) */
1205 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1206 return buf;
1208 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1209 /* Y: year (1970...) */
1210 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1211 return buf;
1213 case WPS_TOKEN_RTC_AM_PM_UPPER:
1214 /* p: upper case AM or PM indicator */
1215 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1216 return buf;
1218 case WPS_TOKEN_RTC_AM_PM_LOWER:
1219 /* P: lower case am or pm indicator */
1220 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1221 return buf;
1223 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1224 /* a: abbreviated weekday name (Sun..Sat) */
1225 snprintf(buf, buf_size, "%s",str(LANG_WEEKDAY_SUNDAY + tm->tm_wday));
1226 return buf;
1228 case WPS_TOKEN_RTC_MONTH_NAME:
1229 /* b: abbreviated month name (Jan..Dec) */
1230 snprintf(buf, buf_size, "%s",str(LANG_MONTH_JANUARY + tm->tm_mon));
1231 return buf;
1233 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1234 /* u: day of week (1..7); 1 is Monday */
1235 if (intval)
1236 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1237 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1238 return buf;
1240 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1241 /* w: day of week (0..6); 0 is Sunday */
1242 if (intval)
1243 *intval = tm->tm_wday + 1;
1244 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1245 return buf;
1246 #else
1247 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1248 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1249 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1250 case WPS_TOKEN_RTC_HOUR_24:
1251 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1252 case WPS_TOKEN_RTC_HOUR_12:
1253 case WPS_TOKEN_RTC_MONTH:
1254 case WPS_TOKEN_RTC_MINUTE:
1255 case WPS_TOKEN_RTC_SECOND:
1256 case WPS_TOKEN_RTC_AM_PM_UPPER:
1257 case WPS_TOKEN_RTC_AM_PM_LOWER:
1258 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1259 strncpy(buf, "--", buf_size);
1260 return buf;
1261 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1262 strncpy(buf, "----", buf_size);
1263 return buf;
1264 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1265 case WPS_TOKEN_RTC_MONTH_NAME:
1266 strncpy(buf, "---", buf_size);
1267 return buf;
1268 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1269 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1270 strncpy(buf, "-", buf_size);
1271 return buf;
1272 #endif
1274 #ifdef HAVE_LCD_CHARCELLS
1275 case WPS_TOKEN_PROGRESSBAR:
1277 char *end = utf8encode(data->wps_progress_pat[0], buf);
1278 *end = '\0';
1279 return buf;
1282 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1283 if(is_new_player())
1285 /* we need 11 characters (full line) for
1286 progress-bar */
1287 snprintf(buf, buf_size, " ");
1289 else
1291 /* Tell the user if we have an OldPlayer */
1292 snprintf(buf, buf_size, " <Old LCD> ");
1294 return buf;
1295 #endif
1297 #ifdef HAVE_TAGCACHE
1298 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1299 if (intval) {
1300 *intval = id3->playcount + 1;
1302 snprintf(buf, buf_size, "%ld", id3->playcount);
1303 return buf;
1305 case WPS_TOKEN_DATABASE_RATING:
1306 if (intval) {
1307 *intval = id3->rating + 1;
1309 snprintf(buf, buf_size, "%d", id3->rating);
1310 return buf;
1312 case WPS_TOKEN_DATABASE_AUTOSCORE:
1313 if (intval)
1314 *intval = id3->score + 1;
1316 snprintf(buf, buf_size, "%d", id3->score);
1317 return buf;
1318 #endif
1320 #if (CONFIG_CODEC == SWCODEC)
1321 case WPS_TOKEN_CROSSFADE:
1322 if (intval)
1323 *intval = global_settings.crossfade + 1;
1324 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1325 return buf;
1327 case WPS_TOKEN_REPLAYGAIN:
1329 int val;
1331 if (global_settings.replaygain == 0)
1332 val = 1; /* off */
1333 else
1335 int type =
1336 get_replaygain_mode(id3->track_gain_string != NULL,
1337 id3->album_gain_string != NULL);
1338 if (type < 0)
1339 val = 6; /* no tag */
1340 else
1341 val = type + 2;
1343 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1344 val += 2;
1347 if (intval)
1348 *intval = val;
1350 switch (val)
1352 case 1:
1353 case 6:
1354 return "+0.00 dB";
1355 break;
1356 case 2:
1357 case 4:
1358 strncpy(buf, id3->track_gain_string, buf_size);
1359 break;
1360 case 3:
1361 case 5:
1362 strncpy(buf, id3->album_gain_string, buf_size);
1363 break;
1365 return buf;
1367 #endif /* (CONFIG_CODEC == SWCODEC) */
1369 #if (CONFIG_CODEC != MAS3507D)
1370 case WPS_TOKEN_SOUND_PITCH:
1372 int val = sound_get_pitch();
1373 snprintf(buf, buf_size, "%d.%d",
1374 val / 10, val % 10);
1375 return buf;
1377 #endif
1379 case WPS_TOKEN_MAIN_HOLD:
1380 #ifdef HAS_BUTTON_HOLD
1381 if (button_hold())
1382 #else
1383 if (is_keys_locked())
1384 #endif /*hold switch or softlock*/
1385 return "h";
1386 else
1387 return NULL;
1389 #ifdef HAS_REMOTE_BUTTON_HOLD
1390 case WPS_TOKEN_REMOTE_HOLD:
1391 if (remote_button_hold())
1392 return "r";
1393 else
1394 return NULL;
1395 #endif
1397 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1398 case WPS_TOKEN_VLED_HDD:
1399 if(led_read(HZ/2))
1400 return "h";
1401 else
1402 return NULL;
1403 #endif
1404 default:
1405 return NULL;
1409 /* Return the index to the end token for the conditional token at index.
1410 The conditional token can be either a start token or a separator
1411 (i.e. option) token.
1413 static int find_conditional_end(struct wps_data *data, int index)
1415 int ret = index;
1416 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1417 ret = data->tokens[ret].value.i;
1419 /* ret now is the index to the end token for the conditional. */
1420 return ret;
1423 /* Return the index of the appropriate case for the conditional
1424 that starts at cond_index.
1426 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1428 if (!gwps)
1429 return false;
1431 struct wps_data *data = gwps->data;
1433 int i, cond_end;
1434 int cond_index = *token_index;
1435 char result[128], *value;
1436 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1437 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1439 /* treat ?xx<true> constructs as if they had 2 options. */
1440 if (num_options < 2)
1441 num_options = 2;
1443 int intval = num_options;
1444 /* get_token_value needs to know the number of options in the enum */
1445 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1446 result, sizeof(result), &intval);
1448 /* intval is now the number of the enum option we want to read,
1449 starting from 1. If intval is -1, we check if value is empty. */
1450 if (intval == -1)
1451 intval = (value && *value) ? 1 : num_options;
1452 else if (intval > num_options || intval < 1)
1453 intval = num_options;
1455 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1457 /* skip to the right enum case */
1458 int next = cond_index + 2;
1459 for (i = 1; i < intval; i++)
1461 next = data->tokens[next].value.i;
1463 *token_index = next;
1465 if (prev_val == intval)
1467 /* Same conditional case as previously. Return without clearing the
1468 pictures */
1469 return false;
1472 cond_end = find_conditional_end(data, cond_index + 2);
1473 for (i = cond_index + 3; i < cond_end; i++)
1475 #ifdef HAVE_LCD_BITMAP
1476 /* clear all pictures in the conditional and nested ones */
1477 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1478 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1479 #endif
1480 #ifdef HAVE_ALBUMART
1481 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1482 draw_album_art(gwps, audio_current_aa_hid(), true);
1483 #endif
1486 return true;
1489 /* Read a (sub)line to the given alignment format buffer.
1490 linebuf is the buffer where the data is actually stored.
1491 align is the alignment format that'll be used to display the text.
1492 The return value indicates whether the line needs to be updated.
1494 static bool get_line(struct gui_wps *gwps,
1495 int line, int subline,
1496 struct align_pos *align,
1497 char *linebuf,
1498 int linebuf_size)
1500 struct wps_data *data = gwps->data;
1502 char temp_buf[128];
1503 char *buf = linebuf; /* will always point to the writing position */
1504 char *linebuf_end = linebuf + linebuf_size - 1;
1505 int i, last_token_idx;
1506 bool update = false;
1508 /* alignment-related variables */
1509 int cur_align;
1510 char* cur_align_start;
1511 cur_align_start = buf;
1512 cur_align = WPS_ALIGN_LEFT;
1513 align->left = NULL;
1514 align->center = NULL;
1515 align->right = NULL;
1517 /* Process all tokens of the desired subline */
1518 last_token_idx = wps_last_token_index(data, line, subline);
1519 for (i = wps_first_token_index(data, line, subline);
1520 i <= last_token_idx; i++)
1522 switch(data->tokens[i].type)
1524 case WPS_TOKEN_CONDITIONAL:
1525 /* place ourselves in the right conditional case */
1526 update |= evaluate_conditional(gwps, &i);
1527 break;
1529 case WPS_TOKEN_CONDITIONAL_OPTION:
1530 /* we've finished in the curent conditional case,
1531 skip to the end of the conditional structure */
1532 i = find_conditional_end(data, i);
1533 break;
1535 #ifdef HAVE_LCD_BITMAP
1536 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1538 struct gui_img *img = data->img;
1539 int n = data->tokens[i].value.i & 0xFF;
1540 int subimage = data->tokens[i].value.i >> 8;
1542 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1543 img[n].display = subimage;
1544 break;
1546 #endif
1548 case WPS_TOKEN_ALIGN_LEFT:
1549 case WPS_TOKEN_ALIGN_CENTER:
1550 case WPS_TOKEN_ALIGN_RIGHT:
1551 /* remember where the current aligned text started */
1552 switch (cur_align)
1554 case WPS_ALIGN_LEFT:
1555 align->left = cur_align_start;
1556 break;
1558 case WPS_ALIGN_CENTER:
1559 align->center = cur_align_start;
1560 break;
1562 case WPS_ALIGN_RIGHT:
1563 align->right = cur_align_start;
1564 break;
1566 /* start a new alignment */
1567 switch (data->tokens[i].type)
1569 case WPS_TOKEN_ALIGN_LEFT:
1570 cur_align = WPS_ALIGN_LEFT;
1571 break;
1572 case WPS_TOKEN_ALIGN_CENTER:
1573 cur_align = WPS_ALIGN_CENTER;
1574 break;
1575 case WPS_TOKEN_ALIGN_RIGHT:
1576 cur_align = WPS_ALIGN_RIGHT;
1577 break;
1578 default:
1579 break;
1581 *buf++ = 0;
1582 cur_align_start = buf;
1583 break;
1584 case WPS_VIEWPORT_ENABLE:
1586 char label = data->tokens[i].value.i;
1587 int j;
1588 char temp = VP_DRAW_HIDEABLE;
1589 for(j=0;j<data->num_viewports;j++)
1591 temp = VP_DRAW_HIDEABLE;
1592 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1593 (data->viewports[j].label == label))
1595 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1596 temp |= VP_DRAW_WASHIDDEN;
1597 data->viewports[j].hidden_flags = temp;
1601 break;
1602 default:
1604 /* get the value of the tag and copy it to the buffer */
1605 char *value = get_token_value(gwps, &data->tokens[i],
1606 temp_buf, sizeof(temp_buf), NULL);
1607 if (value)
1609 update = true;
1610 while (*value && (buf < linebuf_end))
1611 *buf++ = *value++;
1613 break;
1618 /* close the current alignment */
1619 switch (cur_align)
1621 case WPS_ALIGN_LEFT:
1622 align->left = cur_align_start;
1623 break;
1625 case WPS_ALIGN_CENTER:
1626 align->center = cur_align_start;
1627 break;
1629 case WPS_ALIGN_RIGHT:
1630 align->right = cur_align_start;
1631 break;
1634 return update;
1637 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1639 struct wps_data *data = gwps->data;
1640 int i;
1641 int subline_idx = wps_subline_index(data, line, subline);
1642 int last_token_idx = wps_last_token_index(data, line, subline);
1644 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1646 for (i = wps_first_token_index(data, line, subline);
1647 i <= last_token_idx; i++)
1649 switch(data->tokens[i].type)
1651 case WPS_TOKEN_CONDITIONAL:
1652 /* place ourselves in the right conditional case */
1653 evaluate_conditional(gwps, &i);
1654 break;
1656 case WPS_TOKEN_CONDITIONAL_OPTION:
1657 /* we've finished in the curent conditional case,
1658 skip to the end of the conditional structure */
1659 i = find_conditional_end(data, i);
1660 break;
1662 case WPS_TOKEN_SUBLINE_TIMEOUT:
1663 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1664 break;
1666 default:
1667 break;
1672 /* Calculates which subline should be displayed for the specified line
1673 Returns true iff the subline must be refreshed */
1674 static bool update_curr_subline(struct gui_wps *gwps, int line)
1676 struct wps_data *data = gwps->data;
1678 int search, search_start, num_sublines;
1679 bool reset_subline;
1680 bool new_subline_refresh;
1681 bool only_one_subline;
1683 num_sublines = data->lines[line].num_sublines;
1684 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1685 new_subline_refresh = false;
1686 only_one_subline = false;
1688 /* if time to advance to next sub-line */
1689 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1690 reset_subline)
1692 /* search all sublines until the next subline with time > 0
1693 is found or we get back to the subline we started with */
1694 if (reset_subline)
1695 search_start = 0;
1696 else
1697 search_start = data->lines[line].curr_subline;
1699 for (search = 0; search < num_sublines; search++)
1701 data->lines[line].curr_subline++;
1703 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1704 if (data->lines[line].curr_subline == num_sublines)
1706 if (data->lines[line].curr_subline == 1)
1707 only_one_subline = true;
1708 data->lines[line].curr_subline = 0;
1711 /* if back where we started after search or
1712 only one subline is defined on the line */
1713 if (((search > 0) &&
1714 (data->lines[line].curr_subline == search_start)) ||
1715 only_one_subline)
1717 /* no other subline with a time > 0 exists */
1718 data->lines[line].subline_expire_time = (reset_subline ?
1719 current_tick :
1720 data->lines[line].subline_expire_time) + 100 * HZ;
1721 break;
1723 else
1725 /* get initial time multiplier for this subline */
1726 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1728 int subline_idx = wps_subline_index(data, line,
1729 data->lines[line].curr_subline);
1731 /* only use this subline if subline time > 0 */
1732 if (data->sublines[subline_idx].time_mult > 0)
1734 new_subline_refresh = true;
1735 data->lines[line].subline_expire_time = (reset_subline ?
1736 current_tick : data->lines[line].subline_expire_time) +
1737 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1738 break;
1744 return new_subline_refresh;
1747 /* Display a line appropriately according to its alignment format.
1748 format_align contains the text, separated between left, center and right.
1749 line is the index of the line on the screen.
1750 scroll indicates whether the line is a scrolling one or not.
1752 static void write_line(struct screen *display,
1753 struct align_pos *format_align,
1754 int line,
1755 bool scroll)
1758 int left_width = 0, left_xpos;
1759 int center_width = 0, center_xpos;
1760 int right_width = 0, right_xpos;
1761 int ypos;
1762 int space_width;
1763 int string_height;
1764 int scroll_width;
1766 /* calculate different string sizes and positions */
1767 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1768 if (format_align->left != 0) {
1769 display->getstringsize((unsigned char *)format_align->left,
1770 &left_width, &string_height);
1773 if (format_align->right != 0) {
1774 display->getstringsize((unsigned char *)format_align->right,
1775 &right_width, &string_height);
1778 if (format_align->center != 0) {
1779 display->getstringsize((unsigned char *)format_align->center,
1780 &center_width, &string_height);
1783 left_xpos = 0;
1784 right_xpos = (display->getwidth() - right_width);
1785 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1787 scroll_width = display->getwidth() - left_xpos;
1789 /* Checks for overlapping strings.
1790 If needed the overlapping strings will be merged, separated by a
1791 space */
1793 /* CASE 1: left and centered string overlap */
1794 /* there is a left string, need to merge left and center */
1795 if ((left_width != 0 && center_width != 0) &&
1796 (left_xpos + left_width + space_width > center_xpos)) {
1797 /* replace the former separator '\0' of left and
1798 center string with a space */
1799 *(--format_align->center) = ' ';
1800 /* calculate the new width and position of the merged string */
1801 left_width = left_width + space_width + center_width;
1802 /* there is no centered string anymore */
1803 center_width = 0;
1805 /* there is no left string, move center to left */
1806 if ((left_width == 0 && center_width != 0) &&
1807 (left_xpos + left_width > center_xpos)) {
1808 /* move the center string to the left string */
1809 format_align->left = format_align->center;
1810 /* calculate the new width and position of the string */
1811 left_width = center_width;
1812 /* there is no centered string anymore */
1813 center_width = 0;
1816 /* CASE 2: centered and right string overlap */
1817 /* there is a right string, need to merge center and right */
1818 if ((center_width != 0 && right_width != 0) &&
1819 (center_xpos + center_width + space_width > right_xpos)) {
1820 /* replace the former separator '\0' of center and
1821 right string with a space */
1822 *(--format_align->right) = ' ';
1823 /* move the center string to the right after merge */
1824 format_align->right = format_align->center;
1825 /* calculate the new width and position of the merged string */
1826 right_width = center_width + space_width + right_width;
1827 right_xpos = (display->getwidth() - right_width);
1828 /* there is no centered string anymore */
1829 center_width = 0;
1831 /* there is no right string, move center to right */
1832 if ((center_width != 0 && right_width == 0) &&
1833 (center_xpos + center_width > right_xpos)) {
1834 /* move the center string to the right string */
1835 format_align->right = format_align->center;
1836 /* calculate the new width and position of the string */
1837 right_width = center_width;
1838 right_xpos = (display->getwidth() - right_width);
1839 /* there is no centered string anymore */
1840 center_width = 0;
1843 /* CASE 3: left and right overlap
1844 There is no center string anymore, either there never
1845 was one or it has been merged in case 1 or 2 */
1846 /* there is a left string, need to merge left and right */
1847 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1848 (left_xpos + left_width + space_width > right_xpos)) {
1849 /* replace the former separator '\0' of left and
1850 right string with a space */
1851 *(--format_align->right) = ' ';
1852 /* calculate the new width and position of the string */
1853 left_width = left_width + space_width + right_width;
1854 /* there is no right string anymore */
1855 right_width = 0;
1857 /* there is no left string, move right to left */
1858 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1859 (left_width > right_xpos)) {
1860 /* move the right string to the left string */
1861 format_align->left = format_align->right;
1862 /* calculate the new width and position of the string */
1863 left_width = right_width;
1864 /* there is no right string anymore */
1865 right_width = 0;
1868 ypos = (line * string_height);
1871 if (scroll && ((left_width > scroll_width) ||
1872 (center_width > scroll_width) ||
1873 (right_width > scroll_width)))
1875 display->puts_scroll(0, line,
1876 (unsigned char *)format_align->left);
1878 else
1880 #ifdef HAVE_LCD_BITMAP
1881 /* clear the line first */
1882 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1883 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1884 display->set_drawmode(DRMODE_SOLID);
1885 #endif
1887 /* Nasty hack: we output an empty scrolling string,
1888 which will reset the scroller for that line */
1889 display->puts_scroll(0, line, (unsigned char *)"");
1891 /* print aligned strings */
1892 if (left_width != 0)
1894 display->putsxy(left_xpos, ypos,
1895 (unsigned char *)format_align->left);
1897 if (center_width != 0)
1899 display->putsxy(center_xpos, ypos,
1900 (unsigned char *)format_align->center);
1902 if (right_width != 0)
1904 display->putsxy(right_xpos, ypos,
1905 (unsigned char *)format_align->right);
1910 /* Refresh the WPS according to refresh_mode. */
1911 bool gui_wps_refresh(struct gui_wps *gwps,
1912 int ffwd_offset,
1913 unsigned char refresh_mode)
1915 struct wps_data *data = gwps->data;
1916 struct screen *display = gwps->display;
1917 struct wps_state *state = gwps->state;
1919 if(!gwps || !data || !state || !display)
1920 return false;
1922 int v, line, i, subline_idx;
1923 unsigned char flags;
1924 char linebuf[MAX_PATH];
1925 unsigned char vp_refresh_mode;
1927 struct align_pos align;
1928 align.left = NULL;
1929 align.center = NULL;
1930 align.right = NULL;
1932 bool update_line, new_subline_refresh;
1934 #ifdef HAVE_LCD_BITMAP
1935 gui_wps_statusbar_draw(gwps, true);
1937 /* to find out wether the peak meter is enabled we
1938 assume it wasn't until we find a line that contains
1939 the peak meter. We can't use peak_meter_enabled itself
1940 because that would mean to turn off the meter thread
1941 temporarily. (That shouldn't matter unless yield
1942 or sleep is called but who knows...)
1944 bool enable_pm = false;
1946 #endif
1948 /* reset to first subline if refresh all flag is set */
1949 if (refresh_mode == WPS_REFRESH_ALL)
1951 display->set_viewport(&data->viewports[0].vp);
1952 display->clear_viewport();
1954 for (i = 0; i <= data->num_lines; i++)
1956 data->lines[i].curr_subline = SUBLINE_RESET;
1960 #ifdef HAVE_LCD_CHARCELLS
1961 for (i = 0; i < 8; i++)
1963 if (data->wps_progress_pat[i] == 0)
1964 data->wps_progress_pat[i] = display->get_locked_pattern();
1966 #endif
1968 if (!state->id3)
1970 display->stop_scroll();
1971 return false;
1974 state->ff_rewind_count = ffwd_offset;
1976 /* disable any viewports which are conditionally displayed */
1977 for (v = 0; v < data->num_viewports; v++)
1979 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
1981 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
1982 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1983 else
1984 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
1987 for (v = 0; v < data->num_viewports; v++)
1989 display->set_viewport(&data->viewports[v].vp);
1990 vp_refresh_mode = refresh_mode;
1992 #ifdef HAVE_LCD_BITMAP
1993 /* Set images to not to be displayed */
1994 for (i = 0; i < MAX_IMAGES; i++)
1996 data->img[i].display = -1;
1998 #endif
1999 /* dont redraw the viewport if its disabled */
2000 if ((data->viewports[v].hidden_flags&VP_DRAW_HIDDEN))
2002 if (!(data->viewports[v].hidden_flags&VP_DRAW_WASHIDDEN))
2003 display->scroll_stop(&data->viewports[v].vp);
2004 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2005 continue;
2007 else if (((data->viewports[v].hidden_flags&
2008 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2009 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2011 vp_refresh_mode = WPS_REFRESH_ALL;
2012 data->viewports[v].hidden_flags = VP_DRAW_HIDEABLE;
2014 if (vp_refresh_mode == WPS_REFRESH_ALL)
2016 display->clear_viewport();
2019 for (line = data->viewports[v].first_line;
2020 line <= data->viewports[v].last_line; line++)
2022 memset(linebuf, 0, sizeof(linebuf));
2023 update_line = false;
2025 /* get current subline for the line */
2026 new_subline_refresh = update_curr_subline(gwps, line);
2028 subline_idx = wps_subline_index(data, line,
2029 data->lines[line].curr_subline);
2030 flags = data->sublines[subline_idx].line_type;
2032 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2033 || new_subline_refresh)
2035 /* get_line tells us if we need to update the line */
2036 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2037 &align, linebuf, sizeof(linebuf));
2039 #ifdef HAVE_LCD_BITMAP
2040 /* peakmeter */
2041 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2043 /* the peakmeter should be alone on its line */
2044 update_line = false;
2046 int h = font_get(data->viewports[v].vp.font)->height;
2047 int peak_meter_y = (line - data->viewports[v].first_line)* h;
2049 /* The user might decide to have the peak meter in the last
2050 line so that it is only displayed if no status bar is
2051 visible. If so we neither want do draw nor enable the
2052 peak meter. */
2053 if (peak_meter_y + h <= display->getheight()) {
2054 /* found a line with a peak meter -> remember that we must
2055 enable it later */
2056 enable_pm = true;
2057 peak_meter_screen(gwps->display, 0, peak_meter_y,
2058 MIN(h, display->getheight() - peak_meter_y));
2062 #else /* HAVE_LCD_CHARCELL */
2064 /* progressbar */
2065 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2067 if (data->full_line_progressbar)
2068 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2069 else
2070 draw_player_progress(gwps);
2072 #endif
2074 if (update_line &&
2075 /* conditionals clear the line which means if the %Vd is put into the default
2076 viewport there will be a blank line.
2077 To get around this we dont allow any actual drawing to happen in the
2078 deault vp if other vp's are defined */
2079 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2081 if (flags & WPS_REFRESH_SCROLL)
2083 /* if the line is a scrolling one we don't want to update
2084 too often, so that it has the time to scroll */
2085 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2086 write_line(display, &align, line - data->viewports[v].first_line, true);
2088 else
2089 write_line(display, &align, line - data->viewports[v].first_line, false);
2093 #ifdef HAVE_LCD_BITMAP
2094 /* progressbar */
2095 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2097 if (data->viewports[v].pb)
2098 draw_progressbar(gwps, data->viewports[v].pb);
2100 /* Now display any images in this viewport */
2101 wps_display_images(gwps, &data->viewports[v].vp);
2102 #endif
2105 #ifdef HAVE_LCD_BITMAP
2106 data->peak_meter_enabled = enable_pm;
2107 #endif
2109 /* Restore the default viewport */
2110 display->set_viewport(NULL);
2112 display->update();
2114 #ifdef HAVE_BACKLIGHT
2115 if (global_settings.caption_backlight && state->id3)
2117 /* turn on backlight n seconds before track ends, and turn it off n
2118 seconds into the new track. n == backlight_timeout, or 5s */
2119 int n = global_settings.backlight_timeout * 1000;
2121 if ( n < 1000 )
2122 n = 5000; /* use 5s if backlight is always on or off */
2124 if (((state->id3->elapsed < 1000) ||
2125 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2126 (state->paused == false))
2127 backlight_on();
2129 #endif
2130 #ifdef HAVE_REMOTE_LCD
2131 if (global_settings.remote_caption_backlight && state->id3)
2133 /* turn on remote backlight n seconds before track ends, and turn it
2134 off n seconds into the new track. n == remote_backlight_timeout,
2135 or 5s */
2136 int n = global_settings.remote_backlight_timeout * 1000;
2138 if ( n < 1000 )
2139 n = 5000; /* use 5s if backlight is always on or off */
2141 if (((state->id3->elapsed < 1000) ||
2142 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2143 (state->paused == false))
2144 remote_backlight_on();
2146 #endif
2148 return true;