fix red
[kugel-rb.git] / apps / gui / gwps-common.c
blobdbe1a457dd8d7fe51115abcab787a4ffff9a955a
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 "settings_list.h"
30 #include "rbunicode.h"
31 #include "rtc.h"
32 #include "audio.h"
33 #include "status.h"
34 #include "power.h"
35 #include "powermgmt.h"
36 #include "sound.h"
37 #include "debug.h"
38 #ifdef HAVE_LCD_CHARCELLS
39 #include "hwcompat.h"
40 #endif
41 #include "abrepeat.h"
42 #include "mp3_playback.h"
43 #include "backlight.h"
44 #include "lang.h"
45 #include "misc.h"
46 #include "splash.h"
47 #include "scrollbar.h"
48 #include "led.h"
49 #include "lcd.h"
50 #ifdef HAVE_LCD_BITMAP
51 #include "peakmeter.h"
52 /* Image stuff */
53 #include "bmp.h"
54 #include "albumart.h"
55 #endif
56 #include "dsp.h"
57 #include "action.h"
58 #include "cuesheet.h"
59 #include "playlist.h"
60 #if CONFIG_CODEC == SWCODEC
61 #include "playback.h"
62 #endif
63 #include "backdrop.h"
64 #include "viewport.h"
65 #include "pcmbuf.h"
67 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
68 /* 3% of 30min file == 54s step size */
69 #define MIN_FF_REWIND_STEP 500
71 /* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds
72 (possibly with a decimal fraction) but stored as integer values.
73 E.g. 2.5 is stored as 25. This means 25 tenth of a second, i.e. 25 units.
75 #define TIMEOUT_UNIT (HZ/10) /* I.e. 0.1 sec */
76 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* In TIMEOUT_UNIT's */
79 /* fades the volume */
80 bool wps_fading_out = false;
81 void fade(bool fade_in, bool updatewps)
83 int fp_global_vol = global_settings.volume << 8;
84 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
85 int fp_step = (fp_global_vol - fp_min_vol) / 30;
86 int i;
87 wps_fading_out = !fade_in;
88 if (fade_in) {
89 /* fade in */
90 int fp_volume = fp_min_vol;
92 /* zero out the sound */
93 sound_set_volume(fp_min_vol >> 8);
95 sleep(HZ/10); /* let audio thread run */
96 audio_resume();
98 while (fp_volume < fp_global_vol - fp_step) {
99 fp_volume += fp_step;
100 sound_set_volume(fp_volume >> 8);
101 if (updatewps)
103 FOR_NB_SCREENS(i)
104 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
106 sleep(1);
108 sound_set_volume(global_settings.volume);
110 else {
111 /* fade out */
112 int fp_volume = fp_global_vol;
114 while (fp_volume > fp_min_vol + fp_step) {
115 fp_volume -= fp_step;
116 sound_set_volume(fp_volume >> 8);
117 if (updatewps)
119 FOR_NB_SCREENS(i)
120 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
122 sleep(1);
124 audio_pause();
125 wps_fading_out = false;
126 #if CONFIG_CODEC != SWCODEC
127 #ifndef SIMULATOR
128 /* let audio thread run and wait for the mas to run out of data */
129 while (!mp3_pause_done())
130 #endif
131 sleep(HZ/10);
132 #endif
134 /* reset volume to what it was before the fade */
135 sound_set_volume(global_settings.volume);
139 /* return true if screen restore is needed
140 return false otherwise
142 bool update_onvol_change(struct gui_wps * gwps)
144 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
146 #ifdef HAVE_LCD_CHARCELLS
147 splashf(0, "Vol: %3d dB",
148 sound_val2phys(SOUND_VOLUME, global_settings.volume));
149 return true;
150 #endif
151 return false;
154 void play_hop(int direction)
156 if(!wps_state.id3 || !wps_state.id3->length
157 || global_settings.skip_length == 0)
158 return;
159 #define STEP ((unsigned)global_settings.skip_length*1000)
160 if(direction == 1
161 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
162 #if CONFIG_CODEC == SWCODEC
163 if(global_settings.beep)
164 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
165 #endif
166 return;
168 if((direction == -1 && wps_state.id3->elapsed < STEP))
169 wps_state.id3->elapsed = 0;
170 else
171 wps_state.id3->elapsed += STEP *direction;
172 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
173 #if (CONFIG_CODEC == SWCODEC)
174 audio_pre_ff_rewind();
175 #else
176 audio_pause();
177 #endif
179 audio_ff_rewind(wps_state.id3->elapsed);
180 #if (CONFIG_CODEC != SWCODEC)
181 if (!wps_state.paused)
182 audio_resume();
183 #endif
184 #undef STEP
187 bool ffwd_rew(int button)
189 unsigned int step = 0; /* current ff/rewind step */
190 unsigned int max_step = 0; /* maximum ff/rewind step */
191 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
192 int direction = -1; /* forward=1 or backward=-1 */
193 bool exit = false;
194 bool usb = false;
195 int i = 0;
196 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
198 if (button == ACTION_NONE)
200 status_set_ffmode(0);
201 return usb;
203 while (!exit)
205 switch ( button )
207 case ACTION_WPS_SEEKFWD:
208 direction = 1;
209 case ACTION_WPS_SEEKBACK:
210 if (wps_state.ff_rewind)
212 if (direction == 1)
214 /* fast forwarding, calc max step relative to end */
215 max_step = (wps_state.id3->length -
216 (wps_state.id3->elapsed +
217 ff_rewind_count)) *
218 FF_REWIND_MAX_PERCENT / 100;
220 else
222 /* rewinding, calc max step relative to start */
223 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
224 FF_REWIND_MAX_PERCENT / 100;
227 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
229 if (step > max_step)
230 step = max_step;
232 ff_rewind_count += step * direction;
234 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
235 step += step >> ff_rw_accel;
237 else
239 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
240 wps_state.id3 && wps_state.id3->length )
242 if (!wps_state.paused)
243 #if (CONFIG_CODEC == SWCODEC)
244 audio_pre_ff_rewind();
245 #else
246 audio_pause();
247 #endif
248 #if CONFIG_KEYPAD == PLAYER_PAD
249 FOR_NB_SCREENS(i)
250 gui_wps[i].display->stop_scroll();
251 #endif
252 if (direction > 0)
253 status_set_ffmode(STATUS_FASTFORWARD);
254 else
255 status_set_ffmode(STATUS_FASTBACKWARD);
257 wps_state.ff_rewind = true;
259 step = 1000 * global_settings.ff_rewind_min_step;
261 else
262 break;
265 if (direction > 0) {
266 if ((wps_state.id3->elapsed + ff_rewind_count) >
267 wps_state.id3->length)
268 ff_rewind_count = wps_state.id3->length -
269 wps_state.id3->elapsed;
271 else {
272 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
273 ff_rewind_count = -wps_state.id3->elapsed;
276 FOR_NB_SCREENS(i)
277 gui_wps_refresh(&gui_wps[i],
278 (wps_state.wps_time_countup == false)?
279 ff_rewind_count:-ff_rewind_count,
280 WPS_REFRESH_PLAYER_PROGRESS |
281 WPS_REFRESH_DYNAMIC);
283 break;
285 case ACTION_WPS_STOPSEEK:
286 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
287 audio_ff_rewind(wps_state.id3->elapsed);
288 ff_rewind_count = 0;
289 wps_state.ff_rewind = false;
290 status_set_ffmode(0);
291 #if (CONFIG_CODEC != SWCODEC)
292 if (!wps_state.paused)
293 audio_resume();
294 #endif
295 #ifdef HAVE_LCD_CHARCELLS
296 gui_wps_display();
297 #endif
298 exit = true;
299 break;
301 default:
302 if(default_event_handler(button) == SYS_USB_CONNECTED) {
303 status_set_ffmode(0);
304 usb = true;
305 exit = true;
307 break;
309 if (!exit)
310 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
312 return usb;
315 bool gui_wps_display(void)
317 int i;
318 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
320 global_status.resume_index = -1;
321 splash(HZ, ID2P(LANG_END_PLAYLIST));
322 return true;
324 else
326 FOR_NB_SCREENS(i)
328 /* Update the values in the first (default) viewport - in case the user
329 has modified the statusbar or colour settings */
330 #ifdef HAVE_LCD_BITMAP
331 #if LCD_DEPTH > 1
332 if (gui_wps[i].display->depth > 1)
334 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
335 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
337 #endif
338 #endif
339 gui_wps[i].display->clear_display();
340 if (!gui_wps[i].data->wps_loaded) {
341 if ( !gui_wps[i].data->num_tokens ) {
342 /* set the default wps for the main-screen */
343 if(i == 0)
345 #ifdef HAVE_LCD_BITMAP
346 #if LCD_DEPTH > 1
347 unload_wps_backdrop();
348 #endif
349 wps_data_load(gui_wps[i].data,
350 gui_wps[i].display,
351 "%s%?it<%?in<%in. |>%it|%fn>\n"
352 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
353 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
354 "\n"
355 "%al%pc/%pt%ar[%pp:%pe]\n"
356 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
357 "%pb\n"
358 "%pm\n", false);
359 #else
360 wps_data_load(gui_wps[i].data,
361 gui_wps[i].display,
362 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
363 "%pc%?ps<*|/>%pt\n", false);
364 #endif
366 #if NB_SCREENS == 2
367 /* set the default wps for the remote-screen */
368 else if(i == 1)
370 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
371 unload_remote_wps_backdrop();
372 #endif
373 wps_data_load(gui_wps[i].data,
374 gui_wps[i].display,
375 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
376 "%s%?it<%?in<%in. |>%it|%fn>\n"
377 "%al%pc/%pt%ar[%pp:%pe]\n"
378 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
379 "%pb\n", false);
381 #endif
386 yield();
387 FOR_NB_SCREENS(i)
389 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
390 #ifdef HAVE_LCD_BITMAP
391 /* temporary work around so the statusbar doesnt disappear when the WPS
392 * is first entered. This should be removed when the
393 * WPS-statusbar handling is fixed up a bit more */
394 bool draw = global_settings.statusbar;
395 if (gui_wps[i].data->wps_sb_tag)
396 draw = gui_wps[i].data->show_sb_on_wps;
397 if (draw)
398 gui_statusbar_draw(&statusbars.statusbars[i], true);
399 #endif
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 */
419 /* We need to parse the new cuesheet */
421 char cuepath[MAX_PATH];
423 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
424 parse_cuesheet(cuepath, curr_cue))
426 gwps->state->id3->cuesheet_type = 1;
427 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
430 cue_spoof_id3(curr_cue, gwps->state->id3);
433 if (gui_wps_display())
434 retcode = true;
435 else{
436 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
440 if (gwps->state->id3)
442 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
443 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
444 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
445 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
447 /* We've changed tracks within the cuesheet :
448 we need to update the ID3 info and refresh the WPS */
450 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
451 cue_spoof_id3(curr_cue, gwps->state->id3);
453 gwps->display->stop_scroll();
454 if (gui_wps_display())
455 retcode = true;
456 else
457 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
459 else
460 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
463 return retcode;
467 void display_keylock_text(bool locked)
469 int i;
470 FOR_NB_SCREENS(i)
471 gui_wps[i].display->stop_scroll();
473 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
476 #ifdef HAVE_LCD_BITMAP
478 static void draw_progressbar(struct gui_wps *gwps,
479 struct progressbar *pb)
481 struct screen *display = gwps->display;
482 struct wps_state *state = gwps->state;
483 if (pb->have_bitmap_pb)
484 gui_bitmap_scrollbar_draw(display, pb->bm,
485 pb->x, pb->y, pb->width, pb->bm.height,
486 state->id3->length ? state->id3->length : 1, 0,
487 state->id3->length ? state->id3->elapsed
488 + state->ff_rewind_count : 0,
489 HORIZONTAL);
490 else
491 gui_scrollbar_draw(display, pb->x, pb->y, pb->width, pb->height,
492 state->id3->length ? state->id3->length : 1, 0,
493 state->id3->length ? state->id3->elapsed
494 + state->ff_rewind_count : 0,
495 HORIZONTAL);
496 #ifdef AB_REPEAT_ENABLE
497 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
498 ab_draw_markers(display, state->id3->length,
499 pb->x, pb->x + pb->width, pb->y, pb->height);
500 #endif
502 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
503 cue_draw_markers(display, state->id3->length,
504 pb->x, pb->x + pb->width, pb->y+1, pb->height-2);
507 /* clears the area where the image was shown */
508 static void clear_image_pos(struct gui_wps *gwps, int n)
510 if(!gwps)
511 return;
512 struct wps_data *data = gwps->data;
513 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
514 gwps->display->fillrect(data->img[n].x, data->img[n].y,
515 data->img[n].bm.width, data->img[n].subimage_height);
516 gwps->display->set_drawmode(DRMODE_SOLID);
519 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
521 struct screen *display = gwps->display;
522 struct wps_data *data = gwps->data;
523 if(data->img[n].always_display)
524 display->set_drawmode(DRMODE_FG);
525 else
526 display->set_drawmode(DRMODE_SOLID);
528 #if LCD_DEPTH > 1
529 if(data->img[n].bm.format == FORMAT_MONO) {
530 #endif
531 display->mono_bitmap_part(data->img[n].bm.data,
532 0, data->img[n].subimage_height * subimage,
533 data->img[n].bm.width, data->img[n].x,
534 data->img[n].y, data->img[n].bm.width,
535 data->img[n].subimage_height);
536 #if LCD_DEPTH > 1
537 } else {
538 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
539 0, data->img[n].subimage_height * subimage,
540 data->img[n].bm.width, data->img[n].x,
541 data->img[n].y, data->img[n].bm.width,
542 data->img[n].subimage_height);
544 #endif
547 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
549 if(!gwps || !gwps->data || !gwps->display)
550 return;
552 int n;
553 struct wps_data *data = gwps->data;
554 struct screen *display = gwps->display;
556 for (n = 0; n < MAX_IMAGES; n++)
558 if (data->img[n].loaded)
560 if (data->img[n].display >= 0)
562 wps_draw_image(gwps, n, data->img[n].display);
563 } else if (data->img[n].always_display && data->img[n].vp == vp)
565 wps_draw_image(gwps, n, 0);
569 display->set_drawmode(DRMODE_SOLID);
572 #else /* HAVE_LCD_CHARCELL */
574 static bool draw_player_progress(struct gui_wps *gwps)
576 struct wps_state *state = gwps->state;
577 struct screen *display = gwps->display;
578 unsigned char progress_pattern[7];
579 int pos = 0;
580 int i;
582 if (!state->id3)
583 return false;
585 if (state->id3->length)
586 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
587 / state->id3->length;
589 for (i = 0; i < 7; i++, pos -= 5)
591 if (pos <= 0)
592 progress_pattern[i] = 0x1fu;
593 else if (pos >= 5)
594 progress_pattern[i] = 0x00u;
595 else
596 progress_pattern[i] = 0x1fu >> pos;
599 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
600 return true;
603 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
605 static const unsigned char numbers[10][4] = {
606 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
607 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
608 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
609 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
610 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
611 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
612 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
613 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
614 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
615 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
618 struct wps_state *state = gwps->state;
619 struct screen *display = gwps->display;
620 struct wps_data *data = gwps->data;
621 unsigned char progress_pattern[7];
622 char timestr[10];
623 int time;
624 int time_idx = 0;
625 int pos = 0;
626 int pat_idx = 1;
627 int digit, i, j;
628 bool softchar;
630 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
631 return;
633 time = state->id3->elapsed + state->ff_rewind_count;
634 if (state->id3->length)
635 pos = 55 * time / state->id3->length;
637 memset(timestr, 0, sizeof(timestr));
638 format_time(timestr, sizeof(timestr)-2, time);
639 timestr[strlen(timestr)] = ':'; /* always safe */
641 for (i = 0; i < 11; i++, pos -= 5)
643 softchar = false;
644 memset(progress_pattern, 0, sizeof(progress_pattern));
646 if ((digit = timestr[time_idx]))
648 softchar = true;
649 digit -= '0';
651 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
653 memcpy(progress_pattern, numbers[digit], 4);
654 time_idx += 2;
656 else /* tens, shifted right */
658 for (j = 0; j < 4; j++)
659 progress_pattern[j] = numbers[digit][j] >> 1;
661 if (time_idx > 0) /* not the first group, add colon in front */
663 progress_pattern[1] |= 0x10u;
664 progress_pattern[3] |= 0x10u;
666 time_idx++;
669 if (pos >= 5)
670 progress_pattern[5] = progress_pattern[6] = 0x1fu;
673 if (pos > 0 && pos < 5)
675 softchar = true;
676 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
679 if (softchar && pat_idx < 8)
681 display->define_pattern(data->wps_progress_pat[pat_idx],
682 progress_pattern);
683 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
684 pat_idx++;
686 else if (pos <= 0)
687 buf = utf8encode(' ', buf);
688 else
689 buf = utf8encode(0xe115, buf); /* 2/7 _ */
691 *buf = '\0';
694 #endif /* HAVE_LCD_CHARCELL */
696 static char* get_codectype(const struct mp3entry* id3)
698 if (id3->codectype < AFMT_NUM_CODECS) {
699 return (char*)audio_formats[id3->codectype].label;
700 } else {
701 return NULL;
705 /* Extract a part from a path.
707 * buf - buffer extract part to.
708 * buf_size - size of buffer.
709 * path - path to extract from.
710 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
711 * parent of parent, etc.
713 * Returns buf if the desired level was found, NULL otherwise.
715 static char* get_dir(char* buf, int buf_size, const char* path, int level)
717 const char* sep;
718 const char* last_sep;
719 int len;
721 sep = path + strlen(path);
722 last_sep = sep;
724 while (sep > path)
726 if ('/' == *(--sep))
728 if (!level)
729 break;
731 level--;
732 last_sep = sep - 1;
736 if (level || (last_sep <= sep))
737 return NULL;
739 len = MIN(last_sep - sep, buf_size - 1);
740 strncpy(buf, sep + 1, len);
741 buf[len] = 0;
742 return buf;
745 /* Return the tag found at index i and write its value in buf.
746 The return value is buf if the tag had a value, or NULL if not.
748 intval is used with conditionals/enums: when this function is called,
749 intval should contain the number of options in the conditional/enum.
750 When this function returns, intval is -1 if the tag is non numeric or,
751 if the tag is numeric, *intval is the enum case we want to go to (between 1
752 and the original value of *intval, inclusive).
753 When not treating a conditional/enum, intval should be NULL.
755 static const char *get_token_value(struct gui_wps *gwps,
756 struct wps_token *token,
757 char *buf, int buf_size,
758 int *intval)
760 if (!gwps)
761 return NULL;
763 struct wps_data *data = gwps->data;
764 struct wps_state *state = gwps->state;
766 if (!data || !state)
767 return NULL;
769 struct mp3entry *id3;
771 if (token->next)
772 id3 = state->nid3;
773 else
774 id3 = state->id3;
776 if (!id3)
777 return NULL;
779 #if CONFIG_RTC
780 struct tm* tm = NULL;
782 /* if the token is an RTC one, update the time
783 and do the necessary checks */
784 if (token->type >= WPS_TOKENS_RTC_BEGIN
785 && token->type <= WPS_TOKENS_RTC_END)
787 tm = get_time();
789 if (!valid_time(tm))
790 return NULL;
792 #endif
794 int limit = 1;
795 if (intval)
797 limit = *intval;
798 *intval = -1;
801 switch (token->type)
803 case WPS_TOKEN_CHARACTER:
804 return &(token->value.c);
806 case WPS_TOKEN_STRING:
807 return data->strings[token->value.i];
809 case WPS_TOKEN_TRACK_TIME_ELAPSED:
810 format_time(buf, buf_size,
811 id3->elapsed + state->ff_rewind_count);
812 return buf;
814 case WPS_TOKEN_TRACK_TIME_REMAINING:
815 format_time(buf, buf_size,
816 id3->length - id3->elapsed -
817 state->ff_rewind_count);
818 return buf;
820 case WPS_TOKEN_TRACK_LENGTH:
821 format_time(buf, buf_size, id3->length);
822 return buf;
824 case WPS_TOKEN_PLAYLIST_ENTRIES:
825 snprintf(buf, buf_size, "%d", playlist_amount());
826 return buf;
828 case WPS_TOKEN_PLAYLIST_NAME:
829 return playlist_name(NULL, buf, buf_size);
831 case WPS_TOKEN_PLAYLIST_POSITION:
832 snprintf(buf, buf_size, "%d", playlist_get_display_index());
833 return buf;
835 case WPS_TOKEN_PLAYLIST_SHUFFLE:
836 if ( global_settings.playlist_shuffle )
837 return "s";
838 else
839 return NULL;
840 break;
842 case WPS_TOKEN_VOLUME:
843 snprintf(buf, buf_size, "%d", global_settings.volume);
844 if (intval)
846 if (global_settings.volume == sound_min(SOUND_VOLUME))
848 *intval = 1;
850 else if (global_settings.volume == 0)
852 *intval = limit - 1;
854 else if (global_settings.volume > 0)
856 *intval = limit;
858 else
860 *intval = (limit - 3) * (global_settings.volume
861 - sound_min(SOUND_VOLUME) - 1)
862 / (-1 - sound_min(SOUND_VOLUME)) + 2;
865 return buf;
867 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
868 if (id3->length <= 0)
869 return NULL;
871 if (intval)
873 *intval = limit * (id3->elapsed + state->ff_rewind_count)
874 / id3->length + 1;
876 snprintf(buf, buf_size, "%d",
877 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
878 return buf;
880 case WPS_TOKEN_METADATA_ARTIST:
881 return id3->artist;
883 case WPS_TOKEN_METADATA_COMPOSER:
884 return id3->composer;
886 case WPS_TOKEN_METADATA_ALBUM:
887 return id3->album;
889 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
890 return id3->albumartist;
892 case WPS_TOKEN_METADATA_GROUPING:
893 return id3->grouping;
895 case WPS_TOKEN_METADATA_GENRE:
896 return id3->genre_string;
898 case WPS_TOKEN_METADATA_DISC_NUMBER:
899 if (id3->disc_string)
900 return id3->disc_string;
901 if (id3->discnum) {
902 snprintf(buf, buf_size, "%d", id3->discnum);
903 return buf;
905 return NULL;
907 case WPS_TOKEN_METADATA_TRACK_NUMBER:
908 if (id3->track_string)
909 return id3->track_string;
911 if (id3->tracknum) {
912 snprintf(buf, buf_size, "%d", id3->tracknum);
913 return buf;
915 return NULL;
917 case WPS_TOKEN_METADATA_TRACK_TITLE:
918 return id3->title;
920 case WPS_TOKEN_METADATA_VERSION:
921 switch (id3->id3version)
923 case ID3_VER_1_0:
924 return "1";
926 case ID3_VER_1_1:
927 return "1.1";
929 case ID3_VER_2_2:
930 return "2.2";
932 case ID3_VER_2_3:
933 return "2.3";
935 case ID3_VER_2_4:
936 return "2.4";
938 default:
939 return NULL;
942 case WPS_TOKEN_METADATA_YEAR:
943 if( id3->year_string )
944 return id3->year_string;
946 if (id3->year) {
947 snprintf(buf, buf_size, "%d", id3->year);
948 return buf;
950 return NULL;
952 case WPS_TOKEN_METADATA_COMMENT:
953 return id3->comment;
955 #ifdef HAVE_ALBUMART
956 case WPS_TOKEN_ALBUMART_DISPLAY:
957 draw_album_art(gwps, audio_current_aa_hid(), false);
958 return NULL;
960 case WPS_TOKEN_ALBUMART_FOUND:
961 if (audio_current_aa_hid() >= 0) {
962 return "C";
964 return NULL;
965 #endif
967 case WPS_TOKEN_FILE_BITRATE:
968 if(id3->bitrate)
969 snprintf(buf, buf_size, "%d", id3->bitrate);
970 else
971 return "?";
972 return buf;
974 case WPS_TOKEN_FILE_CODEC:
975 if (intval)
977 if(id3->codectype == AFMT_UNKNOWN)
978 *intval = AFMT_NUM_CODECS;
979 else
980 *intval = id3->codectype;
982 return get_codectype(id3);
984 case WPS_TOKEN_FILE_FREQUENCY:
985 snprintf(buf, buf_size, "%ld", id3->frequency);
986 return buf;
988 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
989 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
990 if ((id3->frequency % 1000) < 100)
991 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
992 else
993 snprintf(buf, buf_size, "%ld.%d",
994 id3->frequency / 1000,
995 (id3->frequency % 1000) / 100);
996 return buf;
998 case WPS_TOKEN_FILE_NAME:
999 if (get_dir(buf, buf_size, id3->path, 0)) {
1000 /* Remove extension */
1001 char* sep = strrchr(buf, '.');
1002 if (NULL != sep) {
1003 *sep = 0;
1005 return buf;
1007 else {
1008 return NULL;
1011 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1012 return get_dir(buf, buf_size, id3->path, 0);
1014 case WPS_TOKEN_FILE_PATH:
1015 return id3->path;
1017 case WPS_TOKEN_FILE_SIZE:
1018 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1019 return buf;
1021 case WPS_TOKEN_FILE_VBR:
1022 return id3->vbr ? "(avg)" : NULL;
1024 case WPS_TOKEN_FILE_DIRECTORY:
1025 return get_dir(buf, buf_size, id3->path, token->value.i);
1027 case WPS_TOKEN_BATTERY_PERCENT:
1029 int l = battery_level();
1031 if (intval)
1033 limit = MAX(limit, 2);
1034 if (l > -1) {
1035 /* First enum is used for "unknown level". */
1036 *intval = (limit - 1) * l / 100 + 2;
1037 } else {
1038 *intval = 1;
1042 if (l > -1) {
1043 snprintf(buf, buf_size, "%d", l);
1044 return buf;
1045 } else {
1046 return "?";
1050 case WPS_TOKEN_BATTERY_VOLTS:
1052 unsigned int v = battery_voltage();
1053 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1054 return buf;
1057 case WPS_TOKEN_BATTERY_TIME:
1059 int t = battery_time();
1060 if (t >= 0)
1061 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1062 else
1063 return "?h ?m";
1064 return buf;
1067 #if CONFIG_CHARGING
1068 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1070 if(charger_input_state==CHARGER)
1071 return "p";
1072 else
1073 return NULL;
1075 #endif
1076 #if CONFIG_CHARGING >= CHARGING_MONITOR
1077 case WPS_TOKEN_BATTERY_CHARGING:
1079 if (charge_state == CHARGING || charge_state == TOPOFF) {
1080 return "c";
1081 } else {
1082 return NULL;
1085 #endif
1086 case WPS_TOKEN_BATTERY_SLEEPTIME:
1088 if (get_sleep_timer() == 0)
1089 return NULL;
1090 else
1092 format_time(buf, buf_size, get_sleep_timer() * 1000);
1093 return buf;
1097 case WPS_TOKEN_PLAYBACK_STATUS:
1099 int status = audio_status();
1100 int mode = 1;
1101 if (status == AUDIO_STATUS_PLAY)
1102 mode = 2;
1103 if (wps_fading_out ||
1104 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1105 mode = 3;
1106 if (status_get_ffmode() == STATUS_FASTFORWARD)
1107 mode = 4;
1108 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1109 mode = 5;
1111 if (intval) {
1112 *intval = mode;
1115 snprintf(buf, buf_size, "%d", mode-1);
1116 return buf;
1119 case WPS_TOKEN_REPEAT_MODE:
1120 if (intval)
1121 *intval = global_settings.repeat_mode + 1;
1122 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1123 return buf;
1124 #if CONFIG_RTC
1125 case WPS_TOKEN_RTC_12HOUR_CFG:
1126 if (intval)
1127 *intval = global_settings.timeformat + 1;
1128 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1129 return buf;
1131 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1132 /* d: day of month (01..31) */
1133 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1134 return buf;
1136 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1137 /* e: day of month, blank padded ( 1..31) */
1138 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1139 return buf;
1141 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1142 /* H: hour (00..23) */
1143 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1144 return buf;
1146 case WPS_TOKEN_RTC_HOUR_24:
1147 /* k: hour ( 0..23) */
1148 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1149 return buf;
1151 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1152 /* I: hour (01..12) */
1153 snprintf(buf, buf_size, "%02d",
1154 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1155 return buf;
1157 case WPS_TOKEN_RTC_HOUR_12:
1158 /* l: hour ( 1..12) */
1159 snprintf(buf, buf_size, "%2d",
1160 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1161 return buf;
1163 case WPS_TOKEN_RTC_MONTH:
1164 /* m: month (01..12) */
1165 if (intval)
1166 *intval = tm->tm_mon + 1;
1167 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1168 return buf;
1170 case WPS_TOKEN_RTC_MINUTE:
1171 /* M: minute (00..59) */
1172 snprintf(buf, buf_size, "%02d", tm->tm_min);
1173 return buf;
1175 case WPS_TOKEN_RTC_SECOND:
1176 /* S: second (00..59) */
1177 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1178 return buf;
1180 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1181 /* y: last two digits of year (00..99) */
1182 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1183 return buf;
1185 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1186 /* Y: year (1970...) */
1187 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1188 return buf;
1190 case WPS_TOKEN_RTC_AM_PM_UPPER:
1191 /* p: upper case AM or PM indicator */
1192 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1194 case WPS_TOKEN_RTC_AM_PM_LOWER:
1195 /* P: lower case am or pm indicator */
1196 return tm->tm_hour/12 == 0 ? "am" : "pm";
1198 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1199 /* a: abbreviated weekday name (Sun..Sat) */
1200 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1202 case WPS_TOKEN_RTC_MONTH_NAME:
1203 /* b: abbreviated month name (Jan..Dec) */
1204 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1206 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1207 /* u: day of week (1..7); 1 is Monday */
1208 if (intval)
1209 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1210 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1211 return buf;
1213 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1214 /* w: day of week (0..6); 0 is Sunday */
1215 if (intval)
1216 *intval = tm->tm_wday + 1;
1217 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1218 return buf;
1219 #else
1220 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1221 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1222 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1223 case WPS_TOKEN_RTC_HOUR_24:
1224 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1225 case WPS_TOKEN_RTC_HOUR_12:
1226 case WPS_TOKEN_RTC_MONTH:
1227 case WPS_TOKEN_RTC_MINUTE:
1228 case WPS_TOKEN_RTC_SECOND:
1229 case WPS_TOKEN_RTC_AM_PM_UPPER:
1230 case WPS_TOKEN_RTC_AM_PM_LOWER:
1231 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1232 return "--";
1233 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1234 return "----";
1235 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1236 case WPS_TOKEN_RTC_MONTH_NAME:
1237 return "---";
1238 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1239 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1240 return "-";
1241 #endif
1243 #ifdef HAVE_LCD_CHARCELLS
1244 case WPS_TOKEN_PROGRESSBAR:
1246 char *end = utf8encode(data->wps_progress_pat[0], buf);
1247 *end = '\0';
1248 return buf;
1251 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1252 if(is_new_player())
1254 /* we need 11 characters (full line) for
1255 progress-bar */
1256 strncpy(buf, " ", buf_size);
1258 else
1260 /* Tell the user if we have an OldPlayer */
1261 strncpy(buf, " <Old LCD> ", buf_size);
1263 return buf;
1264 #endif
1266 #ifdef HAVE_TAGCACHE
1267 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1268 if (intval) {
1269 *intval = id3->playcount + 1;
1271 snprintf(buf, buf_size, "%ld", id3->playcount);
1272 return buf;
1274 case WPS_TOKEN_DATABASE_RATING:
1275 if (intval) {
1276 *intval = id3->rating + 1;
1278 snprintf(buf, buf_size, "%d", id3->rating);
1279 return buf;
1281 case WPS_TOKEN_DATABASE_AUTOSCORE:
1282 if (intval)
1283 *intval = id3->score + 1;
1285 snprintf(buf, buf_size, "%d", id3->score);
1286 return buf;
1287 #endif
1289 #if (CONFIG_CODEC == SWCODEC)
1290 case WPS_TOKEN_CROSSFADE:
1291 if (intval)
1292 *intval = global_settings.crossfade + 1;
1293 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1294 return buf;
1296 case WPS_TOKEN_REPLAYGAIN:
1298 int val;
1300 if (global_settings.replaygain == 0)
1301 val = 1; /* off */
1302 else
1304 int type =
1305 get_replaygain_mode(id3->track_gain_string != NULL,
1306 id3->album_gain_string != NULL);
1307 if (type < 0)
1308 val = 6; /* no tag */
1309 else
1310 val = type + 2;
1312 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1313 val += 2;
1316 if (intval)
1317 *intval = val;
1319 switch (val)
1321 case 1:
1322 case 6:
1323 return "+0.00 dB";
1324 break;
1325 case 2:
1326 case 4:
1327 strncpy(buf, id3->track_gain_string, buf_size);
1328 break;
1329 case 3:
1330 case 5:
1331 strncpy(buf, id3->album_gain_string, buf_size);
1332 break;
1334 return buf;
1336 #endif /* (CONFIG_CODEC == SWCODEC) */
1338 #if (CONFIG_CODEC != MAS3507D)
1339 case WPS_TOKEN_SOUND_PITCH:
1341 int val = sound_get_pitch();
1342 snprintf(buf, buf_size, "%d.%d",
1343 val / 10, val % 10);
1344 return buf;
1346 #endif
1348 case WPS_TOKEN_MAIN_HOLD:
1349 #ifdef HAS_BUTTON_HOLD
1350 if (button_hold())
1351 #else
1352 if (is_keys_locked())
1353 #endif /*hold switch or softlock*/
1354 return "h";
1355 else
1356 return NULL;
1358 #ifdef HAS_REMOTE_BUTTON_HOLD
1359 case WPS_TOKEN_REMOTE_HOLD:
1360 if (remote_button_hold())
1361 return "r";
1362 else
1363 return NULL;
1364 #endif
1366 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1367 case WPS_TOKEN_VLED_HDD:
1368 if(led_read(HZ/2))
1369 return "h";
1370 else
1371 return NULL;
1372 #endif
1373 case WPS_TOKEN_BUTTON_VOLUME:
1374 if (data->button_time_volume &&
1375 TIME_BEFORE(current_tick, data->button_time_volume +
1376 token->value.i * TIMEOUT_UNIT))
1377 return "v";
1378 return NULL;
1380 case WPS_TOKEN_SETTING:
1382 if (intval)
1384 /* Handle contionals */
1385 const struct settings_list *s = settings+token->value.i;
1386 switch (s->flags&F_T_MASK)
1388 case F_T_INT:
1389 case F_T_UINT:
1390 if (s->flags&F_RGB)
1391 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1392 /* shouldn't overflow since colors are stored
1393 * on 16 bits ...
1394 * but this is pretty useless anyway */
1395 *intval = *(int*)s->setting + 1;
1396 else if (s->cfg_vals == NULL)
1397 /* %?St|name|<1st choice|2nd choice|...> */
1398 *intval = (*(int*)s->setting-s->int_setting->min)
1399 /s->int_setting->step + 1;
1400 else
1401 /* %?St|name|<1st choice|2nd choice|...> */
1402 /* Not sure about this one. cfg_name/vals are
1403 * indexed from 0 right? */
1404 *intval = *(int*)s->setting + 1;
1405 break;
1406 case F_T_BOOL:
1407 /* %?St|name|<if true|if false> */
1408 *intval = *(bool*)s->setting?1:2;
1409 break;
1410 case F_T_CHARPTR:
1411 /* %?St|name|<if non empty string|if empty>
1412 * The string's emptyness discards the setting's
1413 * prefix and suffix */
1414 *intval = ((char*)s->setting)[0]?1:2;
1415 break;
1416 default:
1417 /* This shouldn't happen ... but you never know */
1418 *intval = -1;
1419 break;
1422 cfg_to_string(token->value.i,buf,buf_size);
1423 return buf;
1426 default:
1427 return NULL;
1431 /* Return the index to the end token for the conditional token at index.
1432 The conditional token can be either a start token or a separator
1433 (i.e. option) token.
1435 static int find_conditional_end(struct wps_data *data, int index)
1437 int ret = index;
1438 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1439 ret = data->tokens[ret].value.i;
1441 /* ret now is the index to the end token for the conditional. */
1442 return ret;
1445 /* Evaluate the conditional that is at *token_index and return whether a skip
1446 has ocurred. *token_index is updated with the new position.
1448 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1450 if (!gwps)
1451 return false;
1453 struct wps_data *data = gwps->data;
1455 int i, cond_end;
1456 int cond_index = *token_index;
1457 char result[128];
1458 const char *value;
1459 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1460 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1462 /* treat ?xx<true> constructs as if they had 2 options. */
1463 if (num_options < 2)
1464 num_options = 2;
1466 int intval = num_options;
1467 /* get_token_value needs to know the number of options in the enum */
1468 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1469 result, sizeof(result), &intval);
1471 /* intval is now the number of the enum option we want to read,
1472 starting from 1. If intval is -1, we check if value is empty. */
1473 if (intval == -1)
1474 intval = (value && *value) ? 1 : num_options;
1475 else if (intval > num_options || intval < 1)
1476 intval = num_options;
1478 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1480 /* skip to the appropriate enum case */
1481 int next = cond_index + 2;
1482 for (i = 1; i < intval; i++)
1484 next = data->tokens[next].value.i;
1486 *token_index = next;
1488 if (prev_val == intval)
1490 /* Same conditional case as previously. Return without clearing the
1491 pictures */
1492 return false;
1495 cond_end = find_conditional_end(data, cond_index + 2);
1496 for (i = cond_index + 3; i < cond_end; i++)
1498 #ifdef HAVE_LCD_BITMAP
1499 /* clear all pictures in the conditional and nested ones */
1500 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1501 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1502 #endif
1503 #ifdef HAVE_ALBUMART
1504 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1505 draw_album_art(gwps, audio_current_aa_hid(), true);
1506 #endif
1509 return true;
1512 /* Read a (sub)line to the given alignment format buffer.
1513 linebuf is the buffer where the data is actually stored.
1514 align is the alignment format that'll be used to display the text.
1515 The return value indicates whether the line needs to be updated.
1517 static bool get_line(struct gui_wps *gwps,
1518 int line, int subline,
1519 struct align_pos *align,
1520 char *linebuf,
1521 int linebuf_size)
1523 struct wps_data *data = gwps->data;
1525 char temp_buf[128];
1526 char *buf = linebuf; /* will always point to the writing position */
1527 char *linebuf_end = linebuf + linebuf_size - 1;
1528 int i, last_token_idx;
1529 bool update = false;
1531 /* alignment-related variables */
1532 int cur_align;
1533 char* cur_align_start;
1534 cur_align_start = buf;
1535 cur_align = WPS_ALIGN_LEFT;
1536 align->left = NULL;
1537 align->center = NULL;
1538 align->right = NULL;
1540 /* Process all tokens of the desired subline */
1541 last_token_idx = wps_last_token_index(data, line, subline);
1542 for (i = wps_first_token_index(data, line, subline);
1543 i <= last_token_idx; i++)
1545 switch(data->tokens[i].type)
1547 case WPS_TOKEN_CONDITIONAL:
1548 /* place ourselves in the right conditional case */
1549 update |= evaluate_conditional(gwps, &i);
1550 break;
1552 case WPS_TOKEN_CONDITIONAL_OPTION:
1553 /* we've finished in the curent conditional case,
1554 skip to the end of the conditional structure */
1555 i = find_conditional_end(data, i);
1556 break;
1558 #ifdef HAVE_LCD_BITMAP
1559 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1561 struct gui_img *img = data->img;
1562 int n = data->tokens[i].value.i & 0xFF;
1563 int subimage = data->tokens[i].value.i >> 8;
1565 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1566 img[n].display = subimage;
1567 break;
1569 #endif
1571 case WPS_TOKEN_ALIGN_LEFT:
1572 case WPS_TOKEN_ALIGN_CENTER:
1573 case WPS_TOKEN_ALIGN_RIGHT:
1574 /* remember where the current aligned text started */
1575 switch (cur_align)
1577 case WPS_ALIGN_LEFT:
1578 align->left = cur_align_start;
1579 break;
1581 case WPS_ALIGN_CENTER:
1582 align->center = cur_align_start;
1583 break;
1585 case WPS_ALIGN_RIGHT:
1586 align->right = cur_align_start;
1587 break;
1589 /* start a new alignment */
1590 switch (data->tokens[i].type)
1592 case WPS_TOKEN_ALIGN_LEFT:
1593 cur_align = WPS_ALIGN_LEFT;
1594 break;
1595 case WPS_TOKEN_ALIGN_CENTER:
1596 cur_align = WPS_ALIGN_CENTER;
1597 break;
1598 case WPS_TOKEN_ALIGN_RIGHT:
1599 cur_align = WPS_ALIGN_RIGHT;
1600 break;
1601 default:
1602 break;
1604 *buf++ = 0;
1605 cur_align_start = buf;
1606 break;
1607 case WPS_VIEWPORT_ENABLE:
1609 char label = data->tokens[i].value.i;
1610 int j;
1611 char temp = VP_DRAW_HIDEABLE;
1612 for(j=0;j<data->num_viewports;j++)
1614 temp = VP_DRAW_HIDEABLE;
1615 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1616 (data->viewports[j].label == label))
1618 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1619 temp |= VP_DRAW_WASHIDDEN;
1620 data->viewports[j].hidden_flags = temp;
1624 break;
1625 default:
1627 /* get the value of the tag and copy it to the buffer */
1628 const char *value = get_token_value(gwps, &data->tokens[i],
1629 temp_buf, sizeof(temp_buf), NULL);
1630 if (value)
1632 update = true;
1633 while (*value && (buf < linebuf_end))
1634 *buf++ = *value++;
1636 break;
1641 /* close the current alignment */
1642 switch (cur_align)
1644 case WPS_ALIGN_LEFT:
1645 align->left = cur_align_start;
1646 break;
1648 case WPS_ALIGN_CENTER:
1649 align->center = cur_align_start;
1650 break;
1652 case WPS_ALIGN_RIGHT:
1653 align->right = cur_align_start;
1654 break;
1657 return update;
1660 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1662 struct wps_data *data = gwps->data;
1663 int i;
1664 int subline_idx = wps_subline_index(data, line, subline);
1665 int last_token_idx = wps_last_token_index(data, line, subline);
1667 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1669 for (i = wps_first_token_index(data, line, subline);
1670 i <= last_token_idx; i++)
1672 switch(data->tokens[i].type)
1674 case WPS_TOKEN_CONDITIONAL:
1675 /* place ourselves in the right conditional case */
1676 evaluate_conditional(gwps, &i);
1677 break;
1679 case WPS_TOKEN_CONDITIONAL_OPTION:
1680 /* we've finished in the curent conditional case,
1681 skip to the end of the conditional structure */
1682 i = find_conditional_end(data, i);
1683 break;
1685 case WPS_TOKEN_SUBLINE_TIMEOUT:
1686 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1687 break;
1689 default:
1690 break;
1695 /* Calculates which subline should be displayed for the specified line
1696 Returns true iff the subline must be refreshed */
1697 static bool update_curr_subline(struct gui_wps *gwps, int line)
1699 struct wps_data *data = gwps->data;
1701 int search, search_start, num_sublines;
1702 bool reset_subline;
1703 bool new_subline_refresh;
1704 bool only_one_subline;
1706 num_sublines = data->lines[line].num_sublines;
1707 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1708 new_subline_refresh = false;
1709 only_one_subline = false;
1711 /* if time to advance to next sub-line */
1712 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1713 reset_subline)
1715 /* search all sublines until the next subline with time > 0
1716 is found or we get back to the subline we started with */
1717 if (reset_subline)
1718 search_start = 0;
1719 else
1720 search_start = data->lines[line].curr_subline;
1722 for (search = 0; search < num_sublines; search++)
1724 data->lines[line].curr_subline++;
1726 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1727 if (data->lines[line].curr_subline == num_sublines)
1729 if (data->lines[line].curr_subline == 1)
1730 only_one_subline = true;
1731 data->lines[line].curr_subline = 0;
1734 /* if back where we started after search or
1735 only one subline is defined on the line */
1736 if (((search > 0) &&
1737 (data->lines[line].curr_subline == search_start)) ||
1738 only_one_subline)
1740 /* no other subline with a time > 0 exists */
1741 data->lines[line].subline_expire_time = (reset_subline ?
1742 current_tick :
1743 data->lines[line].subline_expire_time) + 100 * HZ;
1744 break;
1746 else
1748 /* get initial time multiplier for this subline */
1749 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1751 int subline_idx = wps_subline_index(data, line,
1752 data->lines[line].curr_subline);
1754 /* only use this subline if subline time > 0 */
1755 if (data->sublines[subline_idx].time_mult > 0)
1757 new_subline_refresh = true;
1758 data->lines[line].subline_expire_time = (reset_subline ?
1759 current_tick : data->lines[line].subline_expire_time) +
1760 TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
1761 break;
1767 return new_subline_refresh;
1770 /* Display a line appropriately according to its alignment format.
1771 format_align contains the text, separated between left, center and right.
1772 line is the index of the line on the screen.
1773 scroll indicates whether the line is a scrolling one or not.
1775 static void write_line(struct screen *display,
1776 struct align_pos *format_align,
1777 int line,
1778 bool scroll)
1781 int left_width = 0, left_xpos;
1782 int center_width = 0, center_xpos;
1783 int right_width = 0, right_xpos;
1784 int ypos;
1785 int space_width;
1786 int string_height;
1787 int scroll_width;
1789 /* calculate different string sizes and positions */
1790 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1791 if (format_align->left != 0) {
1792 display->getstringsize((unsigned char *)format_align->left,
1793 &left_width, &string_height);
1796 if (format_align->right != 0) {
1797 display->getstringsize((unsigned char *)format_align->right,
1798 &right_width, &string_height);
1801 if (format_align->center != 0) {
1802 display->getstringsize((unsigned char *)format_align->center,
1803 &center_width, &string_height);
1806 left_xpos = 0;
1807 right_xpos = (display->getwidth() - right_width);
1808 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1810 scroll_width = display->getwidth() - left_xpos;
1812 /* Checks for overlapping strings.
1813 If needed the overlapping strings will be merged, separated by a
1814 space */
1816 /* CASE 1: left and centered string overlap */
1817 /* there is a left string, need to merge left and center */
1818 if ((left_width != 0 && center_width != 0) &&
1819 (left_xpos + left_width + space_width > center_xpos)) {
1820 /* replace the former separator '\0' of left and
1821 center string with a space */
1822 *(--format_align->center) = ' ';
1823 /* calculate the new width and position of the merged string */
1824 left_width = left_width + space_width + center_width;
1825 /* there is no centered string anymore */
1826 center_width = 0;
1828 /* there is no left string, move center to left */
1829 if ((left_width == 0 && center_width != 0) &&
1830 (left_xpos + left_width > center_xpos)) {
1831 /* move the center string to the left string */
1832 format_align->left = format_align->center;
1833 /* calculate the new width and position of the string */
1834 left_width = center_width;
1835 /* there is no centered string anymore */
1836 center_width = 0;
1839 /* CASE 2: centered and right string overlap */
1840 /* there is a right string, need to merge center and right */
1841 if ((center_width != 0 && right_width != 0) &&
1842 (center_xpos + center_width + space_width > right_xpos)) {
1843 /* replace the former separator '\0' of center and
1844 right string with a space */
1845 *(--format_align->right) = ' ';
1846 /* move the center string to the right after merge */
1847 format_align->right = format_align->center;
1848 /* calculate the new width and position of the merged string */
1849 right_width = center_width + space_width + right_width;
1850 right_xpos = (display->getwidth() - right_width);
1851 /* there is no centered string anymore */
1852 center_width = 0;
1854 /* there is no right string, move center to right */
1855 if ((center_width != 0 && right_width == 0) &&
1856 (center_xpos + center_width > right_xpos)) {
1857 /* move the center string to the right string */
1858 format_align->right = format_align->center;
1859 /* calculate the new width and position of the string */
1860 right_width = center_width;
1861 right_xpos = (display->getwidth() - right_width);
1862 /* there is no centered string anymore */
1863 center_width = 0;
1866 /* CASE 3: left and right overlap
1867 There is no center string anymore, either there never
1868 was one or it has been merged in case 1 or 2 */
1869 /* there is a left string, need to merge left and right */
1870 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1871 (left_xpos + left_width + space_width > right_xpos)) {
1872 /* replace the former separator '\0' of left and
1873 right string with a space */
1874 *(--format_align->right) = ' ';
1875 /* calculate the new width and position of the string */
1876 left_width = left_width + space_width + right_width;
1877 /* there is no right string anymore */
1878 right_width = 0;
1880 /* there is no left string, move right to left */
1881 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1882 (left_width > right_xpos)) {
1883 /* move the right string to the left string */
1884 format_align->left = format_align->right;
1885 /* calculate the new width and position of the string */
1886 left_width = right_width;
1887 /* there is no right string anymore */
1888 right_width = 0;
1891 ypos = (line * string_height);
1894 if (scroll && ((left_width > scroll_width) ||
1895 (center_width > scroll_width) ||
1896 (right_width > scroll_width)))
1898 display->puts_scroll(0, line,
1899 (unsigned char *)format_align->left);
1901 else
1903 #ifdef HAVE_LCD_BITMAP
1904 /* clear the line first */
1905 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1906 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1907 display->set_drawmode(DRMODE_SOLID);
1908 #endif
1910 /* Nasty hack: we output an empty scrolling string,
1911 which will reset the scroller for that line */
1912 display->puts_scroll(0, line, (unsigned char *)"");
1914 /* print aligned strings */
1915 if (left_width != 0)
1917 display->putsxy(left_xpos, ypos,
1918 (unsigned char *)format_align->left);
1920 if (center_width != 0)
1922 display->putsxy(center_xpos, ypos,
1923 (unsigned char *)format_align->center);
1925 if (right_width != 0)
1927 display->putsxy(right_xpos, ypos,
1928 (unsigned char *)format_align->right);
1933 /* Refresh the WPS according to refresh_mode. */
1934 bool gui_wps_refresh(struct gui_wps *gwps,
1935 int ffwd_offset,
1936 unsigned char refresh_mode)
1938 struct wps_data *data = gwps->data;
1939 struct screen *display = gwps->display;
1940 struct wps_state *state = gwps->state;
1942 if(!gwps || !data || !state || !display)
1943 return false;
1945 int v, line, i, subline_idx;
1946 unsigned char flags;
1947 char linebuf[MAX_PATH];
1948 unsigned char vp_refresh_mode;
1950 struct align_pos align;
1951 align.left = NULL;
1952 align.center = NULL;
1953 align.right = NULL;
1955 bool update_line, new_subline_refresh;
1957 #ifdef HAVE_LCD_BITMAP
1959 /* to find out wether the peak meter is enabled we
1960 assume it wasn't until we find a line that contains
1961 the peak meter. We can't use peak_meter_enabled itself
1962 because that would mean to turn off the meter thread
1963 temporarily. (That shouldn't matter unless yield
1964 or sleep is called but who knows...)
1966 bool enable_pm = false;
1968 #endif
1970 /* reset to first subline if refresh all flag is set */
1971 if (refresh_mode == WPS_REFRESH_ALL)
1973 display->set_viewport(&data->viewports[0].vp);
1974 display->clear_viewport();
1976 for (i = 0; i <= data->num_lines; i++)
1978 data->lines[i].curr_subline = SUBLINE_RESET;
1982 #ifdef HAVE_LCD_CHARCELLS
1983 for (i = 0; i < 8; i++)
1985 if (data->wps_progress_pat[i] == 0)
1986 data->wps_progress_pat[i] = display->get_locked_pattern();
1988 #endif
1990 if (!state->id3)
1992 display->stop_scroll();
1993 return false;
1996 state->ff_rewind_count = ffwd_offset;
1998 /* disable any viewports which are conditionally displayed */
1999 for (v = 0; v < data->num_viewports; v++)
2001 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
2003 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
2004 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2005 else
2006 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
2009 for (v = 0; v < data->num_viewports; v++)
2011 display->set_viewport(&data->viewports[v].vp);
2012 vp_refresh_mode = refresh_mode;
2014 #ifdef HAVE_LCD_BITMAP
2015 /* Set images to not to be displayed */
2016 for (i = 0; i < MAX_IMAGES; i++)
2018 data->img[i].display = -1;
2020 #endif
2021 /* dont redraw the viewport if its disabled */
2022 if ((data->viewports[v].hidden_flags&VP_DRAW_HIDDEN))
2024 if (!(data->viewports[v].hidden_flags&VP_DRAW_WASHIDDEN))
2025 display->scroll_stop(&data->viewports[v].vp);
2026 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2027 continue;
2029 else if (((data->viewports[v].hidden_flags&
2030 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2031 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2033 vp_refresh_mode = WPS_REFRESH_ALL;
2034 data->viewports[v].hidden_flags = VP_DRAW_HIDEABLE;
2036 if (vp_refresh_mode == WPS_REFRESH_ALL)
2038 display->clear_viewport();
2041 for (line = data->viewports[v].first_line;
2042 line <= data->viewports[v].last_line; line++)
2044 memset(linebuf, 0, sizeof(linebuf));
2045 update_line = false;
2047 /* get current subline for the line */
2048 new_subline_refresh = update_curr_subline(gwps, line);
2050 subline_idx = wps_subline_index(data, line,
2051 data->lines[line].curr_subline);
2052 flags = data->sublines[subline_idx].line_type;
2054 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2055 || new_subline_refresh)
2057 /* get_line tells us if we need to update the line */
2058 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2059 &align, linebuf, sizeof(linebuf));
2061 #ifdef HAVE_LCD_BITMAP
2062 /* peakmeter */
2063 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2065 /* the peakmeter should be alone on its line */
2066 update_line = false;
2068 int h = font_get(data->viewports[v].vp.font)->height;
2069 int peak_meter_y = (line - data->viewports[v].first_line)* h;
2071 /* The user might decide to have the peak meter in the last
2072 line so that it is only displayed if no status bar is
2073 visible. If so we neither want do draw nor enable the
2074 peak meter. */
2075 if (peak_meter_y + h <= display->getheight()) {
2076 /* found a line with a peak meter -> remember that we must
2077 enable it later */
2078 enable_pm = true;
2079 peak_meter_enabled = true;
2080 peak_meter_screen(gwps->display, 0, peak_meter_y,
2081 MIN(h, display->getheight() - peak_meter_y));
2083 else
2085 peak_meter_enabled = false;
2089 #else /* HAVE_LCD_CHARCELL */
2091 /* progressbar */
2092 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2094 if (data->full_line_progressbar)
2095 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2096 else
2097 draw_player_progress(gwps);
2099 #endif
2101 if (update_line &&
2102 /* conditionals clear the line which means if the %Vd is put into the default
2103 viewport there will be a blank line.
2104 To get around this we dont allow any actual drawing to happen in the
2105 deault vp if other vp's are defined */
2106 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2108 if (flags & WPS_REFRESH_SCROLL)
2110 /* if the line is a scrolling one we don't want to update
2111 too often, so that it has the time to scroll */
2112 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2113 write_line(display, &align, line - data->viewports[v].first_line, true);
2115 else
2116 write_line(display, &align, line - data->viewports[v].first_line, false);
2120 #ifdef HAVE_LCD_BITMAP
2121 /* progressbar */
2122 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2124 if (data->viewports[v].pb)
2125 draw_progressbar(gwps, data->viewports[v].pb);
2127 /* Now display any images in this viewport */
2128 wps_display_images(gwps, &data->viewports[v].vp);
2129 #endif
2132 #ifdef HAVE_LCD_BITMAP
2133 data->peak_meter_enabled = enable_pm;
2134 #endif
2136 /* Restore the default viewport */
2137 display->set_viewport(NULL);
2139 display->update();
2141 #ifdef HAVE_BACKLIGHT
2142 if (global_settings.caption_backlight && state->id3)
2144 /* turn on backlight n seconds before track ends, and turn it off n
2145 seconds into the new track. n == backlight_timeout, or 5s */
2146 int n = global_settings.backlight_timeout * 1000;
2148 if ( n < 1000 )
2149 n = 5000; /* use 5s if backlight is always on or off */
2151 if (((state->id3->elapsed < 1000) ||
2152 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2153 (state->paused == false))
2154 backlight_on();
2156 #endif
2157 #ifdef HAVE_REMOTE_LCD
2158 if (global_settings.remote_caption_backlight && state->id3)
2160 /* turn on remote backlight n seconds before track ends, and turn it
2161 off n seconds into the new track. n == remote_backlight_timeout,
2162 or 5s */
2163 int n = global_settings.remote_backlight_timeout * 1000;
2165 if ( n < 1000 )
2166 n = 5000; /* use 5s if backlight is always on or off */
2168 if (((state->id3->elapsed < 1000) ||
2169 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2170 (state->paused == false))
2171 remote_backlight_on();
2173 #endif
2174 /* force a bars update if they are being displayed */
2175 viewportmanager_draw_statusbars(NULL);
2176 return true;