this file also...
[kugel-rb.git] / apps / gui / gwps-common.c
blobc4b0be1eb12eb16cd380b85b203fdb1ba4b48bf5
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)
272 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
273 #ifdef HAVE_TOUCHSCREEN
274 if (button == ACTION_TOUCHSCREEN)
275 button = wps_get_touchaction(gui_wps[SCREEN_MAIN].data);
276 #endif
279 return usb;
282 bool gui_wps_display(struct gui_wps *gwps)
284 struct screen *display = gwps->display;
285 struct wps_data *data = gwps->data;
286 int screen = display->screen_type;
288 /* Update the values in the first (default) viewport - in case the user
289 has modified the statusbar or colour settings */
290 #if LCD_DEPTH > 1
291 if (display->depth > 1)
293 data->viewports[0].vp.fg_pattern = display->get_foreground();
294 data->viewports[0].vp.bg_pattern = display->get_background();
296 #endif
297 display->clear_display();
298 if (!data->wps_loaded) {
299 if ( !data->num_tokens ) {
300 /* set the default wps for the main-screen */
301 if(screen == SCREEN_MAIN)
303 #if LCD_DEPTH > 1
304 unload_wps_backdrop();
305 #endif
306 wps_data_load(data,
307 display,
308 #ifdef HAVE_LCD_BITMAP
309 "%s%?it<%?in<%in. |>%it|%fn>\n"
310 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
311 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
312 "\n"
313 "%al%pc/%pt%ar[%pp:%pe]\n"
314 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
315 "%pb\n"
316 "%pm\n", false);
317 #else
318 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
319 "%pc%?ps<*|/>%pt\n", false);
320 #endif
322 #ifdef HAVE_REMOTE_LCD
323 /* set the default wps for the remote-screen */
324 else if(screen == SCREEN_REMOTE)
326 #if LCD_REMOTE_DEPTH > 1
327 unload_remote_wps_backdrop();
328 #endif
329 wps_data_load(data,
330 display,
331 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
332 "%s%?it<%?in<%in. |>%it|%fn>\n"
333 "%al%pc/%pt%ar[%pp:%pe]\n"
334 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
335 "%pb\n", false);
337 #endif
340 else
342 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
343 if (screen == SCREEN_REMOTE)
344 show_remote_wps_backdrop();
345 else if (screen == SCREEN_MAIN)
346 #endif
347 #if LCD_DEPTH > 1
348 show_wps_backdrop();
349 #endif
351 return gui_wps_redraw(gwps, 0, WPS_REFRESH_ALL);
354 bool gui_wps_update(struct gui_wps *gwps)
356 struct mp3entry *id3 = gwps->state->id3;
357 bool retval;
358 bool cuesheet_update = cuesheet_subtrack_changed(id3);
359 gwps->state->do_full_update = cuesheet_update || gwps->state->do_full_update;
360 retval = gui_wps_redraw(gwps, 0,
361 gwps->state->do_full_update ?
362 WPS_REFRESH_ALL : WPS_REFRESH_NON_STATIC);
363 return retval;
367 void display_keylock_text(bool locked)
369 int i;
370 FOR_NB_SCREENS(i)
371 gui_wps[i].display->stop_scroll();
373 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
376 #ifdef HAVE_LCD_BITMAP
378 static void draw_progressbar(struct gui_wps *gwps,
379 struct wps_viewport *wps_vp)
381 struct screen *display = gwps->display;
382 struct wps_state *state = gwps->state;
383 struct progressbar *pb = wps_vp->pb;
384 int y = pb->y;
386 if (y < 0)
388 int line_height = font_get(wps_vp->vp.font)->height;
389 /* center the pb in the line, but only if the line is higher than the pb */
390 int center = (line_height-pb->height)/2;
391 /* if Y was not set calculate by font height,Y is -line_number-1 */
392 y = (-y -1)*line_height + (0 > center ? 0 : center);
395 if (pb->have_bitmap_pb)
396 gui_bitmap_scrollbar_draw(display, pb->bm,
397 pb->x, y, pb->width, pb->bm.height,
398 state->id3->length ? state->id3->length : 1, 0,
399 state->id3->length ? state->id3->elapsed
400 + state->ff_rewind_count : 0,
401 HORIZONTAL);
402 else
403 gui_scrollbar_draw(display, pb->x, y, pb->width, pb->height,
404 state->id3->length ? state->id3->length : 1, 0,
405 state->id3->length ? state->id3->elapsed
406 + state->ff_rewind_count : 0,
407 HORIZONTAL);
408 #ifdef AB_REPEAT_ENABLE
409 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
410 ab_draw_markers(display, state->id3->length,
411 pb->x, pb->x + pb->width, y, pb->height);
412 #endif
414 if (state->id3->cuesheet)
415 cue_draw_markers(display, state->id3->length,
416 pb->x, pb->x + pb->width, y+1, pb->height-2);
419 /* clears the area where the image was shown */
420 static void clear_image_pos(struct gui_wps *gwps, int n)
422 if(!gwps)
423 return;
424 struct wps_data *data = gwps->data;
425 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
426 gwps->display->fillrect(data->img[n].x, data->img[n].y,
427 data->img[n].bm.width, data->img[n].subimage_height);
428 gwps->display->set_drawmode(DRMODE_SOLID);
431 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
433 struct screen *display = gwps->display;
434 struct wps_data *data = gwps->data;
435 if(data->img[n].always_display)
436 display->set_drawmode(DRMODE_FG);
437 else
438 display->set_drawmode(DRMODE_SOLID);
440 #if LCD_DEPTH > 1
441 if(data->img[n].bm.format == FORMAT_MONO) {
442 #endif
443 display->mono_bitmap_part(data->img[n].bm.data,
444 0, data->img[n].subimage_height * subimage,
445 data->img[n].bm.width, data->img[n].x,
446 data->img[n].y, data->img[n].bm.width,
447 data->img[n].subimage_height);
448 #if LCD_DEPTH > 1
449 } else {
450 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
451 0, data->img[n].subimage_height * subimage,
452 data->img[n].bm.width, data->img[n].x,
453 data->img[n].y, data->img[n].bm.width,
454 data->img[n].subimage_height);
456 #endif
459 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
461 if(!gwps || !gwps->data || !gwps->display)
462 return;
464 int n;
465 struct wps_data *data = gwps->data;
466 struct screen *display = gwps->display;
468 for (n = 0; n < MAX_IMAGES; n++)
470 if (data->img[n].loaded)
472 if (data->img[n].display >= 0)
474 wps_draw_image(gwps, n, data->img[n].display);
475 } else if (data->img[n].always_display && data->img[n].vp == vp)
477 wps_draw_image(gwps, n, 0);
481 display->set_drawmode(DRMODE_SOLID);
484 #else /* HAVE_LCD_CHARCELL */
486 static bool draw_player_progress(struct gui_wps *gwps)
488 struct wps_state *state = gwps->state;
489 struct screen *display = gwps->display;
490 unsigned char progress_pattern[7];
491 int pos = 0;
492 int i;
494 if (!state->id3)
495 return false;
497 if (state->id3->length)
498 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
499 / state->id3->length;
501 for (i = 0; i < 7; i++, pos -= 5)
503 if (pos <= 0)
504 progress_pattern[i] = 0x1fu;
505 else if (pos >= 5)
506 progress_pattern[i] = 0x00u;
507 else
508 progress_pattern[i] = 0x1fu >> pos;
511 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
512 return true;
515 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
517 static const unsigned char numbers[10][4] = {
518 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
519 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
520 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
521 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
522 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
523 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
524 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
525 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
526 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
527 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
530 struct wps_state *state = gwps->state;
531 struct screen *display = gwps->display;
532 struct wps_data *data = gwps->data;
533 unsigned char progress_pattern[7];
534 char timestr[10];
535 int time;
536 int time_idx = 0;
537 int pos = 0;
538 int pat_idx = 1;
539 int digit, i, j;
540 bool softchar;
542 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
543 return;
545 time = state->id3->elapsed + state->ff_rewind_count;
546 if (state->id3->length)
547 pos = 55 * time / state->id3->length;
549 memset(timestr, 0, sizeof(timestr));
550 format_time(timestr, sizeof(timestr)-2, time);
551 timestr[strlen(timestr)] = ':'; /* always safe */
553 for (i = 0; i < 11; i++, pos -= 5)
555 softchar = false;
556 memset(progress_pattern, 0, sizeof(progress_pattern));
558 if ((digit = timestr[time_idx]))
560 softchar = true;
561 digit -= '0';
563 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
565 memcpy(progress_pattern, numbers[digit], 4);
566 time_idx += 2;
568 else /* tens, shifted right */
570 for (j = 0; j < 4; j++)
571 progress_pattern[j] = numbers[digit][j] >> 1;
573 if (time_idx > 0) /* not the first group, add colon in front */
575 progress_pattern[1] |= 0x10u;
576 progress_pattern[3] |= 0x10u;
578 time_idx++;
581 if (pos >= 5)
582 progress_pattern[5] = progress_pattern[6] = 0x1fu;
585 if (pos > 0 && pos < 5)
587 softchar = true;
588 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
591 if (softchar && pat_idx < 8)
593 display->define_pattern(data->wps_progress_pat[pat_idx],
594 progress_pattern);
595 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
596 pat_idx++;
598 else if (pos <= 0)
599 buf = utf8encode(' ', buf);
600 else
601 buf = utf8encode(0xe115, buf); /* 2/7 _ */
603 *buf = '\0';
606 #endif /* HAVE_LCD_CHARCELL */
608 static char* get_codectype(const struct mp3entry* id3)
610 if (id3->codectype < AFMT_NUM_CODECS) {
611 return (char*)audio_formats[id3->codectype].label;
612 } else {
613 return NULL;
617 /* Extract a part from a path.
619 * buf - buffer extract part to.
620 * buf_size - size of buffer.
621 * path - path to extract from.
622 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
623 * parent of parent, etc.
625 * Returns buf if the desired level was found, NULL otherwise.
627 static char* get_dir(char* buf, int buf_size, const char* path, int level)
629 const char* sep;
630 const char* last_sep;
631 int len;
633 sep = path + strlen(path);
634 last_sep = sep;
636 while (sep > path)
638 if ('/' == *(--sep))
640 if (!level)
641 break;
643 level--;
644 last_sep = sep - 1;
648 if (level || (last_sep <= sep))
649 return NULL;
651 len = MIN(last_sep - sep, buf_size - 1);
652 strlcpy(buf, sep + 1, len + 1);
653 return buf;
656 /* Return the tag found at index i and write its value in buf.
657 The return value is buf if the tag had a value, or NULL if not.
659 intval is used with conditionals/enums: when this function is called,
660 intval should contain the number of options in the conditional/enum.
661 When this function returns, intval is -1 if the tag is non numeric or,
662 if the tag is numeric, *intval is the enum case we want to go to (between 1
663 and the original value of *intval, inclusive).
664 When not treating a conditional/enum, intval should be NULL.
666 static const char *get_token_value(struct gui_wps *gwps,
667 struct wps_token *token,
668 char *buf, int buf_size,
669 int *intval)
671 if (!gwps)
672 return NULL;
674 struct wps_data *data = gwps->data;
675 struct wps_state *state = gwps->state;
677 if (!data || !state)
678 return NULL;
680 struct mp3entry *id3;
682 if (token->next)
683 id3 = state->nid3;
684 else
685 id3 = state->id3;
687 if (!id3)
688 return NULL;
690 #if CONFIG_RTC
691 struct tm* tm = NULL;
693 /* if the token is an RTC one, update the time
694 and do the necessary checks */
695 if (token->type >= WPS_TOKENS_RTC_BEGIN
696 && token->type <= WPS_TOKENS_RTC_END)
698 tm = get_time();
700 if (!valid_time(tm))
701 return NULL;
703 #endif
705 int limit = 1;
706 if (intval)
708 limit = *intval;
709 *intval = -1;
712 switch (token->type)
714 case WPS_TOKEN_CHARACTER:
715 return &(token->value.c);
717 case WPS_TOKEN_STRING:
718 return data->strings[token->value.i];
720 case WPS_TOKEN_TRACK_TIME_ELAPSED:
721 format_time(buf, buf_size,
722 id3->elapsed + state->ff_rewind_count);
723 return buf;
725 case WPS_TOKEN_TRACK_TIME_REMAINING:
726 format_time(buf, buf_size,
727 id3->length - id3->elapsed -
728 state->ff_rewind_count);
729 return buf;
731 case WPS_TOKEN_TRACK_LENGTH:
732 format_time(buf, buf_size, id3->length);
733 return buf;
735 case WPS_TOKEN_PLAYLIST_ENTRIES:
736 snprintf(buf, buf_size, "%d", playlist_amount());
737 return buf;
739 case WPS_TOKEN_PLAYLIST_NAME:
740 return playlist_name(NULL, buf, buf_size);
742 case WPS_TOKEN_PLAYLIST_POSITION:
743 snprintf(buf, buf_size, "%d", playlist_get_display_index());
744 return buf;
746 case WPS_TOKEN_PLAYLIST_SHUFFLE:
747 if ( global_settings.playlist_shuffle )
748 return "s";
749 else
750 return NULL;
751 break;
753 case WPS_TOKEN_VOLUME:
754 snprintf(buf, buf_size, "%d", global_settings.volume);
755 if (intval)
757 if (global_settings.volume == sound_min(SOUND_VOLUME))
759 *intval = 1;
761 else if (global_settings.volume == 0)
763 *intval = limit - 1;
765 else if (global_settings.volume > 0)
767 *intval = limit;
769 else
771 *intval = (limit - 3) * (global_settings.volume
772 - sound_min(SOUND_VOLUME) - 1)
773 / (-1 - sound_min(SOUND_VOLUME)) + 2;
776 return buf;
778 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
779 if (id3->length <= 0)
780 return NULL;
782 if (intval)
784 *intval = limit * (id3->elapsed + state->ff_rewind_count)
785 / id3->length + 1;
787 snprintf(buf, buf_size, "%d",
788 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
789 return buf;
791 case WPS_TOKEN_METADATA_ARTIST:
792 return id3->artist;
794 case WPS_TOKEN_METADATA_COMPOSER:
795 return id3->composer;
797 case WPS_TOKEN_METADATA_ALBUM:
798 return id3->album;
800 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
801 return id3->albumartist;
803 case WPS_TOKEN_METADATA_GROUPING:
804 return id3->grouping;
806 case WPS_TOKEN_METADATA_GENRE:
807 return id3->genre_string;
809 case WPS_TOKEN_METADATA_DISC_NUMBER:
810 if (id3->disc_string)
811 return id3->disc_string;
812 if (id3->discnum) {
813 snprintf(buf, buf_size, "%d", id3->discnum);
814 return buf;
816 return NULL;
818 case WPS_TOKEN_METADATA_TRACK_NUMBER:
819 if (id3->track_string)
820 return id3->track_string;
822 if (id3->tracknum) {
823 snprintf(buf, buf_size, "%d", id3->tracknum);
824 return buf;
826 return NULL;
828 case WPS_TOKEN_METADATA_TRACK_TITLE:
829 return id3->title;
831 case WPS_TOKEN_METADATA_VERSION:
832 switch (id3->id3version)
834 case ID3_VER_1_0:
835 return "1";
837 case ID3_VER_1_1:
838 return "1.1";
840 case ID3_VER_2_2:
841 return "2.2";
843 case ID3_VER_2_3:
844 return "2.3";
846 case ID3_VER_2_4:
847 return "2.4";
849 default:
850 return NULL;
853 case WPS_TOKEN_METADATA_YEAR:
854 if( id3->year_string )
855 return id3->year_string;
857 if (id3->year) {
858 snprintf(buf, buf_size, "%d", id3->year);
859 return buf;
861 return NULL;
863 case WPS_TOKEN_METADATA_COMMENT:
864 return id3->comment;
866 #ifdef HAVE_ALBUMART
867 case WPS_TOKEN_ALBUMART_DISPLAY:
868 draw_album_art(gwps, audio_current_aa_hid(), false);
869 return NULL;
871 case WPS_TOKEN_ALBUMART_FOUND:
872 if (audio_current_aa_hid() >= 0) {
873 return "C";
875 return NULL;
876 #endif
878 case WPS_TOKEN_FILE_BITRATE:
879 if(id3->bitrate)
880 snprintf(buf, buf_size, "%d", id3->bitrate);
881 else
882 return "?";
883 return buf;
885 case WPS_TOKEN_FILE_CODEC:
886 if (intval)
888 if(id3->codectype == AFMT_UNKNOWN)
889 *intval = AFMT_NUM_CODECS;
890 else
891 *intval = id3->codectype;
893 return get_codectype(id3);
895 case WPS_TOKEN_FILE_FREQUENCY:
896 snprintf(buf, buf_size, "%ld", id3->frequency);
897 return buf;
899 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
900 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
901 if ((id3->frequency % 1000) < 100)
902 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
903 else
904 snprintf(buf, buf_size, "%ld.%d",
905 id3->frequency / 1000,
906 (id3->frequency % 1000) / 100);
907 return buf;
909 case WPS_TOKEN_FILE_NAME:
910 if (get_dir(buf, buf_size, id3->path, 0)) {
911 /* Remove extension */
912 char* sep = strrchr(buf, '.');
913 if (NULL != sep) {
914 *sep = 0;
916 return buf;
918 else {
919 return NULL;
922 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
923 return get_dir(buf, buf_size, id3->path, 0);
925 case WPS_TOKEN_FILE_PATH:
926 return id3->path;
928 case WPS_TOKEN_FILE_SIZE:
929 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
930 return buf;
932 case WPS_TOKEN_FILE_VBR:
933 return id3->vbr ? "(avg)" : NULL;
935 case WPS_TOKEN_FILE_DIRECTORY:
936 return get_dir(buf, buf_size, id3->path, token->value.i);
938 case WPS_TOKEN_BATTERY_PERCENT:
940 int l = battery_level();
942 if (intval)
944 limit = MAX(limit, 2);
945 if (l > -1) {
946 /* First enum is used for "unknown level". */
947 *intval = (limit - 1) * l / 100 + 2;
948 } else {
949 *intval = 1;
953 if (l > -1) {
954 snprintf(buf, buf_size, "%d", l);
955 return buf;
956 } else {
957 return "?";
961 case WPS_TOKEN_BATTERY_VOLTS:
963 unsigned int v = battery_voltage();
964 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
965 return buf;
968 case WPS_TOKEN_BATTERY_TIME:
970 int t = battery_time();
971 if (t >= 0)
972 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
973 else
974 return "?h ?m";
975 return buf;
978 #if CONFIG_CHARGING
979 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
981 if(charger_input_state==CHARGER)
982 return "p";
983 else
984 return NULL;
986 #endif
987 #if CONFIG_CHARGING >= CHARGING_MONITOR
988 case WPS_TOKEN_BATTERY_CHARGING:
990 if (charge_state == CHARGING || charge_state == TOPOFF) {
991 return "c";
992 } else {
993 return NULL;
996 #endif
997 case WPS_TOKEN_BATTERY_SLEEPTIME:
999 if (get_sleep_timer() == 0)
1000 return NULL;
1001 else
1003 format_time(buf, buf_size, get_sleep_timer() * 1000);
1004 return buf;
1008 case WPS_TOKEN_PLAYBACK_STATUS:
1010 int status = audio_status();
1011 int mode = 1;
1012 if (status == AUDIO_STATUS_PLAY)
1013 mode = 2;
1014 if (wps_fading_out ||
1015 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1016 mode = 3;
1017 if (status_get_ffmode() == STATUS_FASTFORWARD)
1018 mode = 4;
1019 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1020 mode = 5;
1022 if (intval) {
1023 *intval = mode;
1026 snprintf(buf, buf_size, "%d", mode-1);
1027 return buf;
1030 case WPS_TOKEN_REPEAT_MODE:
1031 if (intval)
1032 *intval = global_settings.repeat_mode + 1;
1033 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1034 return buf;
1036 case WPS_TOKEN_RTC_PRESENT:
1037 #if CONFIG_RTC
1038 return "c";
1039 #else
1040 return NULL;
1041 #endif
1043 #if CONFIG_RTC
1044 case WPS_TOKEN_RTC_12HOUR_CFG:
1045 if (intval)
1046 *intval = global_settings.timeformat + 1;
1047 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1048 return buf;
1050 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1051 /* d: day of month (01..31) */
1052 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1053 return buf;
1055 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1056 /* e: day of month, blank padded ( 1..31) */
1057 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1058 return buf;
1060 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1061 /* H: hour (00..23) */
1062 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1063 return buf;
1065 case WPS_TOKEN_RTC_HOUR_24:
1066 /* k: hour ( 0..23) */
1067 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1068 return buf;
1070 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1071 /* I: hour (01..12) */
1072 snprintf(buf, buf_size, "%02d",
1073 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1074 return buf;
1076 case WPS_TOKEN_RTC_HOUR_12:
1077 /* l: hour ( 1..12) */
1078 snprintf(buf, buf_size, "%2d",
1079 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1080 return buf;
1082 case WPS_TOKEN_RTC_MONTH:
1083 /* m: month (01..12) */
1084 if (intval)
1085 *intval = tm->tm_mon + 1;
1086 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1087 return buf;
1089 case WPS_TOKEN_RTC_MINUTE:
1090 /* M: minute (00..59) */
1091 snprintf(buf, buf_size, "%02d", tm->tm_min);
1092 return buf;
1094 case WPS_TOKEN_RTC_SECOND:
1095 /* S: second (00..59) */
1096 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1097 return buf;
1099 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1100 /* y: last two digits of year (00..99) */
1101 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1102 return buf;
1104 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1105 /* Y: year (1970...) */
1106 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1107 return buf;
1109 case WPS_TOKEN_RTC_AM_PM_UPPER:
1110 /* p: upper case AM or PM indicator */
1111 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1113 case WPS_TOKEN_RTC_AM_PM_LOWER:
1114 /* P: lower case am or pm indicator */
1115 return tm->tm_hour/12 == 0 ? "am" : "pm";
1117 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1118 /* a: abbreviated weekday name (Sun..Sat) */
1119 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1121 case WPS_TOKEN_RTC_MONTH_NAME:
1122 /* b: abbreviated month name (Jan..Dec) */
1123 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1125 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1126 /* u: day of week (1..7); 1 is Monday */
1127 if (intval)
1128 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1129 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1130 return buf;
1132 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1133 /* w: day of week (0..6); 0 is Sunday */
1134 if (intval)
1135 *intval = tm->tm_wday + 1;
1136 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1137 return buf;
1138 #else
1139 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1140 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1141 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1142 case WPS_TOKEN_RTC_HOUR_24:
1143 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1144 case WPS_TOKEN_RTC_HOUR_12:
1145 case WPS_TOKEN_RTC_MONTH:
1146 case WPS_TOKEN_RTC_MINUTE:
1147 case WPS_TOKEN_RTC_SECOND:
1148 case WPS_TOKEN_RTC_AM_PM_UPPER:
1149 case WPS_TOKEN_RTC_AM_PM_LOWER:
1150 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1151 return "--";
1152 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1153 return "----";
1154 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1155 case WPS_TOKEN_RTC_MONTH_NAME:
1156 return "---";
1157 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1158 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1159 return "-";
1160 #endif
1162 #ifdef HAVE_LCD_CHARCELLS
1163 case WPS_TOKEN_PROGRESSBAR:
1165 char *end = utf8encode(data->wps_progress_pat[0], buf);
1166 *end = '\0';
1167 return buf;
1170 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1171 if(is_new_player())
1173 /* we need 11 characters (full line) for
1174 progress-bar */
1175 strlcpy(buf, " ", buf_size);
1177 else
1179 /* Tell the user if we have an OldPlayer */
1180 strlcpy(buf, " <Old LCD> ", buf_size);
1182 return buf;
1183 #endif
1185 #ifdef HAVE_TAGCACHE
1186 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1187 if (intval) {
1188 *intval = id3->playcount + 1;
1190 snprintf(buf, buf_size, "%ld", id3->playcount);
1191 return buf;
1193 case WPS_TOKEN_DATABASE_RATING:
1194 if (intval) {
1195 *intval = id3->rating + 1;
1197 snprintf(buf, buf_size, "%d", id3->rating);
1198 return buf;
1200 case WPS_TOKEN_DATABASE_AUTOSCORE:
1201 if (intval)
1202 *intval = id3->score + 1;
1204 snprintf(buf, buf_size, "%d", id3->score);
1205 return buf;
1206 #endif
1208 #if (CONFIG_CODEC == SWCODEC)
1209 case WPS_TOKEN_CROSSFADE:
1210 if (intval)
1211 *intval = global_settings.crossfade + 1;
1212 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1213 return buf;
1215 case WPS_TOKEN_REPLAYGAIN:
1217 int val;
1219 if (global_settings.replaygain_type == REPLAYGAIN_OFF)
1220 val = 1; /* off */
1221 else
1223 int type =
1224 get_replaygain_mode(id3->track_gain_string != NULL,
1225 id3->album_gain_string != NULL);
1226 if (type < 0)
1227 val = 6; /* no tag */
1228 else
1229 val = type + 2;
1231 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1232 val += 2;
1235 if (intval)
1236 *intval = val;
1238 switch (val)
1240 case 1:
1241 case 6:
1242 return "+0.00 dB";
1243 break;
1244 case 2:
1245 case 4:
1246 strlcpy(buf, id3->track_gain_string, buf_size);
1247 break;
1248 case 3:
1249 case 5:
1250 strlcpy(buf, id3->album_gain_string, buf_size);
1251 break;
1253 return buf;
1255 #endif /* (CONFIG_CODEC == SWCODEC) */
1257 #if (CONFIG_CODEC != MAS3507D)
1258 case WPS_TOKEN_SOUND_PITCH:
1260 int val = sound_get_pitch();
1261 snprintf(buf, buf_size, "%d.%d",
1262 val / 10, val % 10);
1263 return buf;
1265 #endif
1267 case WPS_TOKEN_MAIN_HOLD:
1268 #ifdef HAS_BUTTON_HOLD
1269 if (button_hold())
1270 #else
1271 if (is_keys_locked())
1272 #endif /*hold switch or softlock*/
1273 return "h";
1274 else
1275 return NULL;
1277 #ifdef HAS_REMOTE_BUTTON_HOLD
1278 case WPS_TOKEN_REMOTE_HOLD:
1279 if (remote_button_hold())
1280 return "r";
1281 else
1282 return NULL;
1283 #endif
1285 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1286 case WPS_TOKEN_VLED_HDD:
1287 if(led_read(HZ/2))
1288 return "h";
1289 else
1290 return NULL;
1291 #endif
1292 case WPS_TOKEN_BUTTON_VOLUME:
1293 if (data->button_time_volume &&
1294 TIME_BEFORE(current_tick, data->button_time_volume +
1295 token->value.i * TIMEOUT_UNIT))
1296 return "v";
1297 return NULL;
1298 case WPS_TOKEN_LASTTOUCH:
1299 #ifdef HAVE_TOUCHSCREEN
1300 if (TIME_BEFORE(current_tick, token->value.i * TIMEOUT_UNIT +
1301 touchscreen_last_touch()))
1302 return "t";
1303 #endif
1304 return NULL;
1306 case WPS_TOKEN_SETTING:
1308 if (intval)
1310 /* Handle contionals */
1311 const struct settings_list *s = settings+token->value.i;
1312 switch (s->flags&F_T_MASK)
1314 case F_T_INT:
1315 case F_T_UINT:
1316 if (s->flags&F_RGB)
1317 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1318 /* shouldn't overflow since colors are stored
1319 * on 16 bits ...
1320 * but this is pretty useless anyway */
1321 *intval = *(int*)s->setting + 1;
1322 else if (s->cfg_vals == NULL)
1323 /* %?St|name|<1st choice|2nd choice|...> */
1324 *intval = (*(int*)s->setting-s->int_setting->min)
1325 /s->int_setting->step + 1;
1326 else
1327 /* %?St|name|<1st choice|2nd choice|...> */
1328 /* Not sure about this one. cfg_name/vals are
1329 * indexed from 0 right? */
1330 *intval = *(int*)s->setting + 1;
1331 break;
1332 case F_T_BOOL:
1333 /* %?St|name|<if true|if false> */
1334 *intval = *(bool*)s->setting?1:2;
1335 break;
1336 case F_T_CHARPTR:
1337 /* %?St|name|<if non empty string|if empty>
1338 * The string's emptyness discards the setting's
1339 * prefix and suffix */
1340 *intval = ((char*)s->setting)[0]?1:2;
1341 break;
1342 default:
1343 /* This shouldn't happen ... but you never know */
1344 *intval = -1;
1345 break;
1348 cfg_to_string(token->value.i,buf,buf_size);
1349 return buf;
1352 default:
1353 return NULL;
1357 /* Return the index to the end token for the conditional token at index.
1358 The conditional token can be either a start token or a separator
1359 (i.e. option) token.
1361 static int find_conditional_end(struct wps_data *data, int index)
1363 int ret = index;
1364 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1365 ret = data->tokens[ret].value.i;
1367 /* ret now is the index to the end token for the conditional. */
1368 return ret;
1371 /* Evaluate the conditional that is at *token_index and return whether a skip
1372 has ocurred. *token_index is updated with the new position.
1374 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1376 if (!gwps)
1377 return false;
1379 struct wps_data *data = gwps->data;
1381 int i, cond_end;
1382 int cond_index = *token_index;
1383 char result[128];
1384 const char *value;
1385 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1386 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1388 /* treat ?xx<true> constructs as if they had 2 options. */
1389 if (num_options < 2)
1390 num_options = 2;
1392 int intval = num_options;
1393 /* get_token_value needs to know the number of options in the enum */
1394 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1395 result, sizeof(result), &intval);
1397 /* intval is now the number of the enum option we want to read,
1398 starting from 1. If intval is -1, we check if value is empty. */
1399 if (intval == -1)
1400 intval = (value && *value) ? 1 : num_options;
1401 else if (intval > num_options || intval < 1)
1402 intval = num_options;
1404 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1406 /* skip to the appropriate enum case */
1407 int next = cond_index + 2;
1408 for (i = 1; i < intval; i++)
1410 next = data->tokens[next].value.i;
1412 *token_index = next;
1414 if (prev_val == intval)
1416 /* Same conditional case as previously. Return without clearing the
1417 pictures */
1418 return false;
1421 cond_end = find_conditional_end(data, cond_index + 2);
1422 for (i = cond_index + 3; i < cond_end; i++)
1424 #ifdef HAVE_LCD_BITMAP
1425 /* clear all pictures in the conditional and nested ones */
1426 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1427 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1428 #endif
1429 #ifdef HAVE_ALBUMART
1430 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1431 draw_album_art(gwps, audio_current_aa_hid(), true);
1432 #endif
1435 return true;
1438 /* Read a (sub)line to the given alignment format buffer.
1439 linebuf is the buffer where the data is actually stored.
1440 align is the alignment format that'll be used to display the text.
1441 The return value indicates whether the line needs to be updated.
1443 static bool get_line(struct gui_wps *gwps,
1444 int line, int subline,
1445 struct align_pos *align,
1446 char *linebuf,
1447 int linebuf_size)
1449 struct wps_data *data = gwps->data;
1451 char temp_buf[128];
1452 char *buf = linebuf; /* will always point to the writing position */
1453 char *linebuf_end = linebuf + linebuf_size - 1;
1454 int i, last_token_idx;
1455 bool update = false;
1457 /* alignment-related variables */
1458 int cur_align;
1459 char* cur_align_start;
1460 cur_align_start = buf;
1461 cur_align = WPS_ALIGN_LEFT;
1462 align->left = NULL;
1463 align->center = NULL;
1464 align->right = NULL;
1466 /* Process all tokens of the desired subline */
1467 last_token_idx = wps_last_token_index(data, line, subline);
1468 for (i = wps_first_token_index(data, line, subline);
1469 i <= last_token_idx; i++)
1471 switch(data->tokens[i].type)
1473 case WPS_TOKEN_CONDITIONAL:
1474 /* place ourselves in the right conditional case */
1475 update |= evaluate_conditional(gwps, &i);
1476 break;
1478 case WPS_TOKEN_CONDITIONAL_OPTION:
1479 /* we've finished in the curent conditional case,
1480 skip to the end of the conditional structure */
1481 i = find_conditional_end(data, i);
1482 break;
1484 #ifdef HAVE_LCD_BITMAP
1485 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1487 struct gui_img *img = data->img;
1488 int n = data->tokens[i].value.i & 0xFF;
1489 int subimage = data->tokens[i].value.i >> 8;
1491 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1492 img[n].display = subimage;
1493 break;
1495 #endif
1497 case WPS_TOKEN_ALIGN_LEFT:
1498 case WPS_TOKEN_ALIGN_CENTER:
1499 case WPS_TOKEN_ALIGN_RIGHT:
1500 /* remember where the current aligned text started */
1501 switch (cur_align)
1503 case WPS_ALIGN_LEFT:
1504 align->left = cur_align_start;
1505 break;
1507 case WPS_ALIGN_CENTER:
1508 align->center = cur_align_start;
1509 break;
1511 case WPS_ALIGN_RIGHT:
1512 align->right = cur_align_start;
1513 break;
1515 /* start a new alignment */
1516 switch (data->tokens[i].type)
1518 case WPS_TOKEN_ALIGN_LEFT:
1519 cur_align = WPS_ALIGN_LEFT;
1520 break;
1521 case WPS_TOKEN_ALIGN_CENTER:
1522 cur_align = WPS_ALIGN_CENTER;
1523 break;
1524 case WPS_TOKEN_ALIGN_RIGHT:
1525 cur_align = WPS_ALIGN_RIGHT;
1526 break;
1527 default:
1528 break;
1530 *buf++ = 0;
1531 cur_align_start = buf;
1532 break;
1533 case WPS_VIEWPORT_ENABLE:
1535 char label = data->tokens[i].value.i;
1536 int j;
1537 char temp = VP_DRAW_HIDEABLE;
1538 for(j=0;j<data->num_viewports;j++)
1540 temp = VP_DRAW_HIDEABLE;
1541 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1542 (data->viewports[j].label == label))
1544 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1545 temp |= VP_DRAW_WASHIDDEN;
1546 data->viewports[j].hidden_flags = temp;
1550 break;
1551 default:
1553 /* get the value of the tag and copy it to the buffer */
1554 const char *value = get_token_value(gwps, &data->tokens[i],
1555 temp_buf, sizeof(temp_buf), NULL);
1556 if (value)
1558 update = true;
1559 while (*value && (buf < linebuf_end))
1560 *buf++ = *value++;
1562 break;
1567 /* close the current alignment */
1568 switch (cur_align)
1570 case WPS_ALIGN_LEFT:
1571 align->left = cur_align_start;
1572 break;
1574 case WPS_ALIGN_CENTER:
1575 align->center = cur_align_start;
1576 break;
1578 case WPS_ALIGN_RIGHT:
1579 align->right = cur_align_start;
1580 break;
1583 return update;
1586 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1588 struct wps_data *data = gwps->data;
1589 int i;
1590 int subline_idx = wps_subline_index(data, line, subline);
1591 int last_token_idx = wps_last_token_index(data, line, subline);
1593 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1595 for (i = wps_first_token_index(data, line, subline);
1596 i <= last_token_idx; i++)
1598 switch(data->tokens[i].type)
1600 case WPS_TOKEN_CONDITIONAL:
1601 /* place ourselves in the right conditional case */
1602 evaluate_conditional(gwps, &i);
1603 break;
1605 case WPS_TOKEN_CONDITIONAL_OPTION:
1606 /* we've finished in the curent conditional case,
1607 skip to the end of the conditional structure */
1608 i = find_conditional_end(data, i);
1609 break;
1611 case WPS_TOKEN_SUBLINE_TIMEOUT:
1612 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1613 break;
1615 default:
1616 break;
1621 /* Calculates which subline should be displayed for the specified line
1622 Returns true iff the subline must be refreshed */
1623 static bool update_curr_subline(struct gui_wps *gwps, int line)
1625 struct wps_data *data = gwps->data;
1627 int search, search_start, num_sublines;
1628 bool reset_subline;
1629 bool new_subline_refresh;
1630 bool only_one_subline;
1632 num_sublines = data->lines[line].num_sublines;
1633 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1634 new_subline_refresh = false;
1635 only_one_subline = false;
1637 /* if time to advance to next sub-line */
1638 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1639 reset_subline)
1641 /* search all sublines until the next subline with time > 0
1642 is found or we get back to the subline we started with */
1643 if (reset_subline)
1644 search_start = 0;
1645 else
1646 search_start = data->lines[line].curr_subline;
1648 for (search = 0; search < num_sublines; search++)
1650 data->lines[line].curr_subline++;
1652 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1653 if (data->lines[line].curr_subline == num_sublines)
1655 if (data->lines[line].curr_subline == 1)
1656 only_one_subline = true;
1657 data->lines[line].curr_subline = 0;
1660 /* if back where we started after search or
1661 only one subline is defined on the line */
1662 if (((search > 0) &&
1663 (data->lines[line].curr_subline == search_start)) ||
1664 only_one_subline)
1666 /* no other subline with a time > 0 exists */
1667 data->lines[line].subline_expire_time = (reset_subline ?
1668 current_tick :
1669 data->lines[line].subline_expire_time) + 100 * HZ;
1670 break;
1672 else
1674 /* get initial time multiplier for this subline */
1675 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1677 int subline_idx = wps_subline_index(data, line,
1678 data->lines[line].curr_subline);
1680 /* only use this subline if subline time > 0 */
1681 if (data->sublines[subline_idx].time_mult > 0)
1683 new_subline_refresh = true;
1684 data->lines[line].subline_expire_time = (reset_subline ?
1685 current_tick : data->lines[line].subline_expire_time) +
1686 TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
1687 break;
1693 return new_subline_refresh;
1696 /* Display a line appropriately according to its alignment format.
1697 format_align contains the text, separated between left, center and right.
1698 line is the index of the line on the screen.
1699 scroll indicates whether the line is a scrolling one or not.
1701 static void write_line(struct screen *display,
1702 struct align_pos *format_align,
1703 int line,
1704 bool scroll)
1706 int left_width = 0, left_xpos;
1707 int center_width = 0, center_xpos;
1708 int right_width = 0, right_xpos;
1709 int ypos;
1710 int space_width;
1711 int string_height;
1712 int scroll_width;
1714 /* calculate different string sizes and positions */
1715 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1716 if (format_align->left != 0) {
1717 display->getstringsize((unsigned char *)format_align->left,
1718 &left_width, &string_height);
1721 if (format_align->right != 0) {
1722 display->getstringsize((unsigned char *)format_align->right,
1723 &right_width, &string_height);
1726 if (format_align->center != 0) {
1727 display->getstringsize((unsigned char *)format_align->center,
1728 &center_width, &string_height);
1731 left_xpos = 0;
1732 right_xpos = (display->getwidth() - right_width);
1733 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1735 scroll_width = display->getwidth() - left_xpos;
1737 /* Checks for overlapping strings.
1738 If needed the overlapping strings will be merged, separated by a
1739 space */
1741 /* CASE 1: left and centered string overlap */
1742 /* there is a left string, need to merge left and center */
1743 if ((left_width != 0 && center_width != 0) &&
1744 (left_xpos + left_width + space_width > center_xpos)) {
1745 /* replace the former separator '\0' of left and
1746 center string with a space */
1747 *(--format_align->center) = ' ';
1748 /* calculate the new width and position of the merged string */
1749 left_width = left_width + space_width + center_width;
1750 /* there is no centered string anymore */
1751 center_width = 0;
1753 /* there is no left string, move center to left */
1754 if ((left_width == 0 && center_width != 0) &&
1755 (left_xpos + left_width > center_xpos)) {
1756 /* move the center string to the left string */
1757 format_align->left = format_align->center;
1758 /* calculate the new width and position of the string */
1759 left_width = center_width;
1760 /* there is no centered string anymore */
1761 center_width = 0;
1764 /* CASE 2: centered and right string overlap */
1765 /* there is a right string, need to merge center and right */
1766 if ((center_width != 0 && right_width != 0) &&
1767 (center_xpos + center_width + space_width > right_xpos)) {
1768 /* replace the former separator '\0' of center and
1769 right string with a space */
1770 *(--format_align->right) = ' ';
1771 /* move the center string to the right after merge */
1772 format_align->right = format_align->center;
1773 /* calculate the new width and position of the merged string */
1774 right_width = center_width + space_width + right_width;
1775 right_xpos = (display->getwidth() - right_width);
1776 /* there is no centered string anymore */
1777 center_width = 0;
1779 /* there is no right string, move center to right */
1780 if ((center_width != 0 && right_width == 0) &&
1781 (center_xpos + center_width > right_xpos)) {
1782 /* move the center string to the right string */
1783 format_align->right = format_align->center;
1784 /* calculate the new width and position of the string */
1785 right_width = center_width;
1786 right_xpos = (display->getwidth() - right_width);
1787 /* there is no centered string anymore */
1788 center_width = 0;
1791 /* CASE 3: left and right overlap
1792 There is no center string anymore, either there never
1793 was one or it has been merged in case 1 or 2 */
1794 /* there is a left string, need to merge left and right */
1795 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1796 (left_xpos + left_width + space_width > right_xpos)) {
1797 /* replace the former separator '\0' of left and
1798 right string with a space */
1799 *(--format_align->right) = ' ';
1800 /* calculate the new width and position of the string */
1801 left_width = left_width + space_width + right_width;
1802 /* there is no right string anymore */
1803 right_width = 0;
1805 /* there is no left string, move right to left */
1806 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1807 (left_width > right_xpos)) {
1808 /* move the right string to the left string */
1809 format_align->left = format_align->right;
1810 /* calculate the new width and position of the string */
1811 left_width = right_width;
1812 /* there is no right string anymore */
1813 right_width = 0;
1816 ypos = (line * string_height);
1819 if (scroll && ((left_width > scroll_width) ||
1820 (center_width > scroll_width) ||
1821 (right_width > scroll_width)))
1823 display->puts_scroll(0, line,
1824 (unsigned char *)format_align->left);
1826 else
1828 #ifdef HAVE_LCD_BITMAP
1829 /* clear the line first */
1830 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1831 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1832 display->set_drawmode(DRMODE_SOLID);
1833 #endif
1835 /* Nasty hack: we output an empty scrolling string,
1836 which will reset the scroller for that line */
1837 display->puts_scroll(0, line, (unsigned char *)"");
1839 /* print aligned strings */
1840 if (left_width != 0)
1842 display->putsxy(left_xpos, ypos,
1843 (unsigned char *)format_align->left);
1845 if (center_width != 0)
1847 display->putsxy(center_xpos, ypos,
1848 (unsigned char *)format_align->center);
1850 if (right_width != 0)
1852 display->putsxy(right_xpos, ypos,
1853 (unsigned char *)format_align->right);
1858 bool gui_wps_redraw(struct gui_wps *gwps,
1859 int ffwd_offset,
1860 unsigned refresh_mode)
1862 struct wps_data *data = gwps->data;
1863 struct screen *display = gwps->display;
1864 struct wps_state *state = gwps->state;
1866 if (!data || !state || !display)
1867 return false;
1869 struct mp3entry *id3 = state->id3;
1871 if (!id3)
1872 return false;
1874 int v, line, i, subline_idx;
1875 unsigned flags;
1876 char linebuf[MAX_PATH];
1878 struct align_pos align;
1879 align.left = NULL;
1880 align.center = NULL;
1881 align.right = NULL;
1883 bool update_line, new_subline_refresh;
1885 #ifdef HAVE_LCD_BITMAP
1887 /* to find out wether the peak meter is enabled we
1888 assume it wasn't until we find a line that contains
1889 the peak meter. We can't use peak_meter_enabled itself
1890 because that would mean to turn off the meter thread
1891 temporarily. (That shouldn't matter unless yield
1892 or sleep is called but who knows...)
1894 bool enable_pm = false;
1896 #endif
1898 /* reset to first subline if refresh all flag is set */
1899 if (refresh_mode == WPS_REFRESH_ALL)
1901 display->set_viewport(&data->viewports[0].vp);
1902 display->clear_viewport();
1904 for (i = 0; i <= data->num_lines; i++)
1906 data->lines[i].curr_subline = SUBLINE_RESET;
1910 #ifdef HAVE_LCD_CHARCELLS
1911 for (i = 0; i < 8; i++)
1913 if (data->wps_progress_pat[i] == 0)
1914 data->wps_progress_pat[i] = display->get_locked_pattern();
1916 #endif
1918 state->ff_rewind_count = ffwd_offset;
1920 /* disable any viewports which are conditionally displayed */
1921 for (v = 0; v < data->num_viewports; v++)
1923 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
1925 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
1926 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1927 else
1928 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
1931 for (v = 0; v < data->num_viewports; v++)
1933 struct wps_viewport *wps_vp = &(data->viewports[v]);
1934 unsigned vp_refresh_mode = refresh_mode;
1935 display->set_viewport(&wps_vp->vp);
1937 #ifdef HAVE_LCD_BITMAP
1938 /* Set images to not to be displayed */
1939 for (i = 0; i < MAX_IMAGES; i++)
1941 data->img[i].display = -1;
1943 #endif
1944 /* dont redraw the viewport if its disabled */
1945 if ((wps_vp->hidden_flags&VP_DRAW_HIDDEN))
1947 if (!(wps_vp->hidden_flags&VP_DRAW_WASHIDDEN))
1948 display->scroll_stop(&wps_vp->vp);
1949 wps_vp->hidden_flags |= VP_DRAW_WASHIDDEN;
1950 continue;
1952 else if (((wps_vp->hidden_flags&
1953 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1954 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1956 vp_refresh_mode = WPS_REFRESH_ALL;
1957 wps_vp->hidden_flags = VP_DRAW_HIDEABLE;
1959 if (vp_refresh_mode == WPS_REFRESH_ALL)
1961 display->clear_viewport();
1964 for (line = wps_vp->first_line;
1965 line <= wps_vp->last_line; line++)
1967 memset(linebuf, 0, sizeof(linebuf));
1968 update_line = false;
1970 /* get current subline for the line */
1971 new_subline_refresh = update_curr_subline(gwps, line);
1973 subline_idx = wps_subline_index(data, line,
1974 data->lines[line].curr_subline);
1975 flags = data->sublines[subline_idx].line_type;
1977 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1978 || new_subline_refresh)
1980 /* get_line tells us if we need to update the line */
1981 update_line = get_line(gwps, line, data->lines[line].curr_subline,
1982 &align, linebuf, sizeof(linebuf));
1984 #ifdef HAVE_LCD_BITMAP
1985 /* peakmeter */
1986 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1988 /* the peakmeter should be alone on its line */
1989 update_line = false;
1991 int h = font_get(wps_vp->vp.font)->height;
1992 int peak_meter_y = (line - wps_vp->first_line)* h;
1994 /* The user might decide to have the peak meter in the last
1995 line so that it is only displayed if no status bar is
1996 visible. If so we neither want do draw nor enable the
1997 peak meter. */
1998 if (peak_meter_y + h <= display->getheight()) {
1999 /* found a line with a peak meter -> remember that we must
2000 enable it later */
2001 enable_pm = true;
2002 peak_meter_enabled = true;
2003 peak_meter_screen(gwps->display, 0, peak_meter_y,
2004 MIN(h, display->getheight() - peak_meter_y));
2006 else
2008 peak_meter_enabled = false;
2012 #else /* HAVE_LCD_CHARCELL */
2014 /* progressbar */
2015 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2017 if (data->full_line_progressbar)
2018 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2019 else
2020 draw_player_progress(gwps);
2022 #endif
2024 if (update_line &&
2025 /* conditionals clear the line which means if the %Vd is put into the default
2026 viewport there will be a blank line.
2027 To get around this we dont allow any actual drawing to happen in the
2028 deault vp if other vp's are defined */
2029 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2031 if (flags & WPS_REFRESH_SCROLL)
2033 /* if the line is a scrolling one we don't want to update
2034 too often, so that it has the time to scroll */
2035 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2036 write_line(display, &align, line - wps_vp->first_line, true);
2038 else
2039 write_line(display, &align, line - wps_vp->first_line, false);
2043 #ifdef HAVE_LCD_BITMAP
2044 /* progressbar */
2045 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2047 if (wps_vp->pb)
2049 draw_progressbar(gwps, wps_vp);
2052 /* Now display any images in this viewport */
2053 wps_display_images(gwps, &wps_vp->vp);
2054 #endif
2057 #ifdef HAVE_LCD_BITMAP
2058 data->peak_meter_enabled = enable_pm;
2059 #endif
2061 if (refresh_mode & WPS_REFRESH_STATUSBAR)
2063 gwps_draw_statusbars();
2065 /* Restore the default viewport */
2066 display->set_viewport(NULL);
2068 display->update();
2070 return true;