New 'clock present' WPS tag: The tag checks for the presence of the clock hardware...
[kugel-rb.git] / apps / gui / gwps-common.c
blob9a69c74bf3f94583ab2bb79503379ee68e05fa48
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 "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"
63 #include "viewport.h"
65 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
66 /* 3% of 30min file == 54s step size */
67 #define MIN_FF_REWIND_STEP 500
69 /* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds
70 (possibly with a decimal fraction) but stored as integer values.
71 E.g. 2.5 is stored as 25. This means 25 tenth of a second, i.e. 25 units.
73 #define TIMEOUT_UNIT (HZ/10) /* I.e. 0.1 sec */
74 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* In TIMEOUT_UNIT's */
76 bool wps_fading_out = false;
77 void fade(bool fade_in, bool updatewps)
79 int fp_global_vol = global_settings.volume << 8;
80 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
81 int fp_step = (fp_global_vol - fp_min_vol) / 30;
82 int i;
83 wps_fading_out = !fade_in;
84 if (fade_in) {
85 /* fade in */
86 int fp_volume = fp_min_vol;
88 /* zero out the sound */
89 sound_set_volume(fp_min_vol >> 8);
91 sleep(HZ/10); /* let audio thread run */
92 audio_resume();
94 while (fp_volume < fp_global_vol - fp_step) {
95 fp_volume += fp_step;
96 sound_set_volume(fp_volume >> 8);
97 if (updatewps)
99 FOR_NB_SCREENS(i)
100 gui_wps_redraw(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
102 sleep(1);
104 sound_set_volume(global_settings.volume);
106 else {
107 /* fade out */
108 int fp_volume = fp_global_vol;
110 while (fp_volume > fp_min_vol + fp_step) {
111 fp_volume -= fp_step;
112 sound_set_volume(fp_volume >> 8);
113 if (updatewps)
115 FOR_NB_SCREENS(i)
116 gui_wps_redraw(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
118 sleep(1);
120 audio_pause();
121 wps_fading_out = false;
122 #if CONFIG_CODEC != SWCODEC
123 #ifndef SIMULATOR
124 /* let audio thread run and wait for the mas to run out of data */
125 while (!mp3_pause_done())
126 #endif
127 sleep(HZ/10);
128 #endif
130 /* reset volume to what it was before the fade */
131 sound_set_volume(global_settings.volume);
135 bool update_onvol_change(struct gui_wps * gwps)
137 gui_wps_redraw(gwps, 0, WPS_REFRESH_NON_STATIC);
139 #ifdef HAVE_LCD_CHARCELLS
140 splashf(0, "Vol: %3d dB",
141 sound_val2phys(SOUND_VOLUME, global_settings.volume));
142 return true;
143 #endif
144 return false;
147 bool ffwd_rew(int button)
149 unsigned int step = 0; /* current ff/rewind step */
150 unsigned int max_step = 0; /* maximum ff/rewind step */
151 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
152 int direction = -1; /* forward=1 or backward=-1 */
153 bool exit = false;
154 bool usb = false;
155 int i = 0;
156 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
158 if (button == ACTION_NONE)
160 status_set_ffmode(0);
161 return usb;
163 while (!exit)
165 switch ( button )
167 case ACTION_WPS_SEEKFWD:
168 direction = 1;
169 case ACTION_WPS_SEEKBACK:
170 if (wps_state.ff_rewind)
172 if (direction == 1)
174 /* fast forwarding, calc max step relative to end */
175 max_step = (wps_state.id3->length -
176 (wps_state.id3->elapsed +
177 ff_rewind_count)) *
178 FF_REWIND_MAX_PERCENT / 100;
180 else
182 /* rewinding, calc max step relative to start */
183 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
184 FF_REWIND_MAX_PERCENT / 100;
187 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
189 if (step > max_step)
190 step = max_step;
192 ff_rewind_count += step * direction;
194 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
195 step += step >> ff_rw_accel;
197 else
199 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
200 wps_state.id3 && wps_state.id3->length )
202 if (!wps_state.paused)
203 #if (CONFIG_CODEC == SWCODEC)
204 audio_pre_ff_rewind();
205 #else
206 audio_pause();
207 #endif
208 #if CONFIG_KEYPAD == PLAYER_PAD
209 FOR_NB_SCREENS(i)
210 gui_wps[i].display->stop_scroll();
211 #endif
212 if (direction > 0)
213 status_set_ffmode(STATUS_FASTFORWARD);
214 else
215 status_set_ffmode(STATUS_FASTBACKWARD);
217 wps_state.ff_rewind = true;
219 step = 1000 * global_settings.ff_rewind_min_step;
221 else
222 break;
225 if (direction > 0) {
226 if ((wps_state.id3->elapsed + ff_rewind_count) >
227 wps_state.id3->length)
228 ff_rewind_count = wps_state.id3->length -
229 wps_state.id3->elapsed;
231 else {
232 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
233 ff_rewind_count = -wps_state.id3->elapsed;
236 FOR_NB_SCREENS(i)
237 gui_wps_redraw(&gui_wps[i],
238 (wps_state.wps_time_countup == false)?
239 ff_rewind_count:-ff_rewind_count,
240 WPS_REFRESH_PLAYER_PROGRESS |
241 WPS_REFRESH_DYNAMIC);
243 break;
245 case ACTION_WPS_STOPSEEK:
246 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
247 audio_ff_rewind(wps_state.id3->elapsed);
248 ff_rewind_count = 0;
249 wps_state.ff_rewind = false;
250 status_set_ffmode(0);
251 #if (CONFIG_CODEC != SWCODEC)
252 if (!wps_state.paused)
253 audio_resume();
254 #endif
255 #ifdef HAVE_LCD_CHARCELLS
256 FOR_NB_SCREENS(i)
257 gui_wps_redraw(&gui_wps[i],0, WPS_REFRESH_ALL);
258 #endif
259 exit = true;
260 break;
262 default:
263 if(default_event_handler(button) == SYS_USB_CONNECTED) {
264 status_set_ffmode(0);
265 usb = true;
266 exit = true;
268 break;
270 if (!exit)
271 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
273 return usb;
276 bool gui_wps_display(struct gui_wps *gwps)
278 struct screen *display = gwps->display;
279 struct wps_data *data = gwps->data;
280 int screen = display->screen_type;
282 /* Update the values in the first (default) viewport - in case the user
283 has modified the statusbar or colour settings */
284 #if LCD_DEPTH > 1
285 if (display->depth > 1)
287 data->viewports[0].vp.fg_pattern = display->get_foreground();
288 data->viewports[0].vp.bg_pattern = display->get_background();
290 #endif
291 display->clear_display();
292 if (!data->wps_loaded) {
293 if ( !data->num_tokens ) {
294 /* set the default wps for the main-screen */
295 if(screen == SCREEN_MAIN)
297 #if LCD_DEPTH > 1
298 unload_wps_backdrop();
299 #endif
300 wps_data_load(data,
301 display,
302 #ifdef HAVE_LCD_BITMAP
303 "%s%?it<%?in<%in. |>%it|%fn>\n"
304 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
305 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
306 "\n"
307 "%al%pc/%pt%ar[%pp:%pe]\n"
308 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
309 "%pb\n"
310 "%pm\n", false);
311 #else
312 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
313 "%pc%?ps<*|/>%pt\n", false);
314 #endif
316 #ifdef HAVE_REMOTE_LCD
317 /* set the default wps for the remote-screen */
318 else if(screen == SCREEN_REMOTE)
320 #if LCD_REMOTE_DEPTH > 1
321 unload_remote_wps_backdrop();
322 #endif
323 wps_data_load(data,
324 display,
325 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
326 "%s%?it<%?in<%in. |>%it|%fn>\n"
327 "%al%pc/%pt%ar[%pp:%pe]\n"
328 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
329 "%pb\n", false);
331 #endif
334 else
336 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
337 if (screen == SCREEN_REMOTE)
338 show_remote_wps_backdrop();
339 else if (screen == SCREEN_MAIN)
340 #endif
341 #if LCD_DEPTH > 1
342 show_wps_backdrop();
343 #endif
345 return gui_wps_redraw(gwps, 0, WPS_REFRESH_ALL);
348 bool gui_wps_update(struct gui_wps *gwps)
350 struct mp3entry *id3 = gwps->state->id3;
351 bool retval;
352 if (cuesheet_is_enabled() && id3->cuesheet_type
353 && (id3->elapsed < curr_cue->curr_track->offset
354 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
355 && id3->elapsed >= (curr_cue->curr_track+1)->offset)))
357 /* We've changed tracks within the cuesheet :
358 we need to update the ID3 info and refresh the WPS */
359 gwps->state->do_full_update = true;
360 cue_find_current_track(curr_cue, id3->elapsed);
361 cue_spoof_id3(curr_cue, id3);
364 retval = gui_wps_redraw(gwps, 0,
365 gwps->state->do_full_update ?
366 WPS_REFRESH_ALL : WPS_REFRESH_NON_STATIC);
367 return retval;
371 void display_keylock_text(bool locked)
373 int i;
374 FOR_NB_SCREENS(i)
375 gui_wps[i].display->stop_scroll();
377 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
380 #ifdef HAVE_LCD_BITMAP
382 static void draw_progressbar(struct gui_wps *gwps,
383 struct wps_viewport *wps_vp)
385 struct screen *display = gwps->display;
386 struct wps_state *state = gwps->state;
387 struct progressbar *pb = wps_vp->pb;
388 int y = pb->y;
390 if (y < 0)
392 int line_height = font_get(wps_vp->vp.font)->height;
393 /* center the pb in the line, but only if the line is higher than the pb */
394 int center = (line_height-pb->height)/2;
395 /* if Y was not set calculate by font height,Y is -line_number-1 */
396 y = (-y -1)*line_height + (0 > center ? 0 : center);
399 if (pb->have_bitmap_pb)
400 gui_bitmap_scrollbar_draw(display, pb->bm,
401 pb->x, y, pb->width, pb->bm.height,
402 state->id3->length ? state->id3->length : 1, 0,
403 state->id3->length ? state->id3->elapsed
404 + state->ff_rewind_count : 0,
405 HORIZONTAL);
406 else
407 gui_scrollbar_draw(display, pb->x, y, pb->width, pb->height,
408 state->id3->length ? state->id3->length : 1, 0,
409 state->id3->length ? state->id3->elapsed
410 + state->ff_rewind_count : 0,
411 HORIZONTAL);
412 #ifdef AB_REPEAT_ENABLE
413 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
414 ab_draw_markers(display, state->id3->length,
415 pb->x, pb->x + pb->width, y, pb->height);
416 #endif
418 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
419 cue_draw_markers(display, state->id3->length,
420 pb->x, pb->x + pb->width, y+1, pb->height-2);
423 /* clears the area where the image was shown */
424 static void clear_image_pos(struct gui_wps *gwps, int n)
426 if(!gwps)
427 return;
428 struct wps_data *data = gwps->data;
429 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
430 gwps->display->fillrect(data->img[n].x, data->img[n].y,
431 data->img[n].bm.width, data->img[n].subimage_height);
432 gwps->display->set_drawmode(DRMODE_SOLID);
435 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
437 struct screen *display = gwps->display;
438 struct wps_data *data = gwps->data;
439 if(data->img[n].always_display)
440 display->set_drawmode(DRMODE_FG);
441 else
442 display->set_drawmode(DRMODE_SOLID);
444 #if LCD_DEPTH > 1
445 if(data->img[n].bm.format == FORMAT_MONO) {
446 #endif
447 display->mono_bitmap_part(data->img[n].bm.data,
448 0, data->img[n].subimage_height * subimage,
449 data->img[n].bm.width, data->img[n].x,
450 data->img[n].y, data->img[n].bm.width,
451 data->img[n].subimage_height);
452 #if LCD_DEPTH > 1
453 } else {
454 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
455 0, data->img[n].subimage_height * subimage,
456 data->img[n].bm.width, data->img[n].x,
457 data->img[n].y, data->img[n].bm.width,
458 data->img[n].subimage_height);
460 #endif
463 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
465 if(!gwps || !gwps->data || !gwps->display)
466 return;
468 int n;
469 struct wps_data *data = gwps->data;
470 struct screen *display = gwps->display;
472 for (n = 0; n < MAX_IMAGES; n++)
474 if (data->img[n].loaded)
476 if (data->img[n].display >= 0)
478 wps_draw_image(gwps, n, data->img[n].display);
479 } else if (data->img[n].always_display && data->img[n].vp == vp)
481 wps_draw_image(gwps, n, 0);
485 display->set_drawmode(DRMODE_SOLID);
488 #else /* HAVE_LCD_CHARCELL */
490 static bool draw_player_progress(struct gui_wps *gwps)
492 struct wps_state *state = gwps->state;
493 struct screen *display = gwps->display;
494 unsigned char progress_pattern[7];
495 int pos = 0;
496 int i;
498 if (!state->id3)
499 return false;
501 if (state->id3->length)
502 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
503 / state->id3->length;
505 for (i = 0; i < 7; i++, pos -= 5)
507 if (pos <= 0)
508 progress_pattern[i] = 0x1fu;
509 else if (pos >= 5)
510 progress_pattern[i] = 0x00u;
511 else
512 progress_pattern[i] = 0x1fu >> pos;
515 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
516 return true;
519 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
521 static const unsigned char numbers[10][4] = {
522 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
523 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
524 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
525 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
526 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
527 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
528 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
529 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
530 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
531 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
534 struct wps_state *state = gwps->state;
535 struct screen *display = gwps->display;
536 struct wps_data *data = gwps->data;
537 unsigned char progress_pattern[7];
538 char timestr[10];
539 int time;
540 int time_idx = 0;
541 int pos = 0;
542 int pat_idx = 1;
543 int digit, i, j;
544 bool softchar;
546 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
547 return;
549 time = state->id3->elapsed + state->ff_rewind_count;
550 if (state->id3->length)
551 pos = 55 * time / state->id3->length;
553 memset(timestr, 0, sizeof(timestr));
554 format_time(timestr, sizeof(timestr)-2, time);
555 timestr[strlen(timestr)] = ':'; /* always safe */
557 for (i = 0; i < 11; i++, pos -= 5)
559 softchar = false;
560 memset(progress_pattern, 0, sizeof(progress_pattern));
562 if ((digit = timestr[time_idx]))
564 softchar = true;
565 digit -= '0';
567 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
569 memcpy(progress_pattern, numbers[digit], 4);
570 time_idx += 2;
572 else /* tens, shifted right */
574 for (j = 0; j < 4; j++)
575 progress_pattern[j] = numbers[digit][j] >> 1;
577 if (time_idx > 0) /* not the first group, add colon in front */
579 progress_pattern[1] |= 0x10u;
580 progress_pattern[3] |= 0x10u;
582 time_idx++;
585 if (pos >= 5)
586 progress_pattern[5] = progress_pattern[6] = 0x1fu;
589 if (pos > 0 && pos < 5)
591 softchar = true;
592 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
595 if (softchar && pat_idx < 8)
597 display->define_pattern(data->wps_progress_pat[pat_idx],
598 progress_pattern);
599 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
600 pat_idx++;
602 else if (pos <= 0)
603 buf = utf8encode(' ', buf);
604 else
605 buf = utf8encode(0xe115, buf); /* 2/7 _ */
607 *buf = '\0';
610 #endif /* HAVE_LCD_CHARCELL */
612 static char* get_codectype(const struct mp3entry* id3)
614 if (id3->codectype < AFMT_NUM_CODECS) {
615 return (char*)audio_formats[id3->codectype].label;
616 } else {
617 return NULL;
621 /* Extract a part from a path.
623 * buf - buffer extract part to.
624 * buf_size - size of buffer.
625 * path - path to extract from.
626 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
627 * parent of parent, etc.
629 * Returns buf if the desired level was found, NULL otherwise.
631 static char* get_dir(char* buf, int buf_size, const char* path, int level)
633 const char* sep;
634 const char* last_sep;
635 int len;
637 sep = path + strlen(path);
638 last_sep = sep;
640 while (sep > path)
642 if ('/' == *(--sep))
644 if (!level)
645 break;
647 level--;
648 last_sep = sep - 1;
652 if (level || (last_sep <= sep))
653 return NULL;
655 len = MIN(last_sep - sep, buf_size - 1);
656 strncpy(buf, sep + 1, len);
657 buf[len] = 0;
658 return buf;
661 /* Return the tag found at index i and write its value in buf.
662 The return value is buf if the tag had a value, or NULL if not.
664 intval is used with conditionals/enums: when this function is called,
665 intval should contain the number of options in the conditional/enum.
666 When this function returns, intval is -1 if the tag is non numeric or,
667 if the tag is numeric, *intval is the enum case we want to go to (between 1
668 and the original value of *intval, inclusive).
669 When not treating a conditional/enum, intval should be NULL.
671 static const char *get_token_value(struct gui_wps *gwps,
672 struct wps_token *token,
673 char *buf, int buf_size,
674 int *intval)
676 if (!gwps)
677 return NULL;
679 struct wps_data *data = gwps->data;
680 struct wps_state *state = gwps->state;
682 if (!data || !state)
683 return NULL;
685 struct mp3entry *id3;
687 if (token->next)
688 id3 = state->nid3;
689 else
690 id3 = state->id3;
692 if (!id3)
693 return NULL;
695 #if CONFIG_RTC
696 struct tm* tm = NULL;
698 /* if the token is an RTC one, update the time
699 and do the necessary checks */
700 if (token->type >= WPS_TOKENS_RTC_BEGIN
701 && token->type <= WPS_TOKENS_RTC_END)
703 tm = get_time();
705 if (!valid_time(tm))
706 return NULL;
708 #endif
710 int limit = 1;
711 if (intval)
713 limit = *intval;
714 *intval = -1;
717 switch (token->type)
719 case WPS_TOKEN_CHARACTER:
720 return &(token->value.c);
722 case WPS_TOKEN_STRING:
723 return data->strings[token->value.i];
725 case WPS_TOKEN_TRACK_TIME_ELAPSED:
726 format_time(buf, buf_size,
727 id3->elapsed + state->ff_rewind_count);
728 return buf;
730 case WPS_TOKEN_TRACK_TIME_REMAINING:
731 format_time(buf, buf_size,
732 id3->length - id3->elapsed -
733 state->ff_rewind_count);
734 return buf;
736 case WPS_TOKEN_TRACK_LENGTH:
737 format_time(buf, buf_size, id3->length);
738 return buf;
740 case WPS_TOKEN_PLAYLIST_ENTRIES:
741 snprintf(buf, buf_size, "%d", playlist_amount());
742 return buf;
744 case WPS_TOKEN_PLAYLIST_NAME:
745 return playlist_name(NULL, buf, buf_size);
747 case WPS_TOKEN_PLAYLIST_POSITION:
748 snprintf(buf, buf_size, "%d", playlist_get_display_index());
749 return buf;
751 case WPS_TOKEN_PLAYLIST_SHUFFLE:
752 if ( global_settings.playlist_shuffle )
753 return "s";
754 else
755 return NULL;
756 break;
758 case WPS_TOKEN_VOLUME:
759 snprintf(buf, buf_size, "%d", global_settings.volume);
760 if (intval)
762 if (global_settings.volume == sound_min(SOUND_VOLUME))
764 *intval = 1;
766 else if (global_settings.volume == 0)
768 *intval = limit - 1;
770 else if (global_settings.volume > 0)
772 *intval = limit;
774 else
776 *intval = (limit - 3) * (global_settings.volume
777 - sound_min(SOUND_VOLUME) - 1)
778 / (-1 - sound_min(SOUND_VOLUME)) + 2;
781 return buf;
783 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
784 if (id3->length <= 0)
785 return NULL;
787 if (intval)
789 *intval = limit * (id3->elapsed + state->ff_rewind_count)
790 / id3->length + 1;
792 snprintf(buf, buf_size, "%d",
793 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
794 return buf;
796 case WPS_TOKEN_METADATA_ARTIST:
797 return id3->artist;
799 case WPS_TOKEN_METADATA_COMPOSER:
800 return id3->composer;
802 case WPS_TOKEN_METADATA_ALBUM:
803 return id3->album;
805 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
806 return id3->albumartist;
808 case WPS_TOKEN_METADATA_GROUPING:
809 return id3->grouping;
811 case WPS_TOKEN_METADATA_GENRE:
812 return id3->genre_string;
814 case WPS_TOKEN_METADATA_DISC_NUMBER:
815 if (id3->disc_string)
816 return id3->disc_string;
817 if (id3->discnum) {
818 snprintf(buf, buf_size, "%d", id3->discnum);
819 return buf;
821 return NULL;
823 case WPS_TOKEN_METADATA_TRACK_NUMBER:
824 if (id3->track_string)
825 return id3->track_string;
827 if (id3->tracknum) {
828 snprintf(buf, buf_size, "%d", id3->tracknum);
829 return buf;
831 return NULL;
833 case WPS_TOKEN_METADATA_TRACK_TITLE:
834 return id3->title;
836 case WPS_TOKEN_METADATA_VERSION:
837 switch (id3->id3version)
839 case ID3_VER_1_0:
840 return "1";
842 case ID3_VER_1_1:
843 return "1.1";
845 case ID3_VER_2_2:
846 return "2.2";
848 case ID3_VER_2_3:
849 return "2.3";
851 case ID3_VER_2_4:
852 return "2.4";
854 default:
855 return NULL;
858 case WPS_TOKEN_METADATA_YEAR:
859 if( id3->year_string )
860 return id3->year_string;
862 if (id3->year) {
863 snprintf(buf, buf_size, "%d", id3->year);
864 return buf;
866 return NULL;
868 case WPS_TOKEN_METADATA_COMMENT:
869 return id3->comment;
871 #ifdef HAVE_ALBUMART
872 case WPS_TOKEN_ALBUMART_DISPLAY:
873 draw_album_art(gwps, audio_current_aa_hid(), false);
874 return NULL;
876 case WPS_TOKEN_ALBUMART_FOUND:
877 if (audio_current_aa_hid() >= 0) {
878 return "C";
880 return NULL;
881 #endif
883 case WPS_TOKEN_FILE_BITRATE:
884 if(id3->bitrate)
885 snprintf(buf, buf_size, "%d", id3->bitrate);
886 else
887 return "?";
888 return buf;
890 case WPS_TOKEN_FILE_CODEC:
891 if (intval)
893 if(id3->codectype == AFMT_UNKNOWN)
894 *intval = AFMT_NUM_CODECS;
895 else
896 *intval = id3->codectype;
898 return get_codectype(id3);
900 case WPS_TOKEN_FILE_FREQUENCY:
901 snprintf(buf, buf_size, "%ld", id3->frequency);
902 return buf;
904 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
905 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
906 if ((id3->frequency % 1000) < 100)
907 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
908 else
909 snprintf(buf, buf_size, "%ld.%d",
910 id3->frequency / 1000,
911 (id3->frequency % 1000) / 100);
912 return buf;
914 case WPS_TOKEN_FILE_NAME:
915 if (get_dir(buf, buf_size, id3->path, 0)) {
916 /* Remove extension */
917 char* sep = strrchr(buf, '.');
918 if (NULL != sep) {
919 *sep = 0;
921 return buf;
923 else {
924 return NULL;
927 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
928 return get_dir(buf, buf_size, id3->path, 0);
930 case WPS_TOKEN_FILE_PATH:
931 return id3->path;
933 case WPS_TOKEN_FILE_SIZE:
934 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
935 return buf;
937 case WPS_TOKEN_FILE_VBR:
938 return id3->vbr ? "(avg)" : NULL;
940 case WPS_TOKEN_FILE_DIRECTORY:
941 return get_dir(buf, buf_size, id3->path, token->value.i);
943 case WPS_TOKEN_BATTERY_PERCENT:
945 int l = battery_level();
947 if (intval)
949 limit = MAX(limit, 2);
950 if (l > -1) {
951 /* First enum is used for "unknown level". */
952 *intval = (limit - 1) * l / 100 + 2;
953 } else {
954 *intval = 1;
958 if (l > -1) {
959 snprintf(buf, buf_size, "%d", l);
960 return buf;
961 } else {
962 return "?";
966 case WPS_TOKEN_BATTERY_VOLTS:
968 unsigned int v = battery_voltage();
969 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
970 return buf;
973 case WPS_TOKEN_BATTERY_TIME:
975 int t = battery_time();
976 if (t >= 0)
977 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
978 else
979 return "?h ?m";
980 return buf;
983 #if CONFIG_CHARGING
984 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
986 if(charger_input_state==CHARGER)
987 return "p";
988 else
989 return NULL;
991 #endif
992 #if CONFIG_CHARGING >= CHARGING_MONITOR
993 case WPS_TOKEN_BATTERY_CHARGING:
995 if (charge_state == CHARGING || charge_state == TOPOFF) {
996 return "c";
997 } else {
998 return NULL;
1001 #endif
1002 case WPS_TOKEN_BATTERY_SLEEPTIME:
1004 if (get_sleep_timer() == 0)
1005 return NULL;
1006 else
1008 format_time(buf, buf_size, get_sleep_timer() * 1000);
1009 return buf;
1013 case WPS_TOKEN_PLAYBACK_STATUS:
1015 int status = audio_status();
1016 int mode = 1;
1017 if (status == AUDIO_STATUS_PLAY)
1018 mode = 2;
1019 if (wps_fading_out ||
1020 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1021 mode = 3;
1022 if (status_get_ffmode() == STATUS_FASTFORWARD)
1023 mode = 4;
1024 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1025 mode = 5;
1027 if (intval) {
1028 *intval = mode;
1031 snprintf(buf, buf_size, "%d", mode-1);
1032 return buf;
1035 case WPS_TOKEN_REPEAT_MODE:
1036 if (intval)
1037 *intval = global_settings.repeat_mode + 1;
1038 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1039 return buf;
1041 case WPS_TOKEN_RTC_PRESENT:
1042 #if CONFIG_RTC
1043 return "c";
1044 #else
1045 return NULL;
1046 #endif
1048 #if CONFIG_RTC
1049 case WPS_TOKEN_RTC_12HOUR_CFG:
1050 if (intval)
1051 *intval = global_settings.timeformat + 1;
1052 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1053 return buf;
1055 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1056 /* d: day of month (01..31) */
1057 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1058 return buf;
1060 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1061 /* e: day of month, blank padded ( 1..31) */
1062 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1063 return buf;
1065 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1066 /* H: hour (00..23) */
1067 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1068 return buf;
1070 case WPS_TOKEN_RTC_HOUR_24:
1071 /* k: hour ( 0..23) */
1072 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1073 return buf;
1075 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1076 /* I: hour (01..12) */
1077 snprintf(buf, buf_size, "%02d",
1078 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1079 return buf;
1081 case WPS_TOKEN_RTC_HOUR_12:
1082 /* l: hour ( 1..12) */
1083 snprintf(buf, buf_size, "%2d",
1084 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1085 return buf;
1087 case WPS_TOKEN_RTC_MONTH:
1088 /* m: month (01..12) */
1089 if (intval)
1090 *intval = tm->tm_mon + 1;
1091 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1092 return buf;
1094 case WPS_TOKEN_RTC_MINUTE:
1095 /* M: minute (00..59) */
1096 snprintf(buf, buf_size, "%02d", tm->tm_min);
1097 return buf;
1099 case WPS_TOKEN_RTC_SECOND:
1100 /* S: second (00..59) */
1101 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1102 return buf;
1104 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1105 /* y: last two digits of year (00..99) */
1106 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1107 return buf;
1109 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1110 /* Y: year (1970...) */
1111 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1112 return buf;
1114 case WPS_TOKEN_RTC_AM_PM_UPPER:
1115 /* p: upper case AM or PM indicator */
1116 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1118 case WPS_TOKEN_RTC_AM_PM_LOWER:
1119 /* P: lower case am or pm indicator */
1120 return tm->tm_hour/12 == 0 ? "am" : "pm";
1122 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1123 /* a: abbreviated weekday name (Sun..Sat) */
1124 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1126 case WPS_TOKEN_RTC_MONTH_NAME:
1127 /* b: abbreviated month name (Jan..Dec) */
1128 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1130 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1131 /* u: day of week (1..7); 1 is Monday */
1132 if (intval)
1133 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1134 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1135 return buf;
1137 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1138 /* w: day of week (0..6); 0 is Sunday */
1139 if (intval)
1140 *intval = tm->tm_wday + 1;
1141 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1142 return buf;
1143 #else
1144 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1145 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1146 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1147 case WPS_TOKEN_RTC_HOUR_24:
1148 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1149 case WPS_TOKEN_RTC_HOUR_12:
1150 case WPS_TOKEN_RTC_MONTH:
1151 case WPS_TOKEN_RTC_MINUTE:
1152 case WPS_TOKEN_RTC_SECOND:
1153 case WPS_TOKEN_RTC_AM_PM_UPPER:
1154 case WPS_TOKEN_RTC_AM_PM_LOWER:
1155 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1156 return "--";
1157 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1158 return "----";
1159 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1160 case WPS_TOKEN_RTC_MONTH_NAME:
1161 return "---";
1162 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1163 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1164 return "-";
1165 #endif
1167 #ifdef HAVE_LCD_CHARCELLS
1168 case WPS_TOKEN_PROGRESSBAR:
1170 char *end = utf8encode(data->wps_progress_pat[0], buf);
1171 *end = '\0';
1172 return buf;
1175 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1176 if(is_new_player())
1178 /* we need 11 characters (full line) for
1179 progress-bar */
1180 strncpy(buf, " ", buf_size);
1182 else
1184 /* Tell the user if we have an OldPlayer */
1185 strncpy(buf, " <Old LCD> ", buf_size);
1187 return buf;
1188 #endif
1190 #ifdef HAVE_TAGCACHE
1191 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1192 if (intval) {
1193 *intval = id3->playcount + 1;
1195 snprintf(buf, buf_size, "%ld", id3->playcount);
1196 return buf;
1198 case WPS_TOKEN_DATABASE_RATING:
1199 if (intval) {
1200 *intval = id3->rating + 1;
1202 snprintf(buf, buf_size, "%d", id3->rating);
1203 return buf;
1205 case WPS_TOKEN_DATABASE_AUTOSCORE:
1206 if (intval)
1207 *intval = id3->score + 1;
1209 snprintf(buf, buf_size, "%d", id3->score);
1210 return buf;
1211 #endif
1213 #if (CONFIG_CODEC == SWCODEC)
1214 case WPS_TOKEN_CROSSFADE:
1215 if (intval)
1216 *intval = global_settings.crossfade + 1;
1217 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1218 return buf;
1220 case WPS_TOKEN_REPLAYGAIN:
1222 int val;
1224 if (global_settings.replaygain == 0)
1225 val = 1; /* off */
1226 else
1228 int type =
1229 get_replaygain_mode(id3->track_gain_string != NULL,
1230 id3->album_gain_string != NULL);
1231 if (type < 0)
1232 val = 6; /* no tag */
1233 else
1234 val = type + 2;
1236 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1237 val += 2;
1240 if (intval)
1241 *intval = val;
1243 switch (val)
1245 case 1:
1246 case 6:
1247 return "+0.00 dB";
1248 break;
1249 case 2:
1250 case 4:
1251 strncpy(buf, id3->track_gain_string, buf_size);
1252 break;
1253 case 3:
1254 case 5:
1255 strncpy(buf, id3->album_gain_string, buf_size);
1256 break;
1258 return buf;
1260 #endif /* (CONFIG_CODEC == SWCODEC) */
1262 #if (CONFIG_CODEC != MAS3507D)
1263 case WPS_TOKEN_SOUND_PITCH:
1265 int val = sound_get_pitch();
1266 snprintf(buf, buf_size, "%d.%d",
1267 val / 10, val % 10);
1268 return buf;
1270 #endif
1272 case WPS_TOKEN_MAIN_HOLD:
1273 #ifdef HAS_BUTTON_HOLD
1274 if (button_hold())
1275 #else
1276 if (is_keys_locked())
1277 #endif /*hold switch or softlock*/
1278 return "h";
1279 else
1280 return NULL;
1282 #ifdef HAS_REMOTE_BUTTON_HOLD
1283 case WPS_TOKEN_REMOTE_HOLD:
1284 if (remote_button_hold())
1285 return "r";
1286 else
1287 return NULL;
1288 #endif
1290 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1291 case WPS_TOKEN_VLED_HDD:
1292 if(led_read(HZ/2))
1293 return "h";
1294 else
1295 return NULL;
1296 #endif
1297 case WPS_TOKEN_BUTTON_VOLUME:
1298 if (data->button_time_volume &&
1299 TIME_BEFORE(current_tick, data->button_time_volume +
1300 token->value.i * TIMEOUT_UNIT))
1301 return "v";
1302 return NULL;
1303 case WPS_TOKEN_LASTTOUCH:
1304 #ifdef HAVE_TOUCHSCREEN
1305 if (TIME_BEFORE(current_tick, token->value.i * TIMEOUT_UNIT +
1306 touchscreen_last_touch()))
1307 return "t";
1308 #endif
1309 return NULL;
1311 case WPS_TOKEN_SETTING:
1313 if (intval)
1315 /* Handle contionals */
1316 const struct settings_list *s = settings+token->value.i;
1317 switch (s->flags&F_T_MASK)
1319 case F_T_INT:
1320 case F_T_UINT:
1321 if (s->flags&F_RGB)
1322 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1323 /* shouldn't overflow since colors are stored
1324 * on 16 bits ...
1325 * but this is pretty useless anyway */
1326 *intval = *(int*)s->setting + 1;
1327 else if (s->cfg_vals == NULL)
1328 /* %?St|name|<1st choice|2nd choice|...> */
1329 *intval = (*(int*)s->setting-s->int_setting->min)
1330 /s->int_setting->step + 1;
1331 else
1332 /* %?St|name|<1st choice|2nd choice|...> */
1333 /* Not sure about this one. cfg_name/vals are
1334 * indexed from 0 right? */
1335 *intval = *(int*)s->setting + 1;
1336 break;
1337 case F_T_BOOL:
1338 /* %?St|name|<if true|if false> */
1339 *intval = *(bool*)s->setting?1:2;
1340 break;
1341 case F_T_CHARPTR:
1342 /* %?St|name|<if non empty string|if empty>
1343 * The string's emptyness discards the setting's
1344 * prefix and suffix */
1345 *intval = ((char*)s->setting)[0]?1:2;
1346 break;
1347 default:
1348 /* This shouldn't happen ... but you never know */
1349 *intval = -1;
1350 break;
1353 cfg_to_string(token->value.i,buf,buf_size);
1354 return buf;
1357 default:
1358 return NULL;
1362 /* Return the index to the end token for the conditional token at index.
1363 The conditional token can be either a start token or a separator
1364 (i.e. option) token.
1366 static int find_conditional_end(struct wps_data *data, int index)
1368 int ret = index;
1369 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1370 ret = data->tokens[ret].value.i;
1372 /* ret now is the index to the end token for the conditional. */
1373 return ret;
1376 /* Evaluate the conditional that is at *token_index and return whether a skip
1377 has ocurred. *token_index is updated with the new position.
1379 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1381 if (!gwps)
1382 return false;
1384 struct wps_data *data = gwps->data;
1386 int i, cond_end;
1387 int cond_index = *token_index;
1388 char result[128];
1389 const char *value;
1390 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1391 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1393 /* treat ?xx<true> constructs as if they had 2 options. */
1394 if (num_options < 2)
1395 num_options = 2;
1397 int intval = num_options;
1398 /* get_token_value needs to know the number of options in the enum */
1399 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1400 result, sizeof(result), &intval);
1402 /* intval is now the number of the enum option we want to read,
1403 starting from 1. If intval is -1, we check if value is empty. */
1404 if (intval == -1)
1405 intval = (value && *value) ? 1 : num_options;
1406 else if (intval > num_options || intval < 1)
1407 intval = num_options;
1409 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1411 /* skip to the appropriate enum case */
1412 int next = cond_index + 2;
1413 for (i = 1; i < intval; i++)
1415 next = data->tokens[next].value.i;
1417 *token_index = next;
1419 if (prev_val == intval)
1421 /* Same conditional case as previously. Return without clearing the
1422 pictures */
1423 return false;
1426 cond_end = find_conditional_end(data, cond_index + 2);
1427 for (i = cond_index + 3; i < cond_end; i++)
1429 #ifdef HAVE_LCD_BITMAP
1430 /* clear all pictures in the conditional and nested ones */
1431 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1432 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1433 #endif
1434 #ifdef HAVE_ALBUMART
1435 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1436 draw_album_art(gwps, audio_current_aa_hid(), true);
1437 #endif
1440 return true;
1443 /* Read a (sub)line to the given alignment format buffer.
1444 linebuf is the buffer where the data is actually stored.
1445 align is the alignment format that'll be used to display the text.
1446 The return value indicates whether the line needs to be updated.
1448 static bool get_line(struct gui_wps *gwps,
1449 int line, int subline,
1450 struct align_pos *align,
1451 char *linebuf,
1452 int linebuf_size)
1454 struct wps_data *data = gwps->data;
1456 char temp_buf[128];
1457 char *buf = linebuf; /* will always point to the writing position */
1458 char *linebuf_end = linebuf + linebuf_size - 1;
1459 int i, last_token_idx;
1460 bool update = false;
1462 /* alignment-related variables */
1463 int cur_align;
1464 char* cur_align_start;
1465 cur_align_start = buf;
1466 cur_align = WPS_ALIGN_LEFT;
1467 align->left = NULL;
1468 align->center = NULL;
1469 align->right = NULL;
1471 /* Process all tokens of the desired subline */
1472 last_token_idx = wps_last_token_index(data, line, subline);
1473 for (i = wps_first_token_index(data, line, subline);
1474 i <= last_token_idx; i++)
1476 switch(data->tokens[i].type)
1478 case WPS_TOKEN_CONDITIONAL:
1479 /* place ourselves in the right conditional case */
1480 update |= evaluate_conditional(gwps, &i);
1481 break;
1483 case WPS_TOKEN_CONDITIONAL_OPTION:
1484 /* we've finished in the curent conditional case,
1485 skip to the end of the conditional structure */
1486 i = find_conditional_end(data, i);
1487 break;
1489 #ifdef HAVE_LCD_BITMAP
1490 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1492 struct gui_img *img = data->img;
1493 int n = data->tokens[i].value.i & 0xFF;
1494 int subimage = data->tokens[i].value.i >> 8;
1496 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1497 img[n].display = subimage;
1498 break;
1500 #endif
1502 case WPS_TOKEN_ALIGN_LEFT:
1503 case WPS_TOKEN_ALIGN_CENTER:
1504 case WPS_TOKEN_ALIGN_RIGHT:
1505 /* remember where the current aligned text started */
1506 switch (cur_align)
1508 case WPS_ALIGN_LEFT:
1509 align->left = cur_align_start;
1510 break;
1512 case WPS_ALIGN_CENTER:
1513 align->center = cur_align_start;
1514 break;
1516 case WPS_ALIGN_RIGHT:
1517 align->right = cur_align_start;
1518 break;
1520 /* start a new alignment */
1521 switch (data->tokens[i].type)
1523 case WPS_TOKEN_ALIGN_LEFT:
1524 cur_align = WPS_ALIGN_LEFT;
1525 break;
1526 case WPS_TOKEN_ALIGN_CENTER:
1527 cur_align = WPS_ALIGN_CENTER;
1528 break;
1529 case WPS_TOKEN_ALIGN_RIGHT:
1530 cur_align = WPS_ALIGN_RIGHT;
1531 break;
1532 default:
1533 break;
1535 *buf++ = 0;
1536 cur_align_start = buf;
1537 break;
1538 case WPS_VIEWPORT_ENABLE:
1540 char label = data->tokens[i].value.i;
1541 int j;
1542 char temp = VP_DRAW_HIDEABLE;
1543 for(j=0;j<data->num_viewports;j++)
1545 temp = VP_DRAW_HIDEABLE;
1546 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1547 (data->viewports[j].label == label))
1549 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1550 temp |= VP_DRAW_WASHIDDEN;
1551 data->viewports[j].hidden_flags = temp;
1555 break;
1556 default:
1558 /* get the value of the tag and copy it to the buffer */
1559 const char *value = get_token_value(gwps, &data->tokens[i],
1560 temp_buf, sizeof(temp_buf), NULL);
1561 if (value)
1563 update = true;
1564 while (*value && (buf < linebuf_end))
1565 *buf++ = *value++;
1567 break;
1572 /* close the current alignment */
1573 switch (cur_align)
1575 case WPS_ALIGN_LEFT:
1576 align->left = cur_align_start;
1577 break;
1579 case WPS_ALIGN_CENTER:
1580 align->center = cur_align_start;
1581 break;
1583 case WPS_ALIGN_RIGHT:
1584 align->right = cur_align_start;
1585 break;
1588 return update;
1591 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1593 struct wps_data *data = gwps->data;
1594 int i;
1595 int subline_idx = wps_subline_index(data, line, subline);
1596 int last_token_idx = wps_last_token_index(data, line, subline);
1598 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1600 for (i = wps_first_token_index(data, line, subline);
1601 i <= last_token_idx; i++)
1603 switch(data->tokens[i].type)
1605 case WPS_TOKEN_CONDITIONAL:
1606 /* place ourselves in the right conditional case */
1607 evaluate_conditional(gwps, &i);
1608 break;
1610 case WPS_TOKEN_CONDITIONAL_OPTION:
1611 /* we've finished in the curent conditional case,
1612 skip to the end of the conditional structure */
1613 i = find_conditional_end(data, i);
1614 break;
1616 case WPS_TOKEN_SUBLINE_TIMEOUT:
1617 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1618 break;
1620 default:
1621 break;
1626 /* Calculates which subline should be displayed for the specified line
1627 Returns true iff the subline must be refreshed */
1628 static bool update_curr_subline(struct gui_wps *gwps, int line)
1630 struct wps_data *data = gwps->data;
1632 int search, search_start, num_sublines;
1633 bool reset_subline;
1634 bool new_subline_refresh;
1635 bool only_one_subline;
1637 num_sublines = data->lines[line].num_sublines;
1638 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1639 new_subline_refresh = false;
1640 only_one_subline = false;
1642 /* if time to advance to next sub-line */
1643 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1644 reset_subline)
1646 /* search all sublines until the next subline with time > 0
1647 is found or we get back to the subline we started with */
1648 if (reset_subline)
1649 search_start = 0;
1650 else
1651 search_start = data->lines[line].curr_subline;
1653 for (search = 0; search < num_sublines; search++)
1655 data->lines[line].curr_subline++;
1657 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1658 if (data->lines[line].curr_subline == num_sublines)
1660 if (data->lines[line].curr_subline == 1)
1661 only_one_subline = true;
1662 data->lines[line].curr_subline = 0;
1665 /* if back where we started after search or
1666 only one subline is defined on the line */
1667 if (((search > 0) &&
1668 (data->lines[line].curr_subline == search_start)) ||
1669 only_one_subline)
1671 /* no other subline with a time > 0 exists */
1672 data->lines[line].subline_expire_time = (reset_subline ?
1673 current_tick :
1674 data->lines[line].subline_expire_time) + 100 * HZ;
1675 break;
1677 else
1679 /* get initial time multiplier for this subline */
1680 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1682 int subline_idx = wps_subline_index(data, line,
1683 data->lines[line].curr_subline);
1685 /* only use this subline if subline time > 0 */
1686 if (data->sublines[subline_idx].time_mult > 0)
1688 new_subline_refresh = true;
1689 data->lines[line].subline_expire_time = (reset_subline ?
1690 current_tick : data->lines[line].subline_expire_time) +
1691 TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
1692 break;
1698 return new_subline_refresh;
1701 /* Display a line appropriately according to its alignment format.
1702 format_align contains the text, separated between left, center and right.
1703 line is the index of the line on the screen.
1704 scroll indicates whether the line is a scrolling one or not.
1706 static void write_line(struct screen *display,
1707 struct align_pos *format_align,
1708 int line,
1709 bool scroll)
1711 int left_width = 0, left_xpos;
1712 int center_width = 0, center_xpos;
1713 int right_width = 0, right_xpos;
1714 int ypos;
1715 int space_width;
1716 int string_height;
1717 int scroll_width;
1719 /* calculate different string sizes and positions */
1720 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1721 if (format_align->left != 0) {
1722 display->getstringsize((unsigned char *)format_align->left,
1723 &left_width, &string_height);
1726 if (format_align->right != 0) {
1727 display->getstringsize((unsigned char *)format_align->right,
1728 &right_width, &string_height);
1731 if (format_align->center != 0) {
1732 display->getstringsize((unsigned char *)format_align->center,
1733 &center_width, &string_height);
1736 left_xpos = 0;
1737 right_xpos = (display->getwidth() - right_width);
1738 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1740 scroll_width = display->getwidth() - left_xpos;
1742 /* Checks for overlapping strings.
1743 If needed the overlapping strings will be merged, separated by a
1744 space */
1746 /* CASE 1: left and centered string overlap */
1747 /* there is a left string, need to merge left and center */
1748 if ((left_width != 0 && center_width != 0) &&
1749 (left_xpos + left_width + space_width > center_xpos)) {
1750 /* replace the former separator '\0' of left and
1751 center string with a space */
1752 *(--format_align->center) = ' ';
1753 /* calculate the new width and position of the merged string */
1754 left_width = left_width + space_width + center_width;
1755 /* there is no centered string anymore */
1756 center_width = 0;
1758 /* there is no left string, move center to left */
1759 if ((left_width == 0 && center_width != 0) &&
1760 (left_xpos + left_width > center_xpos)) {
1761 /* move the center string to the left string */
1762 format_align->left = format_align->center;
1763 /* calculate the new width and position of the string */
1764 left_width = center_width;
1765 /* there is no centered string anymore */
1766 center_width = 0;
1769 /* CASE 2: centered and right string overlap */
1770 /* there is a right string, need to merge center and right */
1771 if ((center_width != 0 && right_width != 0) &&
1772 (center_xpos + center_width + space_width > right_xpos)) {
1773 /* replace the former separator '\0' of center and
1774 right string with a space */
1775 *(--format_align->right) = ' ';
1776 /* move the center string to the right after merge */
1777 format_align->right = format_align->center;
1778 /* calculate the new width and position of the merged string */
1779 right_width = center_width + space_width + right_width;
1780 right_xpos = (display->getwidth() - right_width);
1781 /* there is no centered string anymore */
1782 center_width = 0;
1784 /* there is no right string, move center to right */
1785 if ((center_width != 0 && right_width == 0) &&
1786 (center_xpos + center_width > right_xpos)) {
1787 /* move the center string to the right string */
1788 format_align->right = format_align->center;
1789 /* calculate the new width and position of the string */
1790 right_width = center_width;
1791 right_xpos = (display->getwidth() - right_width);
1792 /* there is no centered string anymore */
1793 center_width = 0;
1796 /* CASE 3: left and right overlap
1797 There is no center string anymore, either there never
1798 was one or it has been merged in case 1 or 2 */
1799 /* there is a left string, need to merge left and right */
1800 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1801 (left_xpos + left_width + space_width > right_xpos)) {
1802 /* replace the former separator '\0' of left and
1803 right string with a space */
1804 *(--format_align->right) = ' ';
1805 /* calculate the new width and position of the string */
1806 left_width = left_width + space_width + right_width;
1807 /* there is no right string anymore */
1808 right_width = 0;
1810 /* there is no left string, move right to left */
1811 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1812 (left_width > right_xpos)) {
1813 /* move the right string to the left string */
1814 format_align->left = format_align->right;
1815 /* calculate the new width and position of the string */
1816 left_width = right_width;
1817 /* there is no right string anymore */
1818 right_width = 0;
1821 ypos = (line * string_height);
1824 if (scroll && ((left_width > scroll_width) ||
1825 (center_width > scroll_width) ||
1826 (right_width > scroll_width)))
1828 display->puts_scroll(0, line,
1829 (unsigned char *)format_align->left);
1831 else
1833 #ifdef HAVE_LCD_BITMAP
1834 /* clear the line first */
1835 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1836 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1837 display->set_drawmode(DRMODE_SOLID);
1838 #endif
1840 /* Nasty hack: we output an empty scrolling string,
1841 which will reset the scroller for that line */
1842 display->puts_scroll(0, line, (unsigned char *)"");
1844 /* print aligned strings */
1845 if (left_width != 0)
1847 display->putsxy(left_xpos, ypos,
1848 (unsigned char *)format_align->left);
1850 if (center_width != 0)
1852 display->putsxy(center_xpos, ypos,
1853 (unsigned char *)format_align->center);
1855 if (right_width != 0)
1857 display->putsxy(right_xpos, ypos,
1858 (unsigned char *)format_align->right);
1863 bool gui_wps_redraw(struct gui_wps *gwps,
1864 int ffwd_offset,
1865 unsigned refresh_mode)
1867 struct wps_data *data = gwps->data;
1868 struct screen *display = gwps->display;
1869 struct wps_state *state = gwps->state;
1871 if (!data || !state || !display)
1872 return false;
1874 struct mp3entry *id3 = state->id3;
1876 if (!id3)
1877 return false;
1879 int v, line, i, subline_idx;
1880 unsigned flags;
1881 char linebuf[MAX_PATH];
1883 struct align_pos align;
1884 align.left = NULL;
1885 align.center = NULL;
1886 align.right = NULL;
1888 bool update_line, new_subline_refresh;
1890 #ifdef HAVE_LCD_BITMAP
1892 /* to find out wether the peak meter is enabled we
1893 assume it wasn't until we find a line that contains
1894 the peak meter. We can't use peak_meter_enabled itself
1895 because that would mean to turn off the meter thread
1896 temporarily. (That shouldn't matter unless yield
1897 or sleep is called but who knows...)
1899 bool enable_pm = false;
1901 #endif
1903 /* reset to first subline if refresh all flag is set */
1904 if (refresh_mode == WPS_REFRESH_ALL)
1906 display->set_viewport(&data->viewports[0].vp);
1907 display->clear_viewport();
1909 for (i = 0; i <= data->num_lines; i++)
1911 data->lines[i].curr_subline = SUBLINE_RESET;
1915 #ifdef HAVE_LCD_CHARCELLS
1916 for (i = 0; i < 8; i++)
1918 if (data->wps_progress_pat[i] == 0)
1919 data->wps_progress_pat[i] = display->get_locked_pattern();
1921 #endif
1923 state->ff_rewind_count = ffwd_offset;
1925 /* disable any viewports which are conditionally displayed */
1926 for (v = 0; v < data->num_viewports; v++)
1928 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
1930 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
1931 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1932 else
1933 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
1936 for (v = 0; v < data->num_viewports; v++)
1938 struct wps_viewport *wps_vp = &(data->viewports[v]);
1939 unsigned vp_refresh_mode = refresh_mode;
1940 display->set_viewport(&wps_vp->vp);
1942 #ifdef HAVE_LCD_BITMAP
1943 /* Set images to not to be displayed */
1944 for (i = 0; i < MAX_IMAGES; i++)
1946 data->img[i].display = -1;
1948 #endif
1949 /* dont redraw the viewport if its disabled */
1950 if ((wps_vp->hidden_flags&VP_DRAW_HIDDEN))
1952 if (!(wps_vp->hidden_flags&VP_DRAW_WASHIDDEN))
1953 display->scroll_stop(&wps_vp->vp);
1954 wps_vp->hidden_flags |= VP_DRAW_WASHIDDEN;
1955 continue;
1957 else if (((wps_vp->hidden_flags&
1958 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1959 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1961 vp_refresh_mode = WPS_REFRESH_ALL;
1962 wps_vp->hidden_flags = VP_DRAW_HIDEABLE;
1964 if (vp_refresh_mode == WPS_REFRESH_ALL)
1966 display->clear_viewport();
1969 for (line = wps_vp->first_line;
1970 line <= wps_vp->last_line; line++)
1972 memset(linebuf, 0, sizeof(linebuf));
1973 update_line = false;
1975 /* get current subline for the line */
1976 new_subline_refresh = update_curr_subline(gwps, line);
1978 subline_idx = wps_subline_index(data, line,
1979 data->lines[line].curr_subline);
1980 flags = data->sublines[subline_idx].line_type;
1982 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1983 || new_subline_refresh)
1985 /* get_line tells us if we need to update the line */
1986 update_line = get_line(gwps, line, data->lines[line].curr_subline,
1987 &align, linebuf, sizeof(linebuf));
1989 #ifdef HAVE_LCD_BITMAP
1990 /* peakmeter */
1991 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1993 /* the peakmeter should be alone on its line */
1994 update_line = false;
1996 int h = font_get(wps_vp->vp.font)->height;
1997 int peak_meter_y = (line - wps_vp->first_line)* h;
1999 /* The user might decide to have the peak meter in the last
2000 line so that it is only displayed if no status bar is
2001 visible. If so we neither want do draw nor enable the
2002 peak meter. */
2003 if (peak_meter_y + h <= display->getheight()) {
2004 /* found a line with a peak meter -> remember that we must
2005 enable it later */
2006 enable_pm = true;
2007 peak_meter_enabled = true;
2008 peak_meter_screen(gwps->display, 0, peak_meter_y,
2009 MIN(h, display->getheight() - peak_meter_y));
2011 else
2013 peak_meter_enabled = false;
2017 #else /* HAVE_LCD_CHARCELL */
2019 /* progressbar */
2020 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2022 if (data->full_line_progressbar)
2023 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2024 else
2025 draw_player_progress(gwps);
2027 #endif
2029 if (update_line &&
2030 /* conditionals clear the line which means if the %Vd is put into the default
2031 viewport there will be a blank line.
2032 To get around this we dont allow any actual drawing to happen in the
2033 deault vp if other vp's are defined */
2034 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2036 if (flags & WPS_REFRESH_SCROLL)
2038 /* if the line is a scrolling one we don't want to update
2039 too often, so that it has the time to scroll */
2040 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2041 write_line(display, &align, line - wps_vp->first_line, true);
2043 else
2044 write_line(display, &align, line - wps_vp->first_line, false);
2048 #ifdef HAVE_LCD_BITMAP
2049 /* progressbar */
2050 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2052 if (wps_vp->pb)
2054 draw_progressbar(gwps, wps_vp);
2057 /* Now display any images in this viewport */
2058 wps_display_images(gwps, &wps_vp->vp);
2059 #endif
2062 #ifdef HAVE_LCD_BITMAP
2063 data->peak_meter_enabled = enable_pm;
2064 #endif
2066 if (refresh_mode & WPS_REFRESH_STATUSBAR)
2068 gwps_draw_statusbars();
2070 /* Restore the default viewport */
2071 display->set_viewport(NULL);
2073 display->update();
2075 return true;