Make the greylib text output functions handle unicode. Fixes non-working umlauts...
[kugel-rb.git] / apps / gui / gwps-common.c
blob46453dc4c90b60c58e9f03ae61bc79e96ace3835
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002-2007 Björn Stenberg
11 * Copyright (C) 2007-2008 Nicolas Pennequin
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "gwps-common.h"
23 #include "font.h"
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include "system.h"
28 #include "settings.h"
29 #include "settings_list.h"
30 #include "rbunicode.h"
31 #include "rtc.h"
32 #include "audio.h"
33 #include "status.h"
34 #include "power.h"
35 #include "powermgmt.h"
36 #include "sound.h"
37 #include "debug.h"
38 #ifdef HAVE_LCD_CHARCELLS
39 #include "hwcompat.h"
40 #endif
41 #include "abrepeat.h"
42 #include "mp3_playback.h"
43 #include "backlight.h"
44 #include "lang.h"
45 #include "misc.h"
46 #include "splash.h"
47 #include "scrollbar.h"
48 #include "led.h"
49 #include "lcd.h"
50 #ifdef HAVE_LCD_BITMAP
51 #include "peakmeter.h"
52 /* Image stuff */
53 #include "bmp.h"
54 #include "albumart.h"
55 #endif
56 #include "dsp.h"
57 #include "action.h"
58 #include "cuesheet.h"
59 #include "playlist.h"
60 #if CONFIG_CODEC == SWCODEC
61 #include "playback.h"
62 #endif
63 #include "backdrop.h"
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 */
77 /* draws the statusbar on the given wps-screen */
78 #ifdef HAVE_LCD_BITMAP
79 static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
81 bool draw = global_settings.statusbar;
83 if (wps->data->wps_sb_tag)
84 draw = wps->data->show_sb_on_wps;
86 if (draw)
87 gui_statusbar_draw(wps->statusbar, force);
89 #else
90 #define gui_wps_statusbar_draw(wps, force) \
91 gui_statusbar_draw((wps)->statusbar, (force))
92 #endif
93 #include "pcmbuf.h"
95 /* fades the volume */
96 bool wps_fading_out = false;
97 void fade(bool fade_in, bool updatewps)
99 int fp_global_vol = global_settings.volume << 8;
100 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
101 int fp_step = (fp_global_vol - fp_min_vol) / 30;
102 int i;
103 wps_fading_out = !fade_in;
104 if (fade_in) {
105 /* fade in */
106 int fp_volume = fp_min_vol;
108 /* zero out the sound */
109 sound_set_volume(fp_min_vol >> 8);
111 sleep(HZ/10); /* let audio thread run */
112 audio_resume();
114 while (fp_volume < fp_global_vol - fp_step) {
115 fp_volume += fp_step;
116 sound_set_volume(fp_volume >> 8);
117 if (updatewps)
119 FOR_NB_SCREENS(i)
120 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
122 sleep(1);
124 sound_set_volume(global_settings.volume);
126 else {
127 /* fade out */
128 int fp_volume = fp_global_vol;
130 while (fp_volume > fp_min_vol + fp_step) {
131 fp_volume -= fp_step;
132 sound_set_volume(fp_volume >> 8);
133 if (updatewps)
135 FOR_NB_SCREENS(i)
136 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
138 sleep(1);
140 audio_pause();
141 wps_fading_out = false;
142 #if CONFIG_CODEC != SWCODEC
143 #ifndef SIMULATOR
144 /* let audio thread run and wait for the mas to run out of data */
145 while (!mp3_pause_done())
146 #endif
147 sleep(HZ/10);
148 #endif
150 /* reset volume to what it was before the fade */
151 sound_set_volume(global_settings.volume);
155 /* return true if screen restore is needed
156 return false otherwise
158 bool update_onvol_change(struct gui_wps * gwps)
160 gui_wps_statusbar_draw(gwps, false);
161 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
163 #ifdef HAVE_LCD_CHARCELLS
164 splashf(0, "Vol: %3d dB",
165 sound_val2phys(SOUND_VOLUME, global_settings.volume));
166 return true;
167 #endif
168 return false;
171 void play_hop(int direction)
173 if(!wps_state.id3 || !wps_state.id3->length
174 || global_settings.skip_length == 0)
175 return;
176 #define STEP ((unsigned)global_settings.skip_length*1000)
177 if(direction == 1
178 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
179 #if CONFIG_CODEC == SWCODEC
180 if(global_settings.beep)
181 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
182 #endif
183 return;
185 if((direction == -1 && wps_state.id3->elapsed < STEP))
186 wps_state.id3->elapsed = 0;
187 else
188 wps_state.id3->elapsed += STEP *direction;
189 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
190 #if (CONFIG_CODEC == SWCODEC)
191 audio_pre_ff_rewind();
192 #else
193 audio_pause();
194 #endif
196 audio_ff_rewind(wps_state.id3->elapsed);
197 #if (CONFIG_CODEC != SWCODEC)
198 if (!wps_state.paused)
199 audio_resume();
200 #endif
201 #undef STEP
204 bool ffwd_rew(int button)
206 unsigned int step = 0; /* current ff/rewind step */
207 unsigned int max_step = 0; /* maximum ff/rewind step */
208 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
209 int direction = -1; /* forward=1 or backward=-1 */
210 bool exit = false;
211 bool usb = false;
212 int i = 0;
213 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
215 if (button == ACTION_NONE)
217 status_set_ffmode(0);
218 return usb;
220 while (!exit)
222 switch ( button )
224 case ACTION_WPS_SEEKFWD:
225 direction = 1;
226 case ACTION_WPS_SEEKBACK:
227 if (wps_state.ff_rewind)
229 if (direction == 1)
231 /* fast forwarding, calc max step relative to end */
232 max_step = (wps_state.id3->length -
233 (wps_state.id3->elapsed +
234 ff_rewind_count)) *
235 FF_REWIND_MAX_PERCENT / 100;
237 else
239 /* rewinding, calc max step relative to start */
240 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
241 FF_REWIND_MAX_PERCENT / 100;
244 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
246 if (step > max_step)
247 step = max_step;
249 ff_rewind_count += step * direction;
251 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
252 step += step >> ff_rw_accel;
254 else
256 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
257 wps_state.id3 && wps_state.id3->length )
259 if (!wps_state.paused)
260 #if (CONFIG_CODEC == SWCODEC)
261 audio_pre_ff_rewind();
262 #else
263 audio_pause();
264 #endif
265 #if CONFIG_KEYPAD == PLAYER_PAD
266 FOR_NB_SCREENS(i)
267 gui_wps[i].display->stop_scroll();
268 #endif
269 if (direction > 0)
270 status_set_ffmode(STATUS_FASTFORWARD);
271 else
272 status_set_ffmode(STATUS_FASTBACKWARD);
274 wps_state.ff_rewind = true;
276 step = 1000 * global_settings.ff_rewind_min_step;
278 else
279 break;
282 if (direction > 0) {
283 if ((wps_state.id3->elapsed + ff_rewind_count) >
284 wps_state.id3->length)
285 ff_rewind_count = wps_state.id3->length -
286 wps_state.id3->elapsed;
288 else {
289 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
290 ff_rewind_count = -wps_state.id3->elapsed;
293 FOR_NB_SCREENS(i)
294 gui_wps_refresh(&gui_wps[i],
295 (wps_state.wps_time_countup == false)?
296 ff_rewind_count:-ff_rewind_count,
297 WPS_REFRESH_PLAYER_PROGRESS |
298 WPS_REFRESH_DYNAMIC);
300 break;
302 case ACTION_WPS_STOPSEEK:
303 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
304 audio_ff_rewind(wps_state.id3->elapsed);
305 ff_rewind_count = 0;
306 wps_state.ff_rewind = false;
307 status_set_ffmode(0);
308 #if (CONFIG_CODEC != SWCODEC)
309 if (!wps_state.paused)
310 audio_resume();
311 #endif
312 #ifdef HAVE_LCD_CHARCELLS
313 gui_wps_display();
314 #endif
315 exit = true;
316 break;
318 default:
319 if(default_event_handler(button) == SYS_USB_CONNECTED) {
320 status_set_ffmode(0);
321 usb = true;
322 exit = true;
324 break;
326 if (!exit)
327 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
329 return usb;
332 bool gui_wps_display(void)
334 int i;
335 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
337 global_status.resume_index = -1;
338 #ifdef HAVE_LCD_BITMAP
339 gui_syncstatusbar_draw(&statusbars, true);
340 #endif
341 splash(HZ, ID2P(LANG_END_PLAYLIST));
342 return true;
344 else
346 FOR_NB_SCREENS(i)
348 /* Update the values in the first (default) viewport - in case the user
349 has modified the statusbar or colour settings */
350 #ifdef HAVE_LCD_BITMAP
351 #if LCD_DEPTH > 1
352 if (gui_wps[i].display->depth > 1)
354 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
355 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
357 #endif
358 #endif
360 gui_wps[i].display->clear_display();
361 if (!gui_wps[i].data->wps_loaded) {
362 if ( !gui_wps[i].data->num_tokens ) {
363 /* set the default wps for the main-screen */
364 if(i == 0)
366 #ifdef HAVE_LCD_BITMAP
367 #if LCD_DEPTH > 1
368 unload_wps_backdrop();
369 #endif
370 wps_data_load(gui_wps[i].data,
371 gui_wps[i].display,
372 "%s%?it<%?in<%in. |>%it|%fn>\n"
373 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
374 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
375 "\n"
376 "%al%pc/%pt%ar[%pp:%pe]\n"
377 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
378 "%pb\n"
379 "%pm\n", false);
380 #else
381 wps_data_load(gui_wps[i].data,
382 gui_wps[i].display,
383 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
384 "%pc%?ps<*|/>%pt\n", false);
385 #endif
387 #if NB_SCREENS == 2
388 /* set the default wps for the remote-screen */
389 else if(i == 1)
391 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
392 unload_remote_wps_backdrop();
393 #endif
394 wps_data_load(gui_wps[i].data,
395 gui_wps[i].display,
396 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
397 "%s%?it<%?in<%in. |>%it|%fn>\n"
398 "%al%pc/%pt%ar[%pp:%pe]\n"
399 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
400 "%pb\n", false);
402 #endif
407 yield();
408 FOR_NB_SCREENS(i)
410 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
412 return false;
415 bool update(struct gui_wps *gwps)
417 bool track_changed = audio_has_changed_track();
418 bool retcode = false;
420 gwps->state->nid3 = audio_next_track();
421 if (track_changed)
423 gwps->display->stop_scroll();
424 gwps->state->id3 = audio_current_track();
426 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
427 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
429 /* the current cuesheet isn't the right one any more */
431 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
432 /* We have the new cuesheet in memory (temp_cue),
433 let's make it the current one ! */
434 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
436 else {
437 /* We need to parse the new cuesheet */
439 char cuepath[MAX_PATH];
441 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
442 parse_cuesheet(cuepath, curr_cue))
444 gwps->state->id3->cuesheet_type = 1;
445 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
449 cue_spoof_id3(curr_cue, gwps->state->id3);
452 if (gui_wps_display())
453 retcode = true;
454 else{
455 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
458 if (gwps->state->id3)
460 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
461 sizeof(gwps->state->current_track_path));
462 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
466 if (gwps->state->id3)
468 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
469 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
470 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
471 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
473 /* We've changed tracks within the cuesheet :
474 we need to update the ID3 info and refresh the WPS */
476 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
477 cue_spoof_id3(curr_cue, gwps->state->id3);
479 gwps->display->stop_scroll();
480 if (gui_wps_display())
481 retcode = true;
482 else
483 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
485 else
486 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
489 gui_wps_statusbar_draw(gwps, false);
491 return retcode;
495 void display_keylock_text(bool locked)
497 int i;
498 FOR_NB_SCREENS(i)
499 gui_wps[i].display->stop_scroll();
501 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
504 #ifdef HAVE_LCD_BITMAP
506 static void draw_progressbar(struct gui_wps *gwps,
507 struct progressbar *pb)
509 struct screen *display = gwps->display;
510 struct wps_state *state = gwps->state;
511 if (pb->have_bitmap_pb)
512 gui_bitmap_scrollbar_draw(display, pb->bm,
513 pb->x, pb->y, pb->width, pb->bm.height,
514 state->id3->length ? state->id3->length : 1, 0,
515 state->id3->length ? state->id3->elapsed
516 + state->ff_rewind_count : 0,
517 HORIZONTAL);
518 else
519 gui_scrollbar_draw(display, pb->x, pb->y, pb->width, pb->height,
520 state->id3->length ? state->id3->length : 1, 0,
521 state->id3->length ? state->id3->elapsed
522 + state->ff_rewind_count : 0,
523 HORIZONTAL);
524 #ifdef AB_REPEAT_ENABLE
525 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
526 ab_draw_markers(display, state->id3->length,
527 pb->x, pb->x + pb->width, pb->y, pb->height);
528 #endif
530 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
531 cue_draw_markers(display, state->id3->length,
532 pb->x, pb->x + pb->width, pb->y+1, pb->height-2);
535 /* clears the area where the image was shown */
536 static void clear_image_pos(struct gui_wps *gwps, int n)
538 if(!gwps)
539 return;
540 struct wps_data *data = gwps->data;
541 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
542 gwps->display->fillrect(data->img[n].x, data->img[n].y,
543 data->img[n].bm.width, data->img[n].subimage_height);
544 gwps->display->set_drawmode(DRMODE_SOLID);
547 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
549 struct screen *display = gwps->display;
550 struct wps_data *data = gwps->data;
551 if(data->img[n].always_display)
552 display->set_drawmode(DRMODE_FG);
553 else
554 display->set_drawmode(DRMODE_SOLID);
556 #if LCD_DEPTH > 1
557 if(data->img[n].bm.format == FORMAT_MONO) {
558 #endif
559 display->mono_bitmap_part(data->img[n].bm.data,
560 0, data->img[n].subimage_height * subimage,
561 data->img[n].bm.width, data->img[n].x,
562 data->img[n].y, data->img[n].bm.width,
563 data->img[n].subimage_height);
564 #if LCD_DEPTH > 1
565 } else {
566 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
567 0, data->img[n].subimage_height * subimage,
568 data->img[n].bm.width, data->img[n].x,
569 data->img[n].y, data->img[n].bm.width,
570 data->img[n].subimage_height);
572 #endif
575 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
577 if(!gwps || !gwps->data || !gwps->display)
578 return;
580 int n;
581 struct wps_data *data = gwps->data;
582 struct screen *display = gwps->display;
584 for (n = 0; n < MAX_IMAGES; n++)
586 if (data->img[n].loaded)
588 if (data->img[n].display >= 0)
590 wps_draw_image(gwps, n, data->img[n].display);
591 } else if (data->img[n].always_display && data->img[n].vp == vp)
593 wps_draw_image(gwps, n, 0);
597 display->set_drawmode(DRMODE_SOLID);
600 #else /* HAVE_LCD_CHARCELL */
602 static bool draw_player_progress(struct gui_wps *gwps)
604 struct wps_state *state = gwps->state;
605 struct screen *display = gwps->display;
606 unsigned char progress_pattern[7];
607 int pos = 0;
608 int i;
610 if (!state->id3)
611 return false;
613 if (state->id3->length)
614 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
615 / state->id3->length;
617 for (i = 0; i < 7; i++, pos -= 5)
619 if (pos <= 0)
620 progress_pattern[i] = 0x1fu;
621 else if (pos >= 5)
622 progress_pattern[i] = 0x00u;
623 else
624 progress_pattern[i] = 0x1fu >> pos;
627 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
628 return true;
631 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
633 static const unsigned char numbers[10][4] = {
634 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
635 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
636 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
637 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
638 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
639 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
640 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
641 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
642 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
643 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
646 struct wps_state *state = gwps->state;
647 struct screen *display = gwps->display;
648 struct wps_data *data = gwps->data;
649 unsigned char progress_pattern[7];
650 char timestr[10];
651 int time;
652 int time_idx = 0;
653 int pos = 0;
654 int pat_idx = 1;
655 int digit, i, j;
656 bool softchar;
658 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
659 return;
661 time = state->id3->elapsed + state->ff_rewind_count;
662 if (state->id3->length)
663 pos = 55 * time / state->id3->length;
665 memset(timestr, 0, sizeof(timestr));
666 format_time(timestr, sizeof(timestr)-2, time);
667 timestr[strlen(timestr)] = ':'; /* always safe */
669 for (i = 0; i < 11; i++, pos -= 5)
671 softchar = false;
672 memset(progress_pattern, 0, sizeof(progress_pattern));
674 if ((digit = timestr[time_idx]))
676 softchar = true;
677 digit -= '0';
679 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
681 memcpy(progress_pattern, numbers[digit], 4);
682 time_idx += 2;
684 else /* tens, shifted right */
686 for (j = 0; j < 4; j++)
687 progress_pattern[j] = numbers[digit][j] >> 1;
689 if (time_idx > 0) /* not the first group, add colon in front */
691 progress_pattern[1] |= 0x10u;
692 progress_pattern[3] |= 0x10u;
694 time_idx++;
697 if (pos >= 5)
698 progress_pattern[5] = progress_pattern[6] = 0x1fu;
701 if (pos > 0 && pos < 5)
703 softchar = true;
704 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
707 if (softchar && pat_idx < 8)
709 display->define_pattern(data->wps_progress_pat[pat_idx],
710 progress_pattern);
711 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
712 pat_idx++;
714 else if (pos <= 0)
715 buf = utf8encode(' ', buf);
716 else
717 buf = utf8encode(0xe115, buf); /* 2/7 _ */
719 *buf = '\0';
722 #endif /* HAVE_LCD_CHARCELL */
724 static char* get_codectype(const struct mp3entry* id3)
726 if (id3->codectype < AFMT_NUM_CODECS) {
727 return (char*)audio_formats[id3->codectype].label;
728 } else {
729 return NULL;
733 /* Extract a part from a path.
735 * buf - buffer extract part to.
736 * buf_size - size of buffer.
737 * path - path to extract from.
738 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
739 * parent of parent, etc.
741 * Returns buf if the desired level was found, NULL otherwise.
743 static char* get_dir(char* buf, int buf_size, const char* path, int level)
745 const char* sep;
746 const char* last_sep;
747 int len;
749 sep = path + strlen(path);
750 last_sep = sep;
752 while (sep > path)
754 if ('/' == *(--sep))
756 if (!level)
757 break;
759 level--;
760 last_sep = sep - 1;
764 if (level || (last_sep <= sep))
765 return NULL;
767 len = MIN(last_sep - sep, buf_size - 1);
768 strncpy(buf, sep + 1, len);
769 buf[len] = 0;
770 return buf;
773 /* Return the tag found at index i and write its value in buf.
774 The return value is buf if the tag had a value, or NULL if not.
776 intval is used with conditionals/enums: when this function is called,
777 intval should contain the number of options in the conditional/enum.
778 When this function returns, intval is -1 if the tag is non numeric or,
779 if the tag is numeric, *intval is the enum case we want to go to (between 1
780 and the original value of *intval, inclusive).
781 When not treating a conditional/enum, intval should be NULL.
783 static const char *get_token_value(struct gui_wps *gwps,
784 struct wps_token *token,
785 char *buf, int buf_size,
786 int *intval)
788 if (!gwps)
789 return NULL;
791 struct wps_data *data = gwps->data;
792 struct wps_state *state = gwps->state;
794 if (!data || !state)
795 return NULL;
797 struct mp3entry *id3;
799 if (token->next)
800 id3 = state->nid3;
801 else
802 id3 = state->id3;
804 if (!id3)
805 return NULL;
807 #if CONFIG_RTC
808 struct tm* tm = NULL;
810 /* if the token is an RTC one, update the time
811 and do the necessary checks */
812 if (token->type >= WPS_TOKENS_RTC_BEGIN
813 && token->type <= WPS_TOKENS_RTC_END)
815 tm = get_time();
817 if (!valid_time(tm))
818 return NULL;
820 #endif
822 int limit = 1;
823 if (intval)
825 limit = *intval;
826 *intval = -1;
829 switch (token->type)
831 case WPS_TOKEN_CHARACTER:
832 return &(token->value.c);
834 case WPS_TOKEN_STRING:
835 return data->strings[token->value.i];
837 case WPS_TOKEN_TRACK_TIME_ELAPSED:
838 format_time(buf, buf_size,
839 id3->elapsed + state->ff_rewind_count);
840 return buf;
842 case WPS_TOKEN_TRACK_TIME_REMAINING:
843 format_time(buf, buf_size,
844 id3->length - id3->elapsed -
845 state->ff_rewind_count);
846 return buf;
848 case WPS_TOKEN_TRACK_LENGTH:
849 format_time(buf, buf_size, id3->length);
850 return buf;
852 case WPS_TOKEN_PLAYLIST_ENTRIES:
853 snprintf(buf, buf_size, "%d", playlist_amount());
854 return buf;
856 case WPS_TOKEN_PLAYLIST_NAME:
857 return playlist_name(NULL, buf, buf_size);
859 case WPS_TOKEN_PLAYLIST_POSITION:
860 snprintf(buf, buf_size, "%d", playlist_get_display_index());
861 return buf;
863 case WPS_TOKEN_PLAYLIST_SHUFFLE:
864 if ( global_settings.playlist_shuffle )
865 return "s";
866 else
867 return NULL;
868 break;
870 case WPS_TOKEN_VOLUME:
871 snprintf(buf, buf_size, "%d", global_settings.volume);
872 if (intval)
874 if (global_settings.volume == sound_min(SOUND_VOLUME))
876 *intval = 1;
878 else if (global_settings.volume == 0)
880 *intval = limit - 1;
882 else if (global_settings.volume > 0)
884 *intval = limit;
886 else
888 *intval = (limit - 3) * (global_settings.volume
889 - sound_min(SOUND_VOLUME) - 1)
890 / (-1 - sound_min(SOUND_VOLUME)) + 2;
893 return buf;
895 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
896 if (id3->length <= 0)
897 return NULL;
899 if (intval)
901 *intval = limit * (id3->elapsed + state->ff_rewind_count)
902 / id3->length + 1;
904 snprintf(buf, buf_size, "%d",
905 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
906 return buf;
908 case WPS_TOKEN_METADATA_ARTIST:
909 return id3->artist;
911 case WPS_TOKEN_METADATA_COMPOSER:
912 return id3->composer;
914 case WPS_TOKEN_METADATA_ALBUM:
915 return id3->album;
917 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
918 return id3->albumartist;
920 case WPS_TOKEN_METADATA_GROUPING:
921 return id3->grouping;
923 case WPS_TOKEN_METADATA_GENRE:
924 return id3->genre_string;
926 case WPS_TOKEN_METADATA_DISC_NUMBER:
927 if (id3->disc_string)
928 return id3->disc_string;
929 if (id3->discnum) {
930 snprintf(buf, buf_size, "%d", id3->discnum);
931 return buf;
933 return NULL;
935 case WPS_TOKEN_METADATA_TRACK_NUMBER:
936 if (id3->track_string)
937 return id3->track_string;
939 if (id3->tracknum) {
940 snprintf(buf, buf_size, "%d", id3->tracknum);
941 return buf;
943 return NULL;
945 case WPS_TOKEN_METADATA_TRACK_TITLE:
946 return id3->title;
948 case WPS_TOKEN_METADATA_VERSION:
949 switch (id3->id3version)
951 case ID3_VER_1_0:
952 return "1";
954 case ID3_VER_1_1:
955 return "1.1";
957 case ID3_VER_2_2:
958 return "2.2";
960 case ID3_VER_2_3:
961 return "2.3";
963 case ID3_VER_2_4:
964 return "2.4";
966 default:
967 return NULL;
970 case WPS_TOKEN_METADATA_YEAR:
971 if( id3->year_string )
972 return id3->year_string;
974 if (id3->year) {
975 snprintf(buf, buf_size, "%d", id3->year);
976 return buf;
978 return NULL;
980 case WPS_TOKEN_METADATA_COMMENT:
981 return id3->comment;
983 #ifdef HAVE_ALBUMART
984 case WPS_TOKEN_ALBUMART_DISPLAY:
985 draw_album_art(gwps, audio_current_aa_hid(), false);
986 return NULL;
988 case WPS_TOKEN_ALBUMART_FOUND:
989 if (audio_current_aa_hid() >= 0) {
990 return "C";
992 return NULL;
993 #endif
995 case WPS_TOKEN_FILE_BITRATE:
996 if(id3->bitrate)
997 snprintf(buf, buf_size, "%d", id3->bitrate);
998 else
999 return "?";
1000 return buf;
1002 case WPS_TOKEN_FILE_CODEC:
1003 if (intval)
1005 if(id3->codectype == AFMT_UNKNOWN)
1006 *intval = AFMT_NUM_CODECS;
1007 else
1008 *intval = id3->codectype;
1010 return get_codectype(id3);
1012 case WPS_TOKEN_FILE_FREQUENCY:
1013 snprintf(buf, buf_size, "%ld", id3->frequency);
1014 return buf;
1016 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
1017 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1018 if ((id3->frequency % 1000) < 100)
1019 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1020 else
1021 snprintf(buf, buf_size, "%ld.%d",
1022 id3->frequency / 1000,
1023 (id3->frequency % 1000) / 100);
1024 return buf;
1026 case WPS_TOKEN_FILE_NAME:
1027 if (get_dir(buf, buf_size, id3->path, 0)) {
1028 /* Remove extension */
1029 char* sep = strrchr(buf, '.');
1030 if (NULL != sep) {
1031 *sep = 0;
1033 return buf;
1035 else {
1036 return NULL;
1039 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1040 return get_dir(buf, buf_size, id3->path, 0);
1042 case WPS_TOKEN_FILE_PATH:
1043 return id3->path;
1045 case WPS_TOKEN_FILE_SIZE:
1046 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1047 return buf;
1049 case WPS_TOKEN_FILE_VBR:
1050 return id3->vbr ? "(avg)" : NULL;
1052 case WPS_TOKEN_FILE_DIRECTORY:
1053 return get_dir(buf, buf_size, id3->path, token->value.i);
1055 case WPS_TOKEN_BATTERY_PERCENT:
1057 int l = battery_level();
1059 if (intval)
1061 limit = MAX(limit, 2);
1062 if (l > -1) {
1063 /* First enum is used for "unknown level". */
1064 *intval = (limit - 1) * l / 100 + 2;
1065 } else {
1066 *intval = 1;
1070 if (l > -1) {
1071 snprintf(buf, buf_size, "%d", l);
1072 return buf;
1073 } else {
1074 return "?";
1078 case WPS_TOKEN_BATTERY_VOLTS:
1080 unsigned int v = battery_voltage();
1081 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1082 return buf;
1085 case WPS_TOKEN_BATTERY_TIME:
1087 int t = battery_time();
1088 if (t >= 0)
1089 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1090 else
1091 return "?h ?m";
1092 return buf;
1095 #if CONFIG_CHARGING
1096 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1098 if(charger_input_state==CHARGER)
1099 return "p";
1100 else
1101 return NULL;
1103 #endif
1104 #if CONFIG_CHARGING >= CHARGING_MONITOR
1105 case WPS_TOKEN_BATTERY_CHARGING:
1107 if (charge_state == CHARGING || charge_state == TOPOFF) {
1108 return "c";
1109 } else {
1110 return NULL;
1113 #endif
1114 case WPS_TOKEN_BATTERY_SLEEPTIME:
1116 if (get_sleep_timer() == 0)
1117 return NULL;
1118 else
1120 format_time(buf, buf_size, get_sleep_timer() * 1000);
1121 return buf;
1125 case WPS_TOKEN_PLAYBACK_STATUS:
1127 int status = audio_status();
1128 int mode = 1;
1129 if (status == AUDIO_STATUS_PLAY)
1130 mode = 2;
1131 if (wps_fading_out ||
1132 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1133 mode = 3;
1134 if (status_get_ffmode() == STATUS_FASTFORWARD)
1135 mode = 4;
1136 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1137 mode = 5;
1139 if (intval) {
1140 *intval = mode;
1143 snprintf(buf, buf_size, "%d", mode-1);
1144 return buf;
1147 case WPS_TOKEN_REPEAT_MODE:
1148 if (intval)
1149 *intval = global_settings.repeat_mode + 1;
1150 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1151 return buf;
1152 case WPS_TOKEN_RTC_12HOUR_CFG:
1153 if (intval)
1154 *intval = global_settings.timeformat + 1;
1155 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1156 return buf;
1157 #if CONFIG_RTC
1158 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1159 /* d: day of month (01..31) */
1160 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1161 return buf;
1163 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1164 /* e: day of month, blank padded ( 1..31) */
1165 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1166 return buf;
1168 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1169 /* H: hour (00..23) */
1170 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1171 return buf;
1173 case WPS_TOKEN_RTC_HOUR_24:
1174 /* k: hour ( 0..23) */
1175 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1176 return buf;
1178 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1179 /* I: hour (01..12) */
1180 snprintf(buf, buf_size, "%02d",
1181 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1182 return buf;
1184 case WPS_TOKEN_RTC_HOUR_12:
1185 /* l: hour ( 1..12) */
1186 snprintf(buf, buf_size, "%2d",
1187 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1188 return buf;
1190 case WPS_TOKEN_RTC_MONTH:
1191 /* m: month (01..12) */
1192 if (intval)
1193 *intval = tm->tm_mon + 1;
1194 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1195 return buf;
1197 case WPS_TOKEN_RTC_MINUTE:
1198 /* M: minute (00..59) */
1199 snprintf(buf, buf_size, "%02d", tm->tm_min);
1200 return buf;
1202 case WPS_TOKEN_RTC_SECOND:
1203 /* S: second (00..59) */
1204 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1205 return buf;
1207 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1208 /* y: last two digits of year (00..99) */
1209 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1210 return buf;
1212 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1213 /* Y: year (1970...) */
1214 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1215 return buf;
1217 case WPS_TOKEN_RTC_AM_PM_UPPER:
1218 /* p: upper case AM or PM indicator */
1219 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1221 case WPS_TOKEN_RTC_AM_PM_LOWER:
1222 /* P: lower case am or pm indicator */
1223 return tm->tm_hour/12 == 0 ? "am" : "pm";
1225 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1226 /* a: abbreviated weekday name (Sun..Sat) */
1227 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1229 case WPS_TOKEN_RTC_MONTH_NAME:
1230 /* b: abbreviated month name (Jan..Dec) */
1231 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1233 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1234 /* u: day of week (1..7); 1 is Monday */
1235 if (intval)
1236 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1237 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1238 return buf;
1240 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1241 /* w: day of week (0..6); 0 is Sunday */
1242 if (intval)
1243 *intval = tm->tm_wday + 1;
1244 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1245 return buf;
1246 #else
1247 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1248 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1249 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1250 case WPS_TOKEN_RTC_HOUR_24:
1251 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1252 case WPS_TOKEN_RTC_HOUR_12:
1253 case WPS_TOKEN_RTC_MONTH:
1254 case WPS_TOKEN_RTC_MINUTE:
1255 case WPS_TOKEN_RTC_SECOND:
1256 case WPS_TOKEN_RTC_AM_PM_UPPER:
1257 case WPS_TOKEN_RTC_AM_PM_LOWER:
1258 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1259 return "--";
1260 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1261 return "----";
1262 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1263 case WPS_TOKEN_RTC_MONTH_NAME:
1264 return "---";
1265 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1266 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1267 return "-";
1268 #endif
1270 #ifdef HAVE_LCD_CHARCELLS
1271 case WPS_TOKEN_PROGRESSBAR:
1273 char *end = utf8encode(data->wps_progress_pat[0], buf);
1274 *end = '\0';
1275 return buf;
1278 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1279 if(is_new_player())
1281 /* we need 11 characters (full line) for
1282 progress-bar */
1283 strncpy(buf, " ", buf_size);
1285 else
1287 /* Tell the user if we have an OldPlayer */
1288 strncpy(buf, " <Old LCD> ", buf_size);
1290 return buf;
1291 #endif
1293 #ifdef HAVE_TAGCACHE
1294 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1295 if (intval) {
1296 *intval = id3->playcount + 1;
1298 snprintf(buf, buf_size, "%ld", id3->playcount);
1299 return buf;
1301 case WPS_TOKEN_DATABASE_RATING:
1302 if (intval) {
1303 *intval = id3->rating + 1;
1305 snprintf(buf, buf_size, "%d", id3->rating);
1306 return buf;
1308 case WPS_TOKEN_DATABASE_AUTOSCORE:
1309 if (intval)
1310 *intval = id3->score + 1;
1312 snprintf(buf, buf_size, "%d", id3->score);
1313 return buf;
1314 #endif
1316 #if (CONFIG_CODEC == SWCODEC)
1317 case WPS_TOKEN_CROSSFADE:
1318 if (intval)
1319 *intval = global_settings.crossfade + 1;
1320 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1321 return buf;
1323 case WPS_TOKEN_REPLAYGAIN:
1325 int val;
1327 if (global_settings.replaygain == 0)
1328 val = 1; /* off */
1329 else
1331 int type =
1332 get_replaygain_mode(id3->track_gain_string != NULL,
1333 id3->album_gain_string != NULL);
1334 if (type < 0)
1335 val = 6; /* no tag */
1336 else
1337 val = type + 2;
1339 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1340 val += 2;
1343 if (intval)
1344 *intval = val;
1346 switch (val)
1348 case 1:
1349 case 6:
1350 return "+0.00 dB";
1351 break;
1352 case 2:
1353 case 4:
1354 strncpy(buf, id3->track_gain_string, buf_size);
1355 break;
1356 case 3:
1357 case 5:
1358 strncpy(buf, id3->album_gain_string, buf_size);
1359 break;
1361 return buf;
1363 #endif /* (CONFIG_CODEC == SWCODEC) */
1365 #if (CONFIG_CODEC != MAS3507D)
1366 case WPS_TOKEN_SOUND_PITCH:
1368 int val = sound_get_pitch();
1369 snprintf(buf, buf_size, "%d.%d",
1370 val / 10, val % 10);
1371 return buf;
1373 #endif
1375 case WPS_TOKEN_MAIN_HOLD:
1376 #ifdef HAS_BUTTON_HOLD
1377 if (button_hold())
1378 #else
1379 if (is_keys_locked())
1380 #endif /*hold switch or softlock*/
1381 return "h";
1382 else
1383 return NULL;
1385 #ifdef HAS_REMOTE_BUTTON_HOLD
1386 case WPS_TOKEN_REMOTE_HOLD:
1387 if (remote_button_hold())
1388 return "r";
1389 else
1390 return NULL;
1391 #endif
1393 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1394 case WPS_TOKEN_VLED_HDD:
1395 if(led_read(HZ/2))
1396 return "h";
1397 else
1398 return NULL;
1399 #endif
1400 case WPS_TOKEN_BUTTON_VOLUME:
1401 if (data->button_time_volume &&
1402 TIME_BEFORE(current_tick, data->button_time_volume +
1403 token->value.i * TIMEOUT_UNIT))
1404 return "v";
1405 return NULL;
1407 case WPS_TOKEN_SETTING:
1409 if (intval)
1411 /* Handle contionals */
1412 const struct settings_list *s = settings+token->value.i;
1413 switch (s->flags&F_T_MASK)
1415 case F_T_INT:
1416 case F_T_UINT:
1417 if (s->flags&F_RGB)
1418 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1419 /* shouldn't overflow since colors are stored
1420 * on 16 bits ...
1421 * but this is pretty useless anyway */
1422 *intval = *(int*)s->setting + 1;
1423 else if (s->cfg_vals == NULL)
1424 /* %?St|name|<1st choice|2nd choice|...> */
1425 *intval = (*(int*)s->setting-s->int_setting->min)
1426 /s->int_setting->step + 1;
1427 else
1428 /* %?St|name|<1st choice|2nd choice|...> */
1429 /* Not sure about this one. cfg_name/vals are
1430 * indexed from 0 right? */
1431 *intval = *(int*)s->setting + 1;
1432 break;
1433 case F_T_BOOL:
1434 /* %?St|name|<if true|if false> */
1435 *intval = *(bool*)s->setting?1:2;
1436 break;
1437 case F_T_CHARPTR:
1438 /* %?St|name|<if non empty string|if empty>
1439 * The string's emptyness discards the setting's
1440 * prefix and suffix */
1441 *intval = ((char*)s->setting)[0]?1:2;
1442 break;
1443 default:
1444 /* This shouldn't happen ... but you never know */
1445 *intval = -1;
1446 break;
1449 cfg_to_string(token->value.i,buf,buf_size);
1450 return buf;
1453 default:
1454 return NULL;
1458 /* Return the index to the end token for the conditional token at index.
1459 The conditional token can be either a start token or a separator
1460 (i.e. option) token.
1462 static int find_conditional_end(struct wps_data *data, int index)
1464 int ret = index;
1465 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1466 ret = data->tokens[ret].value.i;
1468 /* ret now is the index to the end token for the conditional. */
1469 return ret;
1472 /* Evaluate the conditional that is at *token_index and return whether a skip
1473 has ocurred. *token_index is updated with the new position.
1475 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1477 if (!gwps)
1478 return false;
1480 struct wps_data *data = gwps->data;
1482 int i, cond_end;
1483 int cond_index = *token_index;
1484 char result[128];
1485 const char *value;
1486 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1487 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1489 /* treat ?xx<true> constructs as if they had 2 options. */
1490 if (num_options < 2)
1491 num_options = 2;
1493 int intval = num_options;
1494 /* get_token_value needs to know the number of options in the enum */
1495 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1496 result, sizeof(result), &intval);
1498 /* intval is now the number of the enum option we want to read,
1499 starting from 1. If intval is -1, we check if value is empty. */
1500 if (intval == -1)
1501 intval = (value && *value) ? 1 : num_options;
1502 else if (intval > num_options || intval < 1)
1503 intval = num_options;
1505 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1507 /* skip to the appropriate enum case */
1508 int next = cond_index + 2;
1509 for (i = 1; i < intval; i++)
1511 next = data->tokens[next].value.i;
1513 *token_index = next;
1515 if (prev_val == intval)
1517 /* Same conditional case as previously. Return without clearing the
1518 pictures */
1519 return false;
1522 cond_end = find_conditional_end(data, cond_index + 2);
1523 for (i = cond_index + 3; i < cond_end; i++)
1525 #ifdef HAVE_LCD_BITMAP
1526 /* clear all pictures in the conditional and nested ones */
1527 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1528 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1529 #endif
1530 #ifdef HAVE_ALBUMART
1531 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1532 draw_album_art(gwps, audio_current_aa_hid(), true);
1533 #endif
1536 return true;
1539 /* Read a (sub)line to the given alignment format buffer.
1540 linebuf is the buffer where the data is actually stored.
1541 align is the alignment format that'll be used to display the text.
1542 The return value indicates whether the line needs to be updated.
1544 static bool get_line(struct gui_wps *gwps,
1545 int line, int subline,
1546 struct align_pos *align,
1547 char *linebuf,
1548 int linebuf_size)
1550 struct wps_data *data = gwps->data;
1552 char temp_buf[128];
1553 char *buf = linebuf; /* will always point to the writing position */
1554 char *linebuf_end = linebuf + linebuf_size - 1;
1555 int i, last_token_idx;
1556 bool update = false;
1558 /* alignment-related variables */
1559 int cur_align;
1560 char* cur_align_start;
1561 cur_align_start = buf;
1562 cur_align = WPS_ALIGN_LEFT;
1563 align->left = NULL;
1564 align->center = NULL;
1565 align->right = NULL;
1567 /* Process all tokens of the desired subline */
1568 last_token_idx = wps_last_token_index(data, line, subline);
1569 for (i = wps_first_token_index(data, line, subline);
1570 i <= last_token_idx; i++)
1572 switch(data->tokens[i].type)
1574 case WPS_TOKEN_CONDITIONAL:
1575 /* place ourselves in the right conditional case */
1576 update |= evaluate_conditional(gwps, &i);
1577 break;
1579 case WPS_TOKEN_CONDITIONAL_OPTION:
1580 /* we've finished in the curent conditional case,
1581 skip to the end of the conditional structure */
1582 i = find_conditional_end(data, i);
1583 break;
1585 #ifdef HAVE_LCD_BITMAP
1586 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1588 struct gui_img *img = data->img;
1589 int n = data->tokens[i].value.i & 0xFF;
1590 int subimage = data->tokens[i].value.i >> 8;
1592 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1593 img[n].display = subimage;
1594 break;
1596 #endif
1598 case WPS_TOKEN_ALIGN_LEFT:
1599 case WPS_TOKEN_ALIGN_CENTER:
1600 case WPS_TOKEN_ALIGN_RIGHT:
1601 /* remember where the current aligned text started */
1602 switch (cur_align)
1604 case WPS_ALIGN_LEFT:
1605 align->left = cur_align_start;
1606 break;
1608 case WPS_ALIGN_CENTER:
1609 align->center = cur_align_start;
1610 break;
1612 case WPS_ALIGN_RIGHT:
1613 align->right = cur_align_start;
1614 break;
1616 /* start a new alignment */
1617 switch (data->tokens[i].type)
1619 case WPS_TOKEN_ALIGN_LEFT:
1620 cur_align = WPS_ALIGN_LEFT;
1621 break;
1622 case WPS_TOKEN_ALIGN_CENTER:
1623 cur_align = WPS_ALIGN_CENTER;
1624 break;
1625 case WPS_TOKEN_ALIGN_RIGHT:
1626 cur_align = WPS_ALIGN_RIGHT;
1627 break;
1628 default:
1629 break;
1631 *buf++ = 0;
1632 cur_align_start = buf;
1633 break;
1634 case WPS_VIEWPORT_ENABLE:
1636 char label = data->tokens[i].value.i;
1637 int j;
1638 char temp = VP_DRAW_HIDEABLE;
1639 for(j=0;j<data->num_viewports;j++)
1641 temp = VP_DRAW_HIDEABLE;
1642 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1643 (data->viewports[j].label == label))
1645 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1646 temp |= VP_DRAW_WASHIDDEN;
1647 data->viewports[j].hidden_flags = temp;
1651 break;
1652 default:
1654 /* get the value of the tag and copy it to the buffer */
1655 const char *value = get_token_value(gwps, &data->tokens[i],
1656 temp_buf, sizeof(temp_buf), NULL);
1657 if (value)
1659 update = true;
1660 while (*value && (buf < linebuf_end))
1661 *buf++ = *value++;
1663 break;
1668 /* close the current alignment */
1669 switch (cur_align)
1671 case WPS_ALIGN_LEFT:
1672 align->left = cur_align_start;
1673 break;
1675 case WPS_ALIGN_CENTER:
1676 align->center = cur_align_start;
1677 break;
1679 case WPS_ALIGN_RIGHT:
1680 align->right = cur_align_start;
1681 break;
1684 return update;
1687 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1689 struct wps_data *data = gwps->data;
1690 int i;
1691 int subline_idx = wps_subline_index(data, line, subline);
1692 int last_token_idx = wps_last_token_index(data, line, subline);
1694 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1696 for (i = wps_first_token_index(data, line, subline);
1697 i <= last_token_idx; i++)
1699 switch(data->tokens[i].type)
1701 case WPS_TOKEN_CONDITIONAL:
1702 /* place ourselves in the right conditional case */
1703 evaluate_conditional(gwps, &i);
1704 break;
1706 case WPS_TOKEN_CONDITIONAL_OPTION:
1707 /* we've finished in the curent conditional case,
1708 skip to the end of the conditional structure */
1709 i = find_conditional_end(data, i);
1710 break;
1712 case WPS_TOKEN_SUBLINE_TIMEOUT:
1713 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1714 break;
1716 default:
1717 break;
1722 /* Calculates which subline should be displayed for the specified line
1723 Returns true iff the subline must be refreshed */
1724 static bool update_curr_subline(struct gui_wps *gwps, int line)
1726 struct wps_data *data = gwps->data;
1728 int search, search_start, num_sublines;
1729 bool reset_subline;
1730 bool new_subline_refresh;
1731 bool only_one_subline;
1733 num_sublines = data->lines[line].num_sublines;
1734 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1735 new_subline_refresh = false;
1736 only_one_subline = false;
1738 /* if time to advance to next sub-line */
1739 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1740 reset_subline)
1742 /* search all sublines until the next subline with time > 0
1743 is found or we get back to the subline we started with */
1744 if (reset_subline)
1745 search_start = 0;
1746 else
1747 search_start = data->lines[line].curr_subline;
1749 for (search = 0; search < num_sublines; search++)
1751 data->lines[line].curr_subline++;
1753 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1754 if (data->lines[line].curr_subline == num_sublines)
1756 if (data->lines[line].curr_subline == 1)
1757 only_one_subline = true;
1758 data->lines[line].curr_subline = 0;
1761 /* if back where we started after search or
1762 only one subline is defined on the line */
1763 if (((search > 0) &&
1764 (data->lines[line].curr_subline == search_start)) ||
1765 only_one_subline)
1767 /* no other subline with a time > 0 exists */
1768 data->lines[line].subline_expire_time = (reset_subline ?
1769 current_tick :
1770 data->lines[line].subline_expire_time) + 100 * HZ;
1771 break;
1773 else
1775 /* get initial time multiplier for this subline */
1776 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1778 int subline_idx = wps_subline_index(data, line,
1779 data->lines[line].curr_subline);
1781 /* only use this subline if subline time > 0 */
1782 if (data->sublines[subline_idx].time_mult > 0)
1784 new_subline_refresh = true;
1785 data->lines[line].subline_expire_time = (reset_subline ?
1786 current_tick : data->lines[line].subline_expire_time) +
1787 TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
1788 break;
1794 return new_subline_refresh;
1797 /* Display a line appropriately according to its alignment format.
1798 format_align contains the text, separated between left, center and right.
1799 line is the index of the line on the screen.
1800 scroll indicates whether the line is a scrolling one or not.
1802 static void write_line(struct screen *display,
1803 struct align_pos *format_align,
1804 int line,
1805 bool scroll)
1808 int left_width = 0, left_xpos;
1809 int center_width = 0, center_xpos;
1810 int right_width = 0, right_xpos;
1811 int ypos;
1812 int space_width;
1813 int string_height;
1814 int scroll_width;
1816 /* calculate different string sizes and positions */
1817 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1818 if (format_align->left != 0) {
1819 display->getstringsize((unsigned char *)format_align->left,
1820 &left_width, &string_height);
1823 if (format_align->right != 0) {
1824 display->getstringsize((unsigned char *)format_align->right,
1825 &right_width, &string_height);
1828 if (format_align->center != 0) {
1829 display->getstringsize((unsigned char *)format_align->center,
1830 &center_width, &string_height);
1833 left_xpos = 0;
1834 right_xpos = (display->getwidth() - right_width);
1835 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1837 scroll_width = display->getwidth() - left_xpos;
1839 /* Checks for overlapping strings.
1840 If needed the overlapping strings will be merged, separated by a
1841 space */
1843 /* CASE 1: left and centered string overlap */
1844 /* there is a left string, need to merge left and center */
1845 if ((left_width != 0 && center_width != 0) &&
1846 (left_xpos + left_width + space_width > center_xpos)) {
1847 /* replace the former separator '\0' of left and
1848 center string with a space */
1849 *(--format_align->center) = ' ';
1850 /* calculate the new width and position of the merged string */
1851 left_width = left_width + space_width + center_width;
1852 /* there is no centered string anymore */
1853 center_width = 0;
1855 /* there is no left string, move center to left */
1856 if ((left_width == 0 && center_width != 0) &&
1857 (left_xpos + left_width > center_xpos)) {
1858 /* move the center string to the left string */
1859 format_align->left = format_align->center;
1860 /* calculate the new width and position of the string */
1861 left_width = center_width;
1862 /* there is no centered string anymore */
1863 center_width = 0;
1866 /* CASE 2: centered and right string overlap */
1867 /* there is a right string, need to merge center and right */
1868 if ((center_width != 0 && right_width != 0) &&
1869 (center_xpos + center_width + space_width > right_xpos)) {
1870 /* replace the former separator '\0' of center and
1871 right string with a space */
1872 *(--format_align->right) = ' ';
1873 /* move the center string to the right after merge */
1874 format_align->right = format_align->center;
1875 /* calculate the new width and position of the merged string */
1876 right_width = center_width + space_width + right_width;
1877 right_xpos = (display->getwidth() - right_width);
1878 /* there is no centered string anymore */
1879 center_width = 0;
1881 /* there is no right string, move center to right */
1882 if ((center_width != 0 && right_width == 0) &&
1883 (center_xpos + center_width > right_xpos)) {
1884 /* move the center string to the right string */
1885 format_align->right = format_align->center;
1886 /* calculate the new width and position of the string */
1887 right_width = center_width;
1888 right_xpos = (display->getwidth() - right_width);
1889 /* there is no centered string anymore */
1890 center_width = 0;
1893 /* CASE 3: left and right overlap
1894 There is no center string anymore, either there never
1895 was one or it has been merged in case 1 or 2 */
1896 /* there is a left string, need to merge left and right */
1897 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1898 (left_xpos + left_width + space_width > right_xpos)) {
1899 /* replace the former separator '\0' of left and
1900 right string with a space */
1901 *(--format_align->right) = ' ';
1902 /* calculate the new width and position of the string */
1903 left_width = left_width + space_width + right_width;
1904 /* there is no right string anymore */
1905 right_width = 0;
1907 /* there is no left string, move right to left */
1908 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1909 (left_width > right_xpos)) {
1910 /* move the right string to the left string */
1911 format_align->left = format_align->right;
1912 /* calculate the new width and position of the string */
1913 left_width = right_width;
1914 /* there is no right string anymore */
1915 right_width = 0;
1918 ypos = (line * string_height);
1921 if (scroll && ((left_width > scroll_width) ||
1922 (center_width > scroll_width) ||
1923 (right_width > scroll_width)))
1925 display->puts_scroll(0, line,
1926 (unsigned char *)format_align->left);
1928 else
1930 #ifdef HAVE_LCD_BITMAP
1931 /* clear the line first */
1932 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1933 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1934 display->set_drawmode(DRMODE_SOLID);
1935 #endif
1937 /* Nasty hack: we output an empty scrolling string,
1938 which will reset the scroller for that line */
1939 display->puts_scroll(0, line, (unsigned char *)"");
1941 /* print aligned strings */
1942 if (left_width != 0)
1944 display->putsxy(left_xpos, ypos,
1945 (unsigned char *)format_align->left);
1947 if (center_width != 0)
1949 display->putsxy(center_xpos, ypos,
1950 (unsigned char *)format_align->center);
1952 if (right_width != 0)
1954 display->putsxy(right_xpos, ypos,
1955 (unsigned char *)format_align->right);
1960 /* Refresh the WPS according to refresh_mode. */
1961 bool gui_wps_refresh(struct gui_wps *gwps,
1962 int ffwd_offset,
1963 unsigned char refresh_mode)
1965 struct wps_data *data = gwps->data;
1966 struct screen *display = gwps->display;
1967 struct wps_state *state = gwps->state;
1969 if(!gwps || !data || !state || !display)
1970 return false;
1972 int v, line, i, subline_idx;
1973 unsigned char flags;
1974 char linebuf[MAX_PATH];
1975 unsigned char vp_refresh_mode;
1977 struct align_pos align;
1978 align.left = NULL;
1979 align.center = NULL;
1980 align.right = NULL;
1982 bool update_line, new_subline_refresh;
1984 #ifdef HAVE_LCD_BITMAP
1985 gui_wps_statusbar_draw(gwps, true);
1987 /* to find out wether the peak meter is enabled we
1988 assume it wasn't until we find a line that contains
1989 the peak meter. We can't use peak_meter_enabled itself
1990 because that would mean to turn off the meter thread
1991 temporarily. (That shouldn't matter unless yield
1992 or sleep is called but who knows...)
1994 bool enable_pm = false;
1996 #endif
1998 /* reset to first subline if refresh all flag is set */
1999 if (refresh_mode == WPS_REFRESH_ALL)
2001 display->set_viewport(&data->viewports[0].vp);
2002 display->clear_viewport();
2004 for (i = 0; i <= data->num_lines; i++)
2006 data->lines[i].curr_subline = SUBLINE_RESET;
2010 #ifdef HAVE_LCD_CHARCELLS
2011 for (i = 0; i < 8; i++)
2013 if (data->wps_progress_pat[i] == 0)
2014 data->wps_progress_pat[i] = display->get_locked_pattern();
2016 #endif
2018 if (!state->id3)
2020 display->stop_scroll();
2021 return false;
2024 state->ff_rewind_count = ffwd_offset;
2026 /* disable any viewports which are conditionally displayed */
2027 for (v = 0; v < data->num_viewports; v++)
2029 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
2031 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
2032 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2033 else
2034 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
2037 for (v = 0; v < data->num_viewports; v++)
2039 display->set_viewport(&data->viewports[v].vp);
2040 vp_refresh_mode = refresh_mode;
2042 #ifdef HAVE_LCD_BITMAP
2043 /* Set images to not to be displayed */
2044 for (i = 0; i < MAX_IMAGES; i++)
2046 data->img[i].display = -1;
2048 #endif
2049 /* dont redraw the viewport if its disabled */
2050 if ((data->viewports[v].hidden_flags&VP_DRAW_HIDDEN))
2052 if (!(data->viewports[v].hidden_flags&VP_DRAW_WASHIDDEN))
2053 display->scroll_stop(&data->viewports[v].vp);
2054 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2055 continue;
2057 else if (((data->viewports[v].hidden_flags&
2058 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2059 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2061 vp_refresh_mode = WPS_REFRESH_ALL;
2062 data->viewports[v].hidden_flags = VP_DRAW_HIDEABLE;
2064 if (vp_refresh_mode == WPS_REFRESH_ALL)
2066 display->clear_viewport();
2069 for (line = data->viewports[v].first_line;
2070 line <= data->viewports[v].last_line; line++)
2072 memset(linebuf, 0, sizeof(linebuf));
2073 update_line = false;
2075 /* get current subline for the line */
2076 new_subline_refresh = update_curr_subline(gwps, line);
2078 subline_idx = wps_subline_index(data, line,
2079 data->lines[line].curr_subline);
2080 flags = data->sublines[subline_idx].line_type;
2082 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2083 || new_subline_refresh)
2085 /* get_line tells us if we need to update the line */
2086 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2087 &align, linebuf, sizeof(linebuf));
2089 #ifdef HAVE_LCD_BITMAP
2090 /* peakmeter */
2091 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2093 /* the peakmeter should be alone on its line */
2094 update_line = false;
2096 int h = font_get(data->viewports[v].vp.font)->height;
2097 int peak_meter_y = (line - data->viewports[v].first_line)* h;
2099 /* The user might decide to have the peak meter in the last
2100 line so that it is only displayed if no status bar is
2101 visible. If so we neither want do draw nor enable the
2102 peak meter. */
2103 if (peak_meter_y + h <= display->getheight()) {
2104 /* found a line with a peak meter -> remember that we must
2105 enable it later */
2106 enable_pm = true;
2107 peak_meter_enabled = true;
2108 peak_meter_screen(gwps->display, 0, peak_meter_y,
2109 MIN(h, display->getheight() - peak_meter_y));
2111 else
2113 peak_meter_enabled = false;
2117 #else /* HAVE_LCD_CHARCELL */
2119 /* progressbar */
2120 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2122 if (data->full_line_progressbar)
2123 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2124 else
2125 draw_player_progress(gwps);
2127 #endif
2129 if (update_line &&
2130 /* conditionals clear the line which means if the %Vd is put into the default
2131 viewport there will be a blank line.
2132 To get around this we dont allow any actual drawing to happen in the
2133 deault vp if other vp's are defined */
2134 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2136 if (flags & WPS_REFRESH_SCROLL)
2138 /* if the line is a scrolling one we don't want to update
2139 too often, so that it has the time to scroll */
2140 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2141 write_line(display, &align, line - data->viewports[v].first_line, true);
2143 else
2144 write_line(display, &align, line - data->viewports[v].first_line, false);
2148 #ifdef HAVE_LCD_BITMAP
2149 /* progressbar */
2150 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2152 if (data->viewports[v].pb)
2153 draw_progressbar(gwps, data->viewports[v].pb);
2155 /* Now display any images in this viewport */
2156 wps_display_images(gwps, &data->viewports[v].vp);
2157 #endif
2160 #ifdef HAVE_LCD_BITMAP
2161 data->peak_meter_enabled = enable_pm;
2162 #endif
2164 /* Restore the default viewport */
2165 display->set_viewport(NULL);
2167 display->update();
2169 #ifdef HAVE_BACKLIGHT
2170 if (global_settings.caption_backlight && state->id3)
2172 /* turn on backlight n seconds before track ends, and turn it off n
2173 seconds into the new track. n == backlight_timeout, or 5s */
2174 int n = global_settings.backlight_timeout * 1000;
2176 if ( n < 1000 )
2177 n = 5000; /* use 5s if backlight is always on or off */
2179 if (((state->id3->elapsed < 1000) ||
2180 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2181 (state->paused == false))
2182 backlight_on();
2184 #endif
2185 #ifdef HAVE_REMOTE_LCD
2186 if (global_settings.remote_caption_backlight && state->id3)
2188 /* turn on remote backlight n seconds before track ends, and turn it
2189 off n seconds into the new track. n == remote_backlight_timeout,
2190 or 5s */
2191 int n = global_settings.remote_backlight_timeout * 1000;
2193 if ( n < 1000 )
2194 n = 5000; /* use 5s if backlight is always on or off */
2196 if (((state->id3->elapsed < 1000) ||
2197 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2198 (state->paused == false))
2199 remote_backlight_on();
2201 #endif
2203 return true;