Fix the bug where the short-long fwd/back action would ffwd/rewind the next folder...
[kugel-rb.git] / apps / gui / gwps-common.c
blobf480f616a2f3d79bb841b177f53f4b5b9a4b2c0c
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 if (cuesheet_is_enabled() && id3->cuesheet_type
359 && (id3->elapsed < curr_cue->curr_track->offset
360 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
361 && id3->elapsed >= (curr_cue->curr_track+1)->offset)))
363 /* We've changed tracks within the cuesheet :
364 we need to update the ID3 info and refresh the WPS */
365 gwps->state->do_full_update = true;
366 cue_find_current_track(curr_cue, id3->elapsed);
367 cue_spoof_id3(curr_cue, id3);
370 retval = gui_wps_redraw(gwps, 0,
371 gwps->state->do_full_update ?
372 WPS_REFRESH_ALL : WPS_REFRESH_NON_STATIC);
373 return retval;
377 void display_keylock_text(bool locked)
379 int i;
380 FOR_NB_SCREENS(i)
381 gui_wps[i].display->stop_scroll();
383 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
386 #ifdef HAVE_LCD_BITMAP
388 static void draw_progressbar(struct gui_wps *gwps,
389 struct wps_viewport *wps_vp)
391 struct screen *display = gwps->display;
392 struct wps_state *state = gwps->state;
393 struct progressbar *pb = wps_vp->pb;
394 int y = pb->y;
396 if (y < 0)
398 int line_height = font_get(wps_vp->vp.font)->height;
399 /* center the pb in the line, but only if the line is higher than the pb */
400 int center = (line_height-pb->height)/2;
401 /* if Y was not set calculate by font height,Y is -line_number-1 */
402 y = (-y -1)*line_height + (0 > center ? 0 : center);
405 if (pb->have_bitmap_pb)
406 gui_bitmap_scrollbar_draw(display, pb->bm,
407 pb->x, y, pb->width, pb->bm.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 else
413 gui_scrollbar_draw(display, pb->x, y, pb->width, pb->height,
414 state->id3->length ? state->id3->length : 1, 0,
415 state->id3->length ? state->id3->elapsed
416 + state->ff_rewind_count : 0,
417 HORIZONTAL);
418 #ifdef AB_REPEAT_ENABLE
419 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
420 ab_draw_markers(display, state->id3->length,
421 pb->x, pb->x + pb->width, y, pb->height);
422 #endif
424 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
425 cue_draw_markers(display, state->id3->length,
426 pb->x, pb->x + pb->width, y+1, pb->height-2);
429 /* clears the area where the image was shown */
430 static void clear_image_pos(struct gui_wps *gwps, int n)
432 if(!gwps)
433 return;
434 struct wps_data *data = gwps->data;
435 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
436 gwps->display->fillrect(data->img[n].x, data->img[n].y,
437 data->img[n].bm.width, data->img[n].subimage_height);
438 gwps->display->set_drawmode(DRMODE_SOLID);
441 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
443 struct screen *display = gwps->display;
444 struct wps_data *data = gwps->data;
445 if(data->img[n].always_display)
446 display->set_drawmode(DRMODE_FG);
447 else
448 display->set_drawmode(DRMODE_SOLID);
450 #if LCD_DEPTH > 1
451 if(data->img[n].bm.format == FORMAT_MONO) {
452 #endif
453 display->mono_bitmap_part(data->img[n].bm.data,
454 0, data->img[n].subimage_height * subimage,
455 data->img[n].bm.width, data->img[n].x,
456 data->img[n].y, data->img[n].bm.width,
457 data->img[n].subimage_height);
458 #if LCD_DEPTH > 1
459 } else {
460 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
461 0, data->img[n].subimage_height * subimage,
462 data->img[n].bm.width, data->img[n].x,
463 data->img[n].y, data->img[n].bm.width,
464 data->img[n].subimage_height);
466 #endif
469 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
471 if(!gwps || !gwps->data || !gwps->display)
472 return;
474 int n;
475 struct wps_data *data = gwps->data;
476 struct screen *display = gwps->display;
478 for (n = 0; n < MAX_IMAGES; n++)
480 if (data->img[n].loaded)
482 if (data->img[n].display >= 0)
484 wps_draw_image(gwps, n, data->img[n].display);
485 } else if (data->img[n].always_display && data->img[n].vp == vp)
487 wps_draw_image(gwps, n, 0);
491 display->set_drawmode(DRMODE_SOLID);
494 #else /* HAVE_LCD_CHARCELL */
496 static bool draw_player_progress(struct gui_wps *gwps)
498 struct wps_state *state = gwps->state;
499 struct screen *display = gwps->display;
500 unsigned char progress_pattern[7];
501 int pos = 0;
502 int i;
504 if (!state->id3)
505 return false;
507 if (state->id3->length)
508 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
509 / state->id3->length;
511 for (i = 0; i < 7; i++, pos -= 5)
513 if (pos <= 0)
514 progress_pattern[i] = 0x1fu;
515 else if (pos >= 5)
516 progress_pattern[i] = 0x00u;
517 else
518 progress_pattern[i] = 0x1fu >> pos;
521 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
522 return true;
525 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
527 static const unsigned char numbers[10][4] = {
528 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
529 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
530 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
531 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
532 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
533 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
534 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
535 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
536 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
537 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
540 struct wps_state *state = gwps->state;
541 struct screen *display = gwps->display;
542 struct wps_data *data = gwps->data;
543 unsigned char progress_pattern[7];
544 char timestr[10];
545 int time;
546 int time_idx = 0;
547 int pos = 0;
548 int pat_idx = 1;
549 int digit, i, j;
550 bool softchar;
552 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
553 return;
555 time = state->id3->elapsed + state->ff_rewind_count;
556 if (state->id3->length)
557 pos = 55 * time / state->id3->length;
559 memset(timestr, 0, sizeof(timestr));
560 format_time(timestr, sizeof(timestr)-2, time);
561 timestr[strlen(timestr)] = ':'; /* always safe */
563 for (i = 0; i < 11; i++, pos -= 5)
565 softchar = false;
566 memset(progress_pattern, 0, sizeof(progress_pattern));
568 if ((digit = timestr[time_idx]))
570 softchar = true;
571 digit -= '0';
573 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
575 memcpy(progress_pattern, numbers[digit], 4);
576 time_idx += 2;
578 else /* tens, shifted right */
580 for (j = 0; j < 4; j++)
581 progress_pattern[j] = numbers[digit][j] >> 1;
583 if (time_idx > 0) /* not the first group, add colon in front */
585 progress_pattern[1] |= 0x10u;
586 progress_pattern[3] |= 0x10u;
588 time_idx++;
591 if (pos >= 5)
592 progress_pattern[5] = progress_pattern[6] = 0x1fu;
595 if (pos > 0 && pos < 5)
597 softchar = true;
598 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
601 if (softchar && pat_idx < 8)
603 display->define_pattern(data->wps_progress_pat[pat_idx],
604 progress_pattern);
605 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
606 pat_idx++;
608 else if (pos <= 0)
609 buf = utf8encode(' ', buf);
610 else
611 buf = utf8encode(0xe115, buf); /* 2/7 _ */
613 *buf = '\0';
616 #endif /* HAVE_LCD_CHARCELL */
618 static char* get_codectype(const struct mp3entry* id3)
620 if (id3->codectype < AFMT_NUM_CODECS) {
621 return (char*)audio_formats[id3->codectype].label;
622 } else {
623 return NULL;
627 /* Extract a part from a path.
629 * buf - buffer extract part to.
630 * buf_size - size of buffer.
631 * path - path to extract from.
632 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
633 * parent of parent, etc.
635 * Returns buf if the desired level was found, NULL otherwise.
637 static char* get_dir(char* buf, int buf_size, const char* path, int level)
639 const char* sep;
640 const char* last_sep;
641 int len;
643 sep = path + strlen(path);
644 last_sep = sep;
646 while (sep > path)
648 if ('/' == *(--sep))
650 if (!level)
651 break;
653 level--;
654 last_sep = sep - 1;
658 if (level || (last_sep <= sep))
659 return NULL;
661 len = MIN(last_sep - sep, buf_size - 1);
662 strncpy(buf, sep + 1, len);
663 buf[len] = 0;
664 return buf;
667 /* Return the tag found at index i and write its value in buf.
668 The return value is buf if the tag had a value, or NULL if not.
670 intval is used with conditionals/enums: when this function is called,
671 intval should contain the number of options in the conditional/enum.
672 When this function returns, intval is -1 if the tag is non numeric or,
673 if the tag is numeric, *intval is the enum case we want to go to (between 1
674 and the original value of *intval, inclusive).
675 When not treating a conditional/enum, intval should be NULL.
677 static const char *get_token_value(struct gui_wps *gwps,
678 struct wps_token *token,
679 char *buf, int buf_size,
680 int *intval)
682 if (!gwps)
683 return NULL;
685 struct wps_data *data = gwps->data;
686 struct wps_state *state = gwps->state;
688 if (!data || !state)
689 return NULL;
691 struct mp3entry *id3;
693 if (token->next)
694 id3 = state->nid3;
695 else
696 id3 = state->id3;
698 if (!id3)
699 return NULL;
701 #if CONFIG_RTC
702 struct tm* tm = NULL;
704 /* if the token is an RTC one, update the time
705 and do the necessary checks */
706 if (token->type >= WPS_TOKENS_RTC_BEGIN
707 && token->type <= WPS_TOKENS_RTC_END)
709 tm = get_time();
711 if (!valid_time(tm))
712 return NULL;
714 #endif
716 int limit = 1;
717 if (intval)
719 limit = *intval;
720 *intval = -1;
723 switch (token->type)
725 case WPS_TOKEN_CHARACTER:
726 return &(token->value.c);
728 case WPS_TOKEN_STRING:
729 return data->strings[token->value.i];
731 case WPS_TOKEN_TRACK_TIME_ELAPSED:
732 format_time(buf, buf_size,
733 id3->elapsed + state->ff_rewind_count);
734 return buf;
736 case WPS_TOKEN_TRACK_TIME_REMAINING:
737 format_time(buf, buf_size,
738 id3->length - id3->elapsed -
739 state->ff_rewind_count);
740 return buf;
742 case WPS_TOKEN_TRACK_LENGTH:
743 format_time(buf, buf_size, id3->length);
744 return buf;
746 case WPS_TOKEN_PLAYLIST_ENTRIES:
747 snprintf(buf, buf_size, "%d", playlist_amount());
748 return buf;
750 case WPS_TOKEN_PLAYLIST_NAME:
751 return playlist_name(NULL, buf, buf_size);
753 case WPS_TOKEN_PLAYLIST_POSITION:
754 snprintf(buf, buf_size, "%d", playlist_get_display_index());
755 return buf;
757 case WPS_TOKEN_PLAYLIST_SHUFFLE:
758 if ( global_settings.playlist_shuffle )
759 return "s";
760 else
761 return NULL;
762 break;
764 case WPS_TOKEN_VOLUME:
765 snprintf(buf, buf_size, "%d", global_settings.volume);
766 if (intval)
768 if (global_settings.volume == sound_min(SOUND_VOLUME))
770 *intval = 1;
772 else if (global_settings.volume == 0)
774 *intval = limit - 1;
776 else if (global_settings.volume > 0)
778 *intval = limit;
780 else
782 *intval = (limit - 3) * (global_settings.volume
783 - sound_min(SOUND_VOLUME) - 1)
784 / (-1 - sound_min(SOUND_VOLUME)) + 2;
787 return buf;
789 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
790 if (id3->length <= 0)
791 return NULL;
793 if (intval)
795 *intval = limit * (id3->elapsed + state->ff_rewind_count)
796 / id3->length + 1;
798 snprintf(buf, buf_size, "%d",
799 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
800 return buf;
802 case WPS_TOKEN_METADATA_ARTIST:
803 return id3->artist;
805 case WPS_TOKEN_METADATA_COMPOSER:
806 return id3->composer;
808 case WPS_TOKEN_METADATA_ALBUM:
809 return id3->album;
811 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
812 return id3->albumartist;
814 case WPS_TOKEN_METADATA_GROUPING:
815 return id3->grouping;
817 case WPS_TOKEN_METADATA_GENRE:
818 return id3->genre_string;
820 case WPS_TOKEN_METADATA_DISC_NUMBER:
821 if (id3->disc_string)
822 return id3->disc_string;
823 if (id3->discnum) {
824 snprintf(buf, buf_size, "%d", id3->discnum);
825 return buf;
827 return NULL;
829 case WPS_TOKEN_METADATA_TRACK_NUMBER:
830 if (id3->track_string)
831 return id3->track_string;
833 if (id3->tracknum) {
834 snprintf(buf, buf_size, "%d", id3->tracknum);
835 return buf;
837 return NULL;
839 case WPS_TOKEN_METADATA_TRACK_TITLE:
840 return id3->title;
842 case WPS_TOKEN_METADATA_VERSION:
843 switch (id3->id3version)
845 case ID3_VER_1_0:
846 return "1";
848 case ID3_VER_1_1:
849 return "1.1";
851 case ID3_VER_2_2:
852 return "2.2";
854 case ID3_VER_2_3:
855 return "2.3";
857 case ID3_VER_2_4:
858 return "2.4";
860 default:
861 return NULL;
864 case WPS_TOKEN_METADATA_YEAR:
865 if( id3->year_string )
866 return id3->year_string;
868 if (id3->year) {
869 snprintf(buf, buf_size, "%d", id3->year);
870 return buf;
872 return NULL;
874 case WPS_TOKEN_METADATA_COMMENT:
875 return id3->comment;
877 #ifdef HAVE_ALBUMART
878 case WPS_TOKEN_ALBUMART_DISPLAY:
879 draw_album_art(gwps, audio_current_aa_hid(), false);
880 return NULL;
882 case WPS_TOKEN_ALBUMART_FOUND:
883 if (audio_current_aa_hid() >= 0) {
884 return "C";
886 return NULL;
887 #endif
889 case WPS_TOKEN_FILE_BITRATE:
890 if(id3->bitrate)
891 snprintf(buf, buf_size, "%d", id3->bitrate);
892 else
893 return "?";
894 return buf;
896 case WPS_TOKEN_FILE_CODEC:
897 if (intval)
899 if(id3->codectype == AFMT_UNKNOWN)
900 *intval = AFMT_NUM_CODECS;
901 else
902 *intval = id3->codectype;
904 return get_codectype(id3);
906 case WPS_TOKEN_FILE_FREQUENCY:
907 snprintf(buf, buf_size, "%ld", id3->frequency);
908 return buf;
910 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
911 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
912 if ((id3->frequency % 1000) < 100)
913 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
914 else
915 snprintf(buf, buf_size, "%ld.%d",
916 id3->frequency / 1000,
917 (id3->frequency % 1000) / 100);
918 return buf;
920 case WPS_TOKEN_FILE_NAME:
921 if (get_dir(buf, buf_size, id3->path, 0)) {
922 /* Remove extension */
923 char* sep = strrchr(buf, '.');
924 if (NULL != sep) {
925 *sep = 0;
927 return buf;
929 else {
930 return NULL;
933 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
934 return get_dir(buf, buf_size, id3->path, 0);
936 case WPS_TOKEN_FILE_PATH:
937 return id3->path;
939 case WPS_TOKEN_FILE_SIZE:
940 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
941 return buf;
943 case WPS_TOKEN_FILE_VBR:
944 return id3->vbr ? "(avg)" : NULL;
946 case WPS_TOKEN_FILE_DIRECTORY:
947 return get_dir(buf, buf_size, id3->path, token->value.i);
949 case WPS_TOKEN_BATTERY_PERCENT:
951 int l = battery_level();
953 if (intval)
955 limit = MAX(limit, 2);
956 if (l > -1) {
957 /* First enum is used for "unknown level". */
958 *intval = (limit - 1) * l / 100 + 2;
959 } else {
960 *intval = 1;
964 if (l > -1) {
965 snprintf(buf, buf_size, "%d", l);
966 return buf;
967 } else {
968 return "?";
972 case WPS_TOKEN_BATTERY_VOLTS:
974 unsigned int v = battery_voltage();
975 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
976 return buf;
979 case WPS_TOKEN_BATTERY_TIME:
981 int t = battery_time();
982 if (t >= 0)
983 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
984 else
985 return "?h ?m";
986 return buf;
989 #if CONFIG_CHARGING
990 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
992 if(charger_input_state==CHARGER)
993 return "p";
994 else
995 return NULL;
997 #endif
998 #if CONFIG_CHARGING >= CHARGING_MONITOR
999 case WPS_TOKEN_BATTERY_CHARGING:
1001 if (charge_state == CHARGING || charge_state == TOPOFF) {
1002 return "c";
1003 } else {
1004 return NULL;
1007 #endif
1008 case WPS_TOKEN_BATTERY_SLEEPTIME:
1010 if (get_sleep_timer() == 0)
1011 return NULL;
1012 else
1014 format_time(buf, buf_size, get_sleep_timer() * 1000);
1015 return buf;
1019 case WPS_TOKEN_PLAYBACK_STATUS:
1021 int status = audio_status();
1022 int mode = 1;
1023 if (status == AUDIO_STATUS_PLAY)
1024 mode = 2;
1025 if (wps_fading_out ||
1026 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1027 mode = 3;
1028 if (status_get_ffmode() == STATUS_FASTFORWARD)
1029 mode = 4;
1030 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1031 mode = 5;
1033 if (intval) {
1034 *intval = mode;
1037 snprintf(buf, buf_size, "%d", mode-1);
1038 return buf;
1041 case WPS_TOKEN_REPEAT_MODE:
1042 if (intval)
1043 *intval = global_settings.repeat_mode + 1;
1044 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1045 return buf;
1047 case WPS_TOKEN_RTC_PRESENT:
1048 #if CONFIG_RTC
1049 return "c";
1050 #else
1051 return NULL;
1052 #endif
1054 #if CONFIG_RTC
1055 case WPS_TOKEN_RTC_12HOUR_CFG:
1056 if (intval)
1057 *intval = global_settings.timeformat + 1;
1058 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1059 return buf;
1061 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1062 /* d: day of month (01..31) */
1063 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1064 return buf;
1066 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1067 /* e: day of month, blank padded ( 1..31) */
1068 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1069 return buf;
1071 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1072 /* H: hour (00..23) */
1073 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1074 return buf;
1076 case WPS_TOKEN_RTC_HOUR_24:
1077 /* k: hour ( 0..23) */
1078 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1079 return buf;
1081 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1082 /* I: hour (01..12) */
1083 snprintf(buf, buf_size, "%02d",
1084 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1085 return buf;
1087 case WPS_TOKEN_RTC_HOUR_12:
1088 /* l: hour ( 1..12) */
1089 snprintf(buf, buf_size, "%2d",
1090 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1091 return buf;
1093 case WPS_TOKEN_RTC_MONTH:
1094 /* m: month (01..12) */
1095 if (intval)
1096 *intval = tm->tm_mon + 1;
1097 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1098 return buf;
1100 case WPS_TOKEN_RTC_MINUTE:
1101 /* M: minute (00..59) */
1102 snprintf(buf, buf_size, "%02d", tm->tm_min);
1103 return buf;
1105 case WPS_TOKEN_RTC_SECOND:
1106 /* S: second (00..59) */
1107 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1108 return buf;
1110 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1111 /* y: last two digits of year (00..99) */
1112 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1113 return buf;
1115 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1116 /* Y: year (1970...) */
1117 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1118 return buf;
1120 case WPS_TOKEN_RTC_AM_PM_UPPER:
1121 /* p: upper case AM or PM indicator */
1122 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1124 case WPS_TOKEN_RTC_AM_PM_LOWER:
1125 /* P: lower case am or pm indicator */
1126 return tm->tm_hour/12 == 0 ? "am" : "pm";
1128 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1129 /* a: abbreviated weekday name (Sun..Sat) */
1130 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1132 case WPS_TOKEN_RTC_MONTH_NAME:
1133 /* b: abbreviated month name (Jan..Dec) */
1134 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1136 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1137 /* u: day of week (1..7); 1 is Monday */
1138 if (intval)
1139 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1140 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1141 return buf;
1143 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1144 /* w: day of week (0..6); 0 is Sunday */
1145 if (intval)
1146 *intval = tm->tm_wday + 1;
1147 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1148 return buf;
1149 #else
1150 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1151 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1152 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1153 case WPS_TOKEN_RTC_HOUR_24:
1154 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1155 case WPS_TOKEN_RTC_HOUR_12:
1156 case WPS_TOKEN_RTC_MONTH:
1157 case WPS_TOKEN_RTC_MINUTE:
1158 case WPS_TOKEN_RTC_SECOND:
1159 case WPS_TOKEN_RTC_AM_PM_UPPER:
1160 case WPS_TOKEN_RTC_AM_PM_LOWER:
1161 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1162 return "--";
1163 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1164 return "----";
1165 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1166 case WPS_TOKEN_RTC_MONTH_NAME:
1167 return "---";
1168 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1169 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1170 return "-";
1171 #endif
1173 #ifdef HAVE_LCD_CHARCELLS
1174 case WPS_TOKEN_PROGRESSBAR:
1176 char *end = utf8encode(data->wps_progress_pat[0], buf);
1177 *end = '\0';
1178 return buf;
1181 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1182 if(is_new_player())
1184 /* we need 11 characters (full line) for
1185 progress-bar */
1186 strncpy(buf, " ", buf_size);
1188 else
1190 /* Tell the user if we have an OldPlayer */
1191 strncpy(buf, " <Old LCD> ", buf_size);
1193 return buf;
1194 #endif
1196 #ifdef HAVE_TAGCACHE
1197 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1198 if (intval) {
1199 *intval = id3->playcount + 1;
1201 snprintf(buf, buf_size, "%ld", id3->playcount);
1202 return buf;
1204 case WPS_TOKEN_DATABASE_RATING:
1205 if (intval) {
1206 *intval = id3->rating + 1;
1208 snprintf(buf, buf_size, "%d", id3->rating);
1209 return buf;
1211 case WPS_TOKEN_DATABASE_AUTOSCORE:
1212 if (intval)
1213 *intval = id3->score + 1;
1215 snprintf(buf, buf_size, "%d", id3->score);
1216 return buf;
1217 #endif
1219 #if (CONFIG_CODEC == SWCODEC)
1220 case WPS_TOKEN_CROSSFADE:
1221 if (intval)
1222 *intval = global_settings.crossfade + 1;
1223 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1224 return buf;
1226 case WPS_TOKEN_REPLAYGAIN:
1228 int val;
1230 if (global_settings.replaygain_type == REPLAYGAIN_OFF)
1231 val = 1; /* off */
1232 else
1234 int type =
1235 get_replaygain_mode(id3->track_gain_string != NULL,
1236 id3->album_gain_string != NULL);
1237 if (type < 0)
1238 val = 6; /* no tag */
1239 else
1240 val = type + 2;
1242 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1243 val += 2;
1246 if (intval)
1247 *intval = val;
1249 switch (val)
1251 case 1:
1252 case 6:
1253 return "+0.00 dB";
1254 break;
1255 case 2:
1256 case 4:
1257 strncpy(buf, id3->track_gain_string, buf_size);
1258 break;
1259 case 3:
1260 case 5:
1261 strncpy(buf, id3->album_gain_string, buf_size);
1262 break;
1264 return buf;
1266 #endif /* (CONFIG_CODEC == SWCODEC) */
1268 #if (CONFIG_CODEC != MAS3507D)
1269 case WPS_TOKEN_SOUND_PITCH:
1271 int val = sound_get_pitch();
1272 snprintf(buf, buf_size, "%d.%d",
1273 val / 10, val % 10);
1274 return buf;
1276 #endif
1278 case WPS_TOKEN_MAIN_HOLD:
1279 #ifdef HAS_BUTTON_HOLD
1280 if (button_hold())
1281 #else
1282 if (is_keys_locked())
1283 #endif /*hold switch or softlock*/
1284 return "h";
1285 else
1286 return NULL;
1288 #ifdef HAS_REMOTE_BUTTON_HOLD
1289 case WPS_TOKEN_REMOTE_HOLD:
1290 if (remote_button_hold())
1291 return "r";
1292 else
1293 return NULL;
1294 #endif
1296 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1297 case WPS_TOKEN_VLED_HDD:
1298 if(led_read(HZ/2))
1299 return "h";
1300 else
1301 return NULL;
1302 #endif
1303 case WPS_TOKEN_BUTTON_VOLUME:
1304 if (data->button_time_volume &&
1305 TIME_BEFORE(current_tick, data->button_time_volume +
1306 token->value.i * TIMEOUT_UNIT))
1307 return "v";
1308 return NULL;
1309 case WPS_TOKEN_LASTTOUCH:
1310 #ifdef HAVE_TOUCHSCREEN
1311 if (TIME_BEFORE(current_tick, token->value.i * TIMEOUT_UNIT +
1312 touchscreen_last_touch()))
1313 return "t";
1314 #endif
1315 return NULL;
1317 case WPS_TOKEN_SETTING:
1319 if (intval)
1321 /* Handle contionals */
1322 const struct settings_list *s = settings+token->value.i;
1323 switch (s->flags&F_T_MASK)
1325 case F_T_INT:
1326 case F_T_UINT:
1327 if (s->flags&F_RGB)
1328 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1329 /* shouldn't overflow since colors are stored
1330 * on 16 bits ...
1331 * but this is pretty useless anyway */
1332 *intval = *(int*)s->setting + 1;
1333 else if (s->cfg_vals == NULL)
1334 /* %?St|name|<1st choice|2nd choice|...> */
1335 *intval = (*(int*)s->setting-s->int_setting->min)
1336 /s->int_setting->step + 1;
1337 else
1338 /* %?St|name|<1st choice|2nd choice|...> */
1339 /* Not sure about this one. cfg_name/vals are
1340 * indexed from 0 right? */
1341 *intval = *(int*)s->setting + 1;
1342 break;
1343 case F_T_BOOL:
1344 /* %?St|name|<if true|if false> */
1345 *intval = *(bool*)s->setting?1:2;
1346 break;
1347 case F_T_CHARPTR:
1348 /* %?St|name|<if non empty string|if empty>
1349 * The string's emptyness discards the setting's
1350 * prefix and suffix */
1351 *intval = ((char*)s->setting)[0]?1:2;
1352 break;
1353 default:
1354 /* This shouldn't happen ... but you never know */
1355 *intval = -1;
1356 break;
1359 cfg_to_string(token->value.i,buf,buf_size);
1360 return buf;
1363 default:
1364 return NULL;
1368 /* Return the index to the end token for the conditional token at index.
1369 The conditional token can be either a start token or a separator
1370 (i.e. option) token.
1372 static int find_conditional_end(struct wps_data *data, int index)
1374 int ret = index;
1375 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1376 ret = data->tokens[ret].value.i;
1378 /* ret now is the index to the end token for the conditional. */
1379 return ret;
1382 /* Evaluate the conditional that is at *token_index and return whether a skip
1383 has ocurred. *token_index is updated with the new position.
1385 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1387 if (!gwps)
1388 return false;
1390 struct wps_data *data = gwps->data;
1392 int i, cond_end;
1393 int cond_index = *token_index;
1394 char result[128];
1395 const char *value;
1396 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1397 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1399 /* treat ?xx<true> constructs as if they had 2 options. */
1400 if (num_options < 2)
1401 num_options = 2;
1403 int intval = num_options;
1404 /* get_token_value needs to know the number of options in the enum */
1405 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1406 result, sizeof(result), &intval);
1408 /* intval is now the number of the enum option we want to read,
1409 starting from 1. If intval is -1, we check if value is empty. */
1410 if (intval == -1)
1411 intval = (value && *value) ? 1 : num_options;
1412 else if (intval > num_options || intval < 1)
1413 intval = num_options;
1415 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1417 /* skip to the appropriate enum case */
1418 int next = cond_index + 2;
1419 for (i = 1; i < intval; i++)
1421 next = data->tokens[next].value.i;
1423 *token_index = next;
1425 if (prev_val == intval)
1427 /* Same conditional case as previously. Return without clearing the
1428 pictures */
1429 return false;
1432 cond_end = find_conditional_end(data, cond_index + 2);
1433 for (i = cond_index + 3; i < cond_end; i++)
1435 #ifdef HAVE_LCD_BITMAP
1436 /* clear all pictures in the conditional and nested ones */
1437 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1438 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1439 #endif
1440 #ifdef HAVE_ALBUMART
1441 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1442 draw_album_art(gwps, audio_current_aa_hid(), true);
1443 #endif
1446 return true;
1449 /* Read a (sub)line to the given alignment format buffer.
1450 linebuf is the buffer where the data is actually stored.
1451 align is the alignment format that'll be used to display the text.
1452 The return value indicates whether the line needs to be updated.
1454 static bool get_line(struct gui_wps *gwps,
1455 int line, int subline,
1456 struct align_pos *align,
1457 char *linebuf,
1458 int linebuf_size)
1460 struct wps_data *data = gwps->data;
1462 char temp_buf[128];
1463 char *buf = linebuf; /* will always point to the writing position */
1464 char *linebuf_end = linebuf + linebuf_size - 1;
1465 int i, last_token_idx;
1466 bool update = false;
1468 /* alignment-related variables */
1469 int cur_align;
1470 char* cur_align_start;
1471 cur_align_start = buf;
1472 cur_align = WPS_ALIGN_LEFT;
1473 align->left = NULL;
1474 align->center = NULL;
1475 align->right = NULL;
1477 /* Process all tokens of the desired subline */
1478 last_token_idx = wps_last_token_index(data, line, subline);
1479 for (i = wps_first_token_index(data, line, subline);
1480 i <= last_token_idx; i++)
1482 switch(data->tokens[i].type)
1484 case WPS_TOKEN_CONDITIONAL:
1485 /* place ourselves in the right conditional case */
1486 update |= evaluate_conditional(gwps, &i);
1487 break;
1489 case WPS_TOKEN_CONDITIONAL_OPTION:
1490 /* we've finished in the curent conditional case,
1491 skip to the end of the conditional structure */
1492 i = find_conditional_end(data, i);
1493 break;
1495 #ifdef HAVE_LCD_BITMAP
1496 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1498 struct gui_img *img = data->img;
1499 int n = data->tokens[i].value.i & 0xFF;
1500 int subimage = data->tokens[i].value.i >> 8;
1502 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1503 img[n].display = subimage;
1504 break;
1506 #endif
1508 case WPS_TOKEN_ALIGN_LEFT:
1509 case WPS_TOKEN_ALIGN_CENTER:
1510 case WPS_TOKEN_ALIGN_RIGHT:
1511 /* remember where the current aligned text started */
1512 switch (cur_align)
1514 case WPS_ALIGN_LEFT:
1515 align->left = cur_align_start;
1516 break;
1518 case WPS_ALIGN_CENTER:
1519 align->center = cur_align_start;
1520 break;
1522 case WPS_ALIGN_RIGHT:
1523 align->right = cur_align_start;
1524 break;
1526 /* start a new alignment */
1527 switch (data->tokens[i].type)
1529 case WPS_TOKEN_ALIGN_LEFT:
1530 cur_align = WPS_ALIGN_LEFT;
1531 break;
1532 case WPS_TOKEN_ALIGN_CENTER:
1533 cur_align = WPS_ALIGN_CENTER;
1534 break;
1535 case WPS_TOKEN_ALIGN_RIGHT:
1536 cur_align = WPS_ALIGN_RIGHT;
1537 break;
1538 default:
1539 break;
1541 *buf++ = 0;
1542 cur_align_start = buf;
1543 break;
1544 case WPS_VIEWPORT_ENABLE:
1546 char label = data->tokens[i].value.i;
1547 int j;
1548 char temp = VP_DRAW_HIDEABLE;
1549 for(j=0;j<data->num_viewports;j++)
1551 temp = VP_DRAW_HIDEABLE;
1552 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1553 (data->viewports[j].label == label))
1555 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1556 temp |= VP_DRAW_WASHIDDEN;
1557 data->viewports[j].hidden_flags = temp;
1561 break;
1562 default:
1564 /* get the value of the tag and copy it to the buffer */
1565 const char *value = get_token_value(gwps, &data->tokens[i],
1566 temp_buf, sizeof(temp_buf), NULL);
1567 if (value)
1569 update = true;
1570 while (*value && (buf < linebuf_end))
1571 *buf++ = *value++;
1573 break;
1578 /* close the current alignment */
1579 switch (cur_align)
1581 case WPS_ALIGN_LEFT:
1582 align->left = cur_align_start;
1583 break;
1585 case WPS_ALIGN_CENTER:
1586 align->center = cur_align_start;
1587 break;
1589 case WPS_ALIGN_RIGHT:
1590 align->right = cur_align_start;
1591 break;
1594 return update;
1597 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1599 struct wps_data *data = gwps->data;
1600 int i;
1601 int subline_idx = wps_subline_index(data, line, subline);
1602 int last_token_idx = wps_last_token_index(data, line, subline);
1604 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1606 for (i = wps_first_token_index(data, line, subline);
1607 i <= last_token_idx; i++)
1609 switch(data->tokens[i].type)
1611 case WPS_TOKEN_CONDITIONAL:
1612 /* place ourselves in the right conditional case */
1613 evaluate_conditional(gwps, &i);
1614 break;
1616 case WPS_TOKEN_CONDITIONAL_OPTION:
1617 /* we've finished in the curent conditional case,
1618 skip to the end of the conditional structure */
1619 i = find_conditional_end(data, i);
1620 break;
1622 case WPS_TOKEN_SUBLINE_TIMEOUT:
1623 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1624 break;
1626 default:
1627 break;
1632 /* Calculates which subline should be displayed for the specified line
1633 Returns true iff the subline must be refreshed */
1634 static bool update_curr_subline(struct gui_wps *gwps, int line)
1636 struct wps_data *data = gwps->data;
1638 int search, search_start, num_sublines;
1639 bool reset_subline;
1640 bool new_subline_refresh;
1641 bool only_one_subline;
1643 num_sublines = data->lines[line].num_sublines;
1644 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1645 new_subline_refresh = false;
1646 only_one_subline = false;
1648 /* if time to advance to next sub-line */
1649 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1650 reset_subline)
1652 /* search all sublines until the next subline with time > 0
1653 is found or we get back to the subline we started with */
1654 if (reset_subline)
1655 search_start = 0;
1656 else
1657 search_start = data->lines[line].curr_subline;
1659 for (search = 0; search < num_sublines; search++)
1661 data->lines[line].curr_subline++;
1663 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1664 if (data->lines[line].curr_subline == num_sublines)
1666 if (data->lines[line].curr_subline == 1)
1667 only_one_subline = true;
1668 data->lines[line].curr_subline = 0;
1671 /* if back where we started after search or
1672 only one subline is defined on the line */
1673 if (((search > 0) &&
1674 (data->lines[line].curr_subline == search_start)) ||
1675 only_one_subline)
1677 /* no other subline with a time > 0 exists */
1678 data->lines[line].subline_expire_time = (reset_subline ?
1679 current_tick :
1680 data->lines[line].subline_expire_time) + 100 * HZ;
1681 break;
1683 else
1685 /* get initial time multiplier for this subline */
1686 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1688 int subline_idx = wps_subline_index(data, line,
1689 data->lines[line].curr_subline);
1691 /* only use this subline if subline time > 0 */
1692 if (data->sublines[subline_idx].time_mult > 0)
1694 new_subline_refresh = true;
1695 data->lines[line].subline_expire_time = (reset_subline ?
1696 current_tick : data->lines[line].subline_expire_time) +
1697 TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
1698 break;
1704 return new_subline_refresh;
1707 /* Display a line appropriately according to its alignment format.
1708 format_align contains the text, separated between left, center and right.
1709 line is the index of the line on the screen.
1710 scroll indicates whether the line is a scrolling one or not.
1712 static void write_line(struct screen *display,
1713 struct align_pos *format_align,
1714 int line,
1715 bool scroll)
1717 int left_width = 0, left_xpos;
1718 int center_width = 0, center_xpos;
1719 int right_width = 0, right_xpos;
1720 int ypos;
1721 int space_width;
1722 int string_height;
1723 int scroll_width;
1725 /* calculate different string sizes and positions */
1726 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1727 if (format_align->left != 0) {
1728 display->getstringsize((unsigned char *)format_align->left,
1729 &left_width, &string_height);
1732 if (format_align->right != 0) {
1733 display->getstringsize((unsigned char *)format_align->right,
1734 &right_width, &string_height);
1737 if (format_align->center != 0) {
1738 display->getstringsize((unsigned char *)format_align->center,
1739 &center_width, &string_height);
1742 left_xpos = 0;
1743 right_xpos = (display->getwidth() - right_width);
1744 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1746 scroll_width = display->getwidth() - left_xpos;
1748 /* Checks for overlapping strings.
1749 If needed the overlapping strings will be merged, separated by a
1750 space */
1752 /* CASE 1: left and centered string overlap */
1753 /* there is a left string, need to merge left and center */
1754 if ((left_width != 0 && center_width != 0) &&
1755 (left_xpos + left_width + space_width > center_xpos)) {
1756 /* replace the former separator '\0' of left and
1757 center string with a space */
1758 *(--format_align->center) = ' ';
1759 /* calculate the new width and position of the merged string */
1760 left_width = left_width + space_width + center_width;
1761 /* there is no centered string anymore */
1762 center_width = 0;
1764 /* there is no left string, move center to left */
1765 if ((left_width == 0 && center_width != 0) &&
1766 (left_xpos + left_width > center_xpos)) {
1767 /* move the center string to the left string */
1768 format_align->left = format_align->center;
1769 /* calculate the new width and position of the string */
1770 left_width = center_width;
1771 /* there is no centered string anymore */
1772 center_width = 0;
1775 /* CASE 2: centered and right string overlap */
1776 /* there is a right string, need to merge center and right */
1777 if ((center_width != 0 && right_width != 0) &&
1778 (center_xpos + center_width + space_width > right_xpos)) {
1779 /* replace the former separator '\0' of center and
1780 right string with a space */
1781 *(--format_align->right) = ' ';
1782 /* move the center string to the right after merge */
1783 format_align->right = format_align->center;
1784 /* calculate the new width and position of the merged string */
1785 right_width = center_width + space_width + right_width;
1786 right_xpos = (display->getwidth() - right_width);
1787 /* there is no centered string anymore */
1788 center_width = 0;
1790 /* there is no right string, move center to right */
1791 if ((center_width != 0 && right_width == 0) &&
1792 (center_xpos + center_width > right_xpos)) {
1793 /* move the center string to the right string */
1794 format_align->right = format_align->center;
1795 /* calculate the new width and position of the string */
1796 right_width = center_width;
1797 right_xpos = (display->getwidth() - right_width);
1798 /* there is no centered string anymore */
1799 center_width = 0;
1802 /* CASE 3: left and right overlap
1803 There is no center string anymore, either there never
1804 was one or it has been merged in case 1 or 2 */
1805 /* there is a left string, need to merge left and right */
1806 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1807 (left_xpos + left_width + space_width > right_xpos)) {
1808 /* replace the former separator '\0' of left and
1809 right string with a space */
1810 *(--format_align->right) = ' ';
1811 /* calculate the new width and position of the string */
1812 left_width = left_width + space_width + right_width;
1813 /* there is no right string anymore */
1814 right_width = 0;
1816 /* there is no left string, move right to left */
1817 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1818 (left_width > right_xpos)) {
1819 /* move the right string to the left string */
1820 format_align->left = format_align->right;
1821 /* calculate the new width and position of the string */
1822 left_width = right_width;
1823 /* there is no right string anymore */
1824 right_width = 0;
1827 ypos = (line * string_height);
1830 if (scroll && ((left_width > scroll_width) ||
1831 (center_width > scroll_width) ||
1832 (right_width > scroll_width)))
1834 display->puts_scroll(0, line,
1835 (unsigned char *)format_align->left);
1837 else
1839 #ifdef HAVE_LCD_BITMAP
1840 /* clear the line first */
1841 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1842 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1843 display->set_drawmode(DRMODE_SOLID);
1844 #endif
1846 /* Nasty hack: we output an empty scrolling string,
1847 which will reset the scroller for that line */
1848 display->puts_scroll(0, line, (unsigned char *)"");
1850 /* print aligned strings */
1851 if (left_width != 0)
1853 display->putsxy(left_xpos, ypos,
1854 (unsigned char *)format_align->left);
1856 if (center_width != 0)
1858 display->putsxy(center_xpos, ypos,
1859 (unsigned char *)format_align->center);
1861 if (right_width != 0)
1863 display->putsxy(right_xpos, ypos,
1864 (unsigned char *)format_align->right);
1869 bool gui_wps_redraw(struct gui_wps *gwps,
1870 int ffwd_offset,
1871 unsigned refresh_mode)
1873 struct wps_data *data = gwps->data;
1874 struct screen *display = gwps->display;
1875 struct wps_state *state = gwps->state;
1877 if (!data || !state || !display)
1878 return false;
1880 struct mp3entry *id3 = state->id3;
1882 if (!id3)
1883 return false;
1885 int v, line, i, subline_idx;
1886 unsigned flags;
1887 char linebuf[MAX_PATH];
1889 struct align_pos align;
1890 align.left = NULL;
1891 align.center = NULL;
1892 align.right = NULL;
1894 bool update_line, new_subline_refresh;
1896 #ifdef HAVE_LCD_BITMAP
1898 /* to find out wether the peak meter is enabled we
1899 assume it wasn't until we find a line that contains
1900 the peak meter. We can't use peak_meter_enabled itself
1901 because that would mean to turn off the meter thread
1902 temporarily. (That shouldn't matter unless yield
1903 or sleep is called but who knows...)
1905 bool enable_pm = false;
1907 #endif
1909 /* reset to first subline if refresh all flag is set */
1910 if (refresh_mode == WPS_REFRESH_ALL)
1912 display->set_viewport(&data->viewports[0].vp);
1913 display->clear_viewport();
1915 for (i = 0; i <= data->num_lines; i++)
1917 data->lines[i].curr_subline = SUBLINE_RESET;
1921 #ifdef HAVE_LCD_CHARCELLS
1922 for (i = 0; i < 8; i++)
1924 if (data->wps_progress_pat[i] == 0)
1925 data->wps_progress_pat[i] = display->get_locked_pattern();
1927 #endif
1929 state->ff_rewind_count = ffwd_offset;
1931 /* disable any viewports which are conditionally displayed */
1932 for (v = 0; v < data->num_viewports; v++)
1934 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
1936 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
1937 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1938 else
1939 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
1942 for (v = 0; v < data->num_viewports; v++)
1944 struct wps_viewport *wps_vp = &(data->viewports[v]);
1945 unsigned vp_refresh_mode = refresh_mode;
1946 display->set_viewport(&wps_vp->vp);
1948 #ifdef HAVE_LCD_BITMAP
1949 /* Set images to not to be displayed */
1950 for (i = 0; i < MAX_IMAGES; i++)
1952 data->img[i].display = -1;
1954 #endif
1955 /* dont redraw the viewport if its disabled */
1956 if ((wps_vp->hidden_flags&VP_DRAW_HIDDEN))
1958 if (!(wps_vp->hidden_flags&VP_DRAW_WASHIDDEN))
1959 display->scroll_stop(&wps_vp->vp);
1960 wps_vp->hidden_flags |= VP_DRAW_WASHIDDEN;
1961 continue;
1963 else if (((wps_vp->hidden_flags&
1964 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1965 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1967 vp_refresh_mode = WPS_REFRESH_ALL;
1968 wps_vp->hidden_flags = VP_DRAW_HIDEABLE;
1970 if (vp_refresh_mode == WPS_REFRESH_ALL)
1972 display->clear_viewport();
1975 for (line = wps_vp->first_line;
1976 line <= wps_vp->last_line; line++)
1978 memset(linebuf, 0, sizeof(linebuf));
1979 update_line = false;
1981 /* get current subline for the line */
1982 new_subline_refresh = update_curr_subline(gwps, line);
1984 subline_idx = wps_subline_index(data, line,
1985 data->lines[line].curr_subline);
1986 flags = data->sublines[subline_idx].line_type;
1988 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1989 || new_subline_refresh)
1991 /* get_line tells us if we need to update the line */
1992 update_line = get_line(gwps, line, data->lines[line].curr_subline,
1993 &align, linebuf, sizeof(linebuf));
1995 #ifdef HAVE_LCD_BITMAP
1996 /* peakmeter */
1997 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1999 /* the peakmeter should be alone on its line */
2000 update_line = false;
2002 int h = font_get(wps_vp->vp.font)->height;
2003 int peak_meter_y = (line - wps_vp->first_line)* h;
2005 /* The user might decide to have the peak meter in the last
2006 line so that it is only displayed if no status bar is
2007 visible. If so we neither want do draw nor enable the
2008 peak meter. */
2009 if (peak_meter_y + h <= display->getheight()) {
2010 /* found a line with a peak meter -> remember that we must
2011 enable it later */
2012 enable_pm = true;
2013 peak_meter_enabled = true;
2014 peak_meter_screen(gwps->display, 0, peak_meter_y,
2015 MIN(h, display->getheight() - peak_meter_y));
2017 else
2019 peak_meter_enabled = false;
2023 #else /* HAVE_LCD_CHARCELL */
2025 /* progressbar */
2026 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2028 if (data->full_line_progressbar)
2029 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2030 else
2031 draw_player_progress(gwps);
2033 #endif
2035 if (update_line &&
2036 /* conditionals clear the line which means if the %Vd is put into the default
2037 viewport there will be a blank line.
2038 To get around this we dont allow any actual drawing to happen in the
2039 deault vp if other vp's are defined */
2040 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2042 if (flags & WPS_REFRESH_SCROLL)
2044 /* if the line is a scrolling one we don't want to update
2045 too often, so that it has the time to scroll */
2046 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2047 write_line(display, &align, line - wps_vp->first_line, true);
2049 else
2050 write_line(display, &align, line - wps_vp->first_line, false);
2054 #ifdef HAVE_LCD_BITMAP
2055 /* progressbar */
2056 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2058 if (wps_vp->pb)
2060 draw_progressbar(gwps, wps_vp);
2063 /* Now display any images in this viewport */
2064 wps_display_images(gwps, &wps_vp->vp);
2065 #endif
2068 #ifdef HAVE_LCD_BITMAP
2069 data->peak_meter_enabled = enable_pm;
2070 #endif
2072 if (refresh_mode & WPS_REFRESH_STATUSBAR)
2074 gwps_draw_statusbars();
2076 /* Restore the default viewport */
2077 display->set_viewport(NULL);
2079 display->update();
2081 return true;