Cleanup play_hop() slightly and remove redundant/uneeded checks, reclaim a tiny bit...
[kugel-rb.git] / apps / gui / gwps-common.c
blobb43c35740897ba36ad6ce1f45525f7b907bbc2dc
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002-2007 Björn Stenberg
11 * Copyright (C) 2007-2008 Nicolas Pennequin
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "gwps-common.h"
23 #include "font.h"
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include "system.h"
28 #include "settings.h"
29 #include "settings_list.h"
30 #include "rbunicode.h"
31 #include "rtc.h"
32 #include "audio.h"
33 #include "status.h"
34 #include "power.h"
35 #include "powermgmt.h"
36 #include "sound.h"
37 #include "debug.h"
38 #ifdef HAVE_LCD_CHARCELLS
39 #include "hwcompat.h"
40 #endif
41 #include "abrepeat.h"
42 #include "mp3_playback.h"
43 #include "backlight.h"
44 #include "lang.h"
45 #include "misc.h"
46 #include "splash.h"
47 #include "scrollbar.h"
48 #include "led.h"
49 #include "lcd.h"
50 #ifdef HAVE_LCD_BITMAP
51 #include "peakmeter.h"
52 /* Image stuff */
53 #include "bmp.h"
54 #include "albumart.h"
55 #endif
56 #include "dsp.h"
57 #include "action.h"
58 #include "cuesheet.h"
59 #include "playlist.h"
60 #if CONFIG_CODEC == SWCODEC
61 #include "playback.h"
62 #endif
63 #include "backdrop.h"
64 #include "viewport.h"
65 #include "pcmbuf.h"
67 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
68 /* 3% of 30min file == 54s step size */
69 #define MIN_FF_REWIND_STEP 500
71 /* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds
72 (possibly with a decimal fraction) but stored as integer values.
73 E.g. 2.5 is stored as 25. This means 25 tenth of a second, i.e. 25 units.
75 #define TIMEOUT_UNIT (HZ/10) /* I.e. 0.1 sec */
76 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* In TIMEOUT_UNIT's */
79 /* fades the volume */
80 bool wps_fading_out = false;
81 void fade(bool fade_in, bool updatewps)
83 int fp_global_vol = global_settings.volume << 8;
84 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
85 int fp_step = (fp_global_vol - fp_min_vol) / 30;
86 int i;
87 wps_fading_out = !fade_in;
88 if (fade_in) {
89 /* fade in */
90 int fp_volume = fp_min_vol;
92 /* zero out the sound */
93 sound_set_volume(fp_min_vol >> 8);
95 sleep(HZ/10); /* let audio thread run */
96 audio_resume();
98 while (fp_volume < fp_global_vol - fp_step) {
99 fp_volume += fp_step;
100 sound_set_volume(fp_volume >> 8);
101 if (updatewps)
103 FOR_NB_SCREENS(i)
104 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
106 sleep(1);
108 sound_set_volume(global_settings.volume);
110 else {
111 /* fade out */
112 int fp_volume = fp_global_vol;
114 while (fp_volume > fp_min_vol + fp_step) {
115 fp_volume -= fp_step;
116 sound_set_volume(fp_volume >> 8);
117 if (updatewps)
119 FOR_NB_SCREENS(i)
120 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
122 sleep(1);
124 audio_pause();
125 wps_fading_out = false;
126 #if CONFIG_CODEC != SWCODEC
127 #ifndef SIMULATOR
128 /* let audio thread run and wait for the mas to run out of data */
129 while (!mp3_pause_done())
130 #endif
131 sleep(HZ/10);
132 #endif
134 /* reset volume to what it was before the fade */
135 sound_set_volume(global_settings.volume);
139 /* return true if screen restore is needed
140 return false otherwise
142 bool update_onvol_change(struct gui_wps * gwps)
144 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
146 #ifdef HAVE_LCD_CHARCELLS
147 splashf(0, "Vol: %3d dB",
148 sound_val2phys(SOUND_VOLUME, global_settings.volume));
149 return true;
150 #endif
151 return false;
154 void play_hop(int direction)
156 unsigned step = ((unsigned)global_settings.skip_length*1000);
157 unsigned long *elapsed = &(wps_state.id3->elapsed);
159 if (direction == 1 && wps_state.id3->length - *elapsed < step+1000) {
160 #if CONFIG_CODEC == SWCODEC
161 if(global_settings.beep)
162 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
163 #endif
164 return;
165 } else if ((direction == -1 && *elapsed < step)) {
166 *elapsed = 0;
167 } else {
168 *elapsed += step * direction;
170 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
171 #if (CONFIG_CODEC == SWCODEC)
172 audio_pre_ff_rewind();
173 #else
174 audio_pause();
175 #endif
177 audio_ff_rewind(*elapsed);
178 #if (CONFIG_CODEC != SWCODEC)
179 if (!wps_state.paused)
180 audio_resume();
181 #endif
184 bool ffwd_rew(int button)
186 unsigned int step = 0; /* current ff/rewind step */
187 unsigned int max_step = 0; /* maximum ff/rewind step */
188 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
189 int direction = -1; /* forward=1 or backward=-1 */
190 bool exit = false;
191 bool usb = false;
192 int i = 0;
193 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
195 if (button == ACTION_NONE)
197 status_set_ffmode(0);
198 return usb;
200 while (!exit)
202 switch ( button )
204 case ACTION_WPS_SEEKFWD:
205 direction = 1;
206 case ACTION_WPS_SEEKBACK:
207 if (wps_state.ff_rewind)
209 if (direction == 1)
211 /* fast forwarding, calc max step relative to end */
212 max_step = (wps_state.id3->length -
213 (wps_state.id3->elapsed +
214 ff_rewind_count)) *
215 FF_REWIND_MAX_PERCENT / 100;
217 else
219 /* rewinding, calc max step relative to start */
220 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
221 FF_REWIND_MAX_PERCENT / 100;
224 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
226 if (step > max_step)
227 step = max_step;
229 ff_rewind_count += step * direction;
231 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
232 step += step >> ff_rw_accel;
234 else
236 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
237 wps_state.id3 && wps_state.id3->length )
239 if (!wps_state.paused)
240 #if (CONFIG_CODEC == SWCODEC)
241 audio_pre_ff_rewind();
242 #else
243 audio_pause();
244 #endif
245 #if CONFIG_KEYPAD == PLAYER_PAD
246 FOR_NB_SCREENS(i)
247 gui_wps[i].display->stop_scroll();
248 #endif
249 if (direction > 0)
250 status_set_ffmode(STATUS_FASTFORWARD);
251 else
252 status_set_ffmode(STATUS_FASTBACKWARD);
254 wps_state.ff_rewind = true;
256 step = 1000 * global_settings.ff_rewind_min_step;
258 else
259 break;
262 if (direction > 0) {
263 if ((wps_state.id3->elapsed + ff_rewind_count) >
264 wps_state.id3->length)
265 ff_rewind_count = wps_state.id3->length -
266 wps_state.id3->elapsed;
268 else {
269 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
270 ff_rewind_count = -wps_state.id3->elapsed;
273 FOR_NB_SCREENS(i)
274 gui_wps_refresh(&gui_wps[i],
275 (wps_state.wps_time_countup == false)?
276 ff_rewind_count:-ff_rewind_count,
277 WPS_REFRESH_PLAYER_PROGRESS |
278 WPS_REFRESH_DYNAMIC);
280 break;
282 case ACTION_WPS_STOPSEEK:
283 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
284 audio_ff_rewind(wps_state.id3->elapsed);
285 ff_rewind_count = 0;
286 wps_state.ff_rewind = false;
287 status_set_ffmode(0);
288 #if (CONFIG_CODEC != SWCODEC)
289 if (!wps_state.paused)
290 audio_resume();
291 #endif
292 #ifdef HAVE_LCD_CHARCELLS
293 gui_wps_display();
294 #endif
295 exit = true;
296 break;
298 default:
299 if(default_event_handler(button) == SYS_USB_CONNECTED) {
300 status_set_ffmode(0);
301 usb = true;
302 exit = true;
304 break;
306 if (!exit)
307 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
309 return usb;
312 bool gui_wps_display(void)
314 int i;
315 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
317 global_status.resume_index = -1;
318 splash(HZ, ID2P(LANG_END_PLAYLIST));
319 return true;
321 else
323 FOR_NB_SCREENS(i)
325 /* Update the values in the first (default) viewport - in case the user
326 has modified the statusbar or colour settings */
327 #ifdef HAVE_LCD_BITMAP
328 #if LCD_DEPTH > 1
329 if (gui_wps[i].display->depth > 1)
331 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
332 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
334 #endif
335 #endif
336 gui_wps[i].display->clear_display();
337 if (!gui_wps[i].data->wps_loaded) {
338 if ( !gui_wps[i].data->num_tokens ) {
339 /* set the default wps for the main-screen */
340 if(i == 0)
342 #ifdef HAVE_LCD_BITMAP
343 #if LCD_DEPTH > 1
344 unload_wps_backdrop();
345 #endif
346 wps_data_load(gui_wps[i].data,
347 gui_wps[i].display,
348 "%s%?it<%?in<%in. |>%it|%fn>\n"
349 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
350 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
351 "\n"
352 "%al%pc/%pt%ar[%pp:%pe]\n"
353 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
354 "%pb\n"
355 "%pm\n", false);
356 #else
357 wps_data_load(gui_wps[i].data,
358 gui_wps[i].display,
359 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
360 "%pc%?ps<*|/>%pt\n", false);
361 #endif
363 #if NB_SCREENS == 2
364 /* set the default wps for the remote-screen */
365 else if(i == 1)
367 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
368 unload_remote_wps_backdrop();
369 #endif
370 wps_data_load(gui_wps[i].data,
371 gui_wps[i].display,
372 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
373 "%s%?it<%?in<%in. |>%it|%fn>\n"
374 "%al%pc/%pt%ar[%pp:%pe]\n"
375 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
376 "%pb\n", false);
378 #endif
383 yield();
384 FOR_NB_SCREENS(i)
386 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
387 #ifdef HAVE_LCD_BITMAP
388 /* temporary work around so the statusbar doesnt disappear when the WPS
389 * is first entered. This should be removed when the
390 * WPS-statusbar handling is fixed up a bit more */
391 bool draw = global_settings.statusbar;
392 if (gui_wps[i].data->wps_sb_tag)
393 draw = gui_wps[i].data->show_sb_on_wps;
394 if (draw)
395 gui_statusbar_draw(&statusbars.statusbars[i], true);
396 #endif
398 return false;
401 bool update(struct gui_wps *gwps)
403 bool track_changed = audio_has_changed_track();
404 bool retcode = false;
406 gwps->state->nid3 = audio_next_track();
407 if (track_changed)
409 gwps->display->stop_scroll();
410 gwps->state->id3 = audio_current_track();
412 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
413 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
415 /* the current cuesheet isn't the right one any more */
416 /* We need to parse the new cuesheet */
418 char cuepath[MAX_PATH];
420 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
421 parse_cuesheet(cuepath, curr_cue))
423 gwps->state->id3->cuesheet_type = 1;
424 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
427 cue_spoof_id3(curr_cue, gwps->state->id3);
430 if (gui_wps_display())
431 retcode = true;
432 else{
433 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
437 if (gwps->state->id3)
439 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
440 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
441 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
442 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
444 /* We've changed tracks within the cuesheet :
445 we need to update the ID3 info and refresh the WPS */
447 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
448 cue_spoof_id3(curr_cue, gwps->state->id3);
450 gwps->display->stop_scroll();
451 if (gui_wps_display())
452 retcode = true;
453 else
454 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
456 else
457 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
460 return retcode;
464 void display_keylock_text(bool locked)
466 int i;
467 FOR_NB_SCREENS(i)
468 gui_wps[i].display->stop_scroll();
470 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
473 #ifdef HAVE_LCD_BITMAP
475 static void draw_progressbar(struct gui_wps *gwps,
476 struct wps_viewport *wps_vp)
478 struct screen *display = gwps->display;
479 struct wps_state *state = gwps->state;
480 struct progressbar *pb = wps_vp->pb;
481 int y = pb->y;
483 if (y < 0)
485 int line_height = font_get(wps_vp->vp.font)->height;
486 /* center the pb in the line, but only if the line is higher than the pb */
487 int center = (line_height-pb->height)/2;
488 /* if Y was not set calculate by font height,Y is -line_number-1 */
489 y = (-y -1)*line_height + (0 > center ? 0 : center);
492 if (pb->have_bitmap_pb)
493 gui_bitmap_scrollbar_draw(display, pb->bm,
494 pb->x, y, pb->width, pb->bm.height,
495 state->id3->length ? state->id3->length : 1, 0,
496 state->id3->length ? state->id3->elapsed
497 + state->ff_rewind_count : 0,
498 HORIZONTAL);
499 else
500 gui_scrollbar_draw(display, pb->x, y, pb->width, pb->height,
501 state->id3->length ? state->id3->length : 1, 0,
502 state->id3->length ? state->id3->elapsed
503 + state->ff_rewind_count : 0,
504 HORIZONTAL);
505 #ifdef AB_REPEAT_ENABLE
506 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
507 ab_draw_markers(display, state->id3->length,
508 pb->x, pb->x + pb->width, y, pb->height);
509 #endif
511 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
512 cue_draw_markers(display, state->id3->length,
513 pb->x, pb->x + pb->width, y+1, pb->height-2);
516 /* clears the area where the image was shown */
517 static void clear_image_pos(struct gui_wps *gwps, int n)
519 if(!gwps)
520 return;
521 struct wps_data *data = gwps->data;
522 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
523 gwps->display->fillrect(data->img[n].x, data->img[n].y,
524 data->img[n].bm.width, data->img[n].subimage_height);
525 gwps->display->set_drawmode(DRMODE_SOLID);
528 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
530 struct screen *display = gwps->display;
531 struct wps_data *data = gwps->data;
532 if(data->img[n].always_display)
533 display->set_drawmode(DRMODE_FG);
534 else
535 display->set_drawmode(DRMODE_SOLID);
537 #if LCD_DEPTH > 1
538 if(data->img[n].bm.format == FORMAT_MONO) {
539 #endif
540 display->mono_bitmap_part(data->img[n].bm.data,
541 0, data->img[n].subimage_height * subimage,
542 data->img[n].bm.width, data->img[n].x,
543 data->img[n].y, data->img[n].bm.width,
544 data->img[n].subimage_height);
545 #if LCD_DEPTH > 1
546 } else {
547 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
548 0, data->img[n].subimage_height * subimage,
549 data->img[n].bm.width, data->img[n].x,
550 data->img[n].y, data->img[n].bm.width,
551 data->img[n].subimage_height);
553 #endif
556 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
558 if(!gwps || !gwps->data || !gwps->display)
559 return;
561 int n;
562 struct wps_data *data = gwps->data;
563 struct screen *display = gwps->display;
565 for (n = 0; n < MAX_IMAGES; n++)
567 if (data->img[n].loaded)
569 if (data->img[n].display >= 0)
571 wps_draw_image(gwps, n, data->img[n].display);
572 } else if (data->img[n].always_display && data->img[n].vp == vp)
574 wps_draw_image(gwps, n, 0);
578 display->set_drawmode(DRMODE_SOLID);
581 #else /* HAVE_LCD_CHARCELL */
583 static bool draw_player_progress(struct gui_wps *gwps)
585 struct wps_state *state = gwps->state;
586 struct screen *display = gwps->display;
587 unsigned char progress_pattern[7];
588 int pos = 0;
589 int i;
591 if (!state->id3)
592 return false;
594 if (state->id3->length)
595 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
596 / state->id3->length;
598 for (i = 0; i < 7; i++, pos -= 5)
600 if (pos <= 0)
601 progress_pattern[i] = 0x1fu;
602 else if (pos >= 5)
603 progress_pattern[i] = 0x00u;
604 else
605 progress_pattern[i] = 0x1fu >> pos;
608 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
609 return true;
612 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
614 static const unsigned char numbers[10][4] = {
615 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
616 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
617 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
618 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
619 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
620 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
621 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
622 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
623 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
624 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
627 struct wps_state *state = gwps->state;
628 struct screen *display = gwps->display;
629 struct wps_data *data = gwps->data;
630 unsigned char progress_pattern[7];
631 char timestr[10];
632 int time;
633 int time_idx = 0;
634 int pos = 0;
635 int pat_idx = 1;
636 int digit, i, j;
637 bool softchar;
639 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
640 return;
642 time = state->id3->elapsed + state->ff_rewind_count;
643 if (state->id3->length)
644 pos = 55 * time / state->id3->length;
646 memset(timestr, 0, sizeof(timestr));
647 format_time(timestr, sizeof(timestr)-2, time);
648 timestr[strlen(timestr)] = ':'; /* always safe */
650 for (i = 0; i < 11; i++, pos -= 5)
652 softchar = false;
653 memset(progress_pattern, 0, sizeof(progress_pattern));
655 if ((digit = timestr[time_idx]))
657 softchar = true;
658 digit -= '0';
660 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
662 memcpy(progress_pattern, numbers[digit], 4);
663 time_idx += 2;
665 else /* tens, shifted right */
667 for (j = 0; j < 4; j++)
668 progress_pattern[j] = numbers[digit][j] >> 1;
670 if (time_idx > 0) /* not the first group, add colon in front */
672 progress_pattern[1] |= 0x10u;
673 progress_pattern[3] |= 0x10u;
675 time_idx++;
678 if (pos >= 5)
679 progress_pattern[5] = progress_pattern[6] = 0x1fu;
682 if (pos > 0 && pos < 5)
684 softchar = true;
685 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
688 if (softchar && pat_idx < 8)
690 display->define_pattern(data->wps_progress_pat[pat_idx],
691 progress_pattern);
692 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
693 pat_idx++;
695 else if (pos <= 0)
696 buf = utf8encode(' ', buf);
697 else
698 buf = utf8encode(0xe115, buf); /* 2/7 _ */
700 *buf = '\0';
703 #endif /* HAVE_LCD_CHARCELL */
705 static char* get_codectype(const struct mp3entry* id3)
707 if (id3->codectype < AFMT_NUM_CODECS) {
708 return (char*)audio_formats[id3->codectype].label;
709 } else {
710 return NULL;
714 /* Extract a part from a path.
716 * buf - buffer extract part to.
717 * buf_size - size of buffer.
718 * path - path to extract from.
719 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
720 * parent of parent, etc.
722 * Returns buf if the desired level was found, NULL otherwise.
724 static char* get_dir(char* buf, int buf_size, const char* path, int level)
726 const char* sep;
727 const char* last_sep;
728 int len;
730 sep = path + strlen(path);
731 last_sep = sep;
733 while (sep > path)
735 if ('/' == *(--sep))
737 if (!level)
738 break;
740 level--;
741 last_sep = sep - 1;
745 if (level || (last_sep <= sep))
746 return NULL;
748 len = MIN(last_sep - sep, buf_size - 1);
749 strncpy(buf, sep + 1, len);
750 buf[len] = 0;
751 return buf;
754 /* Return the tag found at index i and write its value in buf.
755 The return value is buf if the tag had a value, or NULL if not.
757 intval is used with conditionals/enums: when this function is called,
758 intval should contain the number of options in the conditional/enum.
759 When this function returns, intval is -1 if the tag is non numeric or,
760 if the tag is numeric, *intval is the enum case we want to go to (between 1
761 and the original value of *intval, inclusive).
762 When not treating a conditional/enum, intval should be NULL.
764 static const char *get_token_value(struct gui_wps *gwps,
765 struct wps_token *token,
766 char *buf, int buf_size,
767 int *intval)
769 if (!gwps)
770 return NULL;
772 struct wps_data *data = gwps->data;
773 struct wps_state *state = gwps->state;
775 if (!data || !state)
776 return NULL;
778 struct mp3entry *id3;
780 if (token->next)
781 id3 = state->nid3;
782 else
783 id3 = state->id3;
785 if (!id3)
786 return NULL;
788 #if CONFIG_RTC
789 struct tm* tm = NULL;
791 /* if the token is an RTC one, update the time
792 and do the necessary checks */
793 if (token->type >= WPS_TOKENS_RTC_BEGIN
794 && token->type <= WPS_TOKENS_RTC_END)
796 tm = get_time();
798 if (!valid_time(tm))
799 return NULL;
801 #endif
803 int limit = 1;
804 if (intval)
806 limit = *intval;
807 *intval = -1;
810 switch (token->type)
812 case WPS_TOKEN_CHARACTER:
813 return &(token->value.c);
815 case WPS_TOKEN_STRING:
816 return data->strings[token->value.i];
818 case WPS_TOKEN_TRACK_TIME_ELAPSED:
819 format_time(buf, buf_size,
820 id3->elapsed + state->ff_rewind_count);
821 return buf;
823 case WPS_TOKEN_TRACK_TIME_REMAINING:
824 format_time(buf, buf_size,
825 id3->length - id3->elapsed -
826 state->ff_rewind_count);
827 return buf;
829 case WPS_TOKEN_TRACK_LENGTH:
830 format_time(buf, buf_size, id3->length);
831 return buf;
833 case WPS_TOKEN_PLAYLIST_ENTRIES:
834 snprintf(buf, buf_size, "%d", playlist_amount());
835 return buf;
837 case WPS_TOKEN_PLAYLIST_NAME:
838 return playlist_name(NULL, buf, buf_size);
840 case WPS_TOKEN_PLAYLIST_POSITION:
841 snprintf(buf, buf_size, "%d", playlist_get_display_index());
842 return buf;
844 case WPS_TOKEN_PLAYLIST_SHUFFLE:
845 if ( global_settings.playlist_shuffle )
846 return "s";
847 else
848 return NULL;
849 break;
851 case WPS_TOKEN_VOLUME:
852 snprintf(buf, buf_size, "%d", global_settings.volume);
853 if (intval)
855 if (global_settings.volume == sound_min(SOUND_VOLUME))
857 *intval = 1;
859 else if (global_settings.volume == 0)
861 *intval = limit - 1;
863 else if (global_settings.volume > 0)
865 *intval = limit;
867 else
869 *intval = (limit - 3) * (global_settings.volume
870 - sound_min(SOUND_VOLUME) - 1)
871 / (-1 - sound_min(SOUND_VOLUME)) + 2;
874 return buf;
876 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
877 if (id3->length <= 0)
878 return NULL;
880 if (intval)
882 *intval = limit * (id3->elapsed + state->ff_rewind_count)
883 / id3->length + 1;
885 snprintf(buf, buf_size, "%d",
886 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
887 return buf;
889 case WPS_TOKEN_METADATA_ARTIST:
890 return id3->artist;
892 case WPS_TOKEN_METADATA_COMPOSER:
893 return id3->composer;
895 case WPS_TOKEN_METADATA_ALBUM:
896 return id3->album;
898 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
899 return id3->albumartist;
901 case WPS_TOKEN_METADATA_GROUPING:
902 return id3->grouping;
904 case WPS_TOKEN_METADATA_GENRE:
905 return id3->genre_string;
907 case WPS_TOKEN_METADATA_DISC_NUMBER:
908 if (id3->disc_string)
909 return id3->disc_string;
910 if (id3->discnum) {
911 snprintf(buf, buf_size, "%d", id3->discnum);
912 return buf;
914 return NULL;
916 case WPS_TOKEN_METADATA_TRACK_NUMBER:
917 if (id3->track_string)
918 return id3->track_string;
920 if (id3->tracknum) {
921 snprintf(buf, buf_size, "%d", id3->tracknum);
922 return buf;
924 return NULL;
926 case WPS_TOKEN_METADATA_TRACK_TITLE:
927 return id3->title;
929 case WPS_TOKEN_METADATA_VERSION:
930 switch (id3->id3version)
932 case ID3_VER_1_0:
933 return "1";
935 case ID3_VER_1_1:
936 return "1.1";
938 case ID3_VER_2_2:
939 return "2.2";
941 case ID3_VER_2_3:
942 return "2.3";
944 case ID3_VER_2_4:
945 return "2.4";
947 default:
948 return NULL;
951 case WPS_TOKEN_METADATA_YEAR:
952 if( id3->year_string )
953 return id3->year_string;
955 if (id3->year) {
956 snprintf(buf, buf_size, "%d", id3->year);
957 return buf;
959 return NULL;
961 case WPS_TOKEN_METADATA_COMMENT:
962 return id3->comment;
964 #ifdef HAVE_ALBUMART
965 case WPS_TOKEN_ALBUMART_DISPLAY:
966 draw_album_art(gwps, audio_current_aa_hid(), false);
967 return NULL;
969 case WPS_TOKEN_ALBUMART_FOUND:
970 if (audio_current_aa_hid() >= 0) {
971 return "C";
973 return NULL;
974 #endif
976 case WPS_TOKEN_FILE_BITRATE:
977 if(id3->bitrate)
978 snprintf(buf, buf_size, "%d", id3->bitrate);
979 else
980 return "?";
981 return buf;
983 case WPS_TOKEN_FILE_CODEC:
984 if (intval)
986 if(id3->codectype == AFMT_UNKNOWN)
987 *intval = AFMT_NUM_CODECS;
988 else
989 *intval = id3->codectype;
991 return get_codectype(id3);
993 case WPS_TOKEN_FILE_FREQUENCY:
994 snprintf(buf, buf_size, "%ld", id3->frequency);
995 return buf;
997 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
998 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
999 if ((id3->frequency % 1000) < 100)
1000 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1001 else
1002 snprintf(buf, buf_size, "%ld.%d",
1003 id3->frequency / 1000,
1004 (id3->frequency % 1000) / 100);
1005 return buf;
1007 case WPS_TOKEN_FILE_NAME:
1008 if (get_dir(buf, buf_size, id3->path, 0)) {
1009 /* Remove extension */
1010 char* sep = strrchr(buf, '.');
1011 if (NULL != sep) {
1012 *sep = 0;
1014 return buf;
1016 else {
1017 return NULL;
1020 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1021 return get_dir(buf, buf_size, id3->path, 0);
1023 case WPS_TOKEN_FILE_PATH:
1024 return id3->path;
1026 case WPS_TOKEN_FILE_SIZE:
1027 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1028 return buf;
1030 case WPS_TOKEN_FILE_VBR:
1031 return id3->vbr ? "(avg)" : NULL;
1033 case WPS_TOKEN_FILE_DIRECTORY:
1034 return get_dir(buf, buf_size, id3->path, token->value.i);
1036 case WPS_TOKEN_BATTERY_PERCENT:
1038 int l = battery_level();
1040 if (intval)
1042 limit = MAX(limit, 2);
1043 if (l > -1) {
1044 /* First enum is used for "unknown level". */
1045 *intval = (limit - 1) * l / 100 + 2;
1046 } else {
1047 *intval = 1;
1051 if (l > -1) {
1052 snprintf(buf, buf_size, "%d", l);
1053 return buf;
1054 } else {
1055 return "?";
1059 case WPS_TOKEN_BATTERY_VOLTS:
1061 unsigned int v = battery_voltage();
1062 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1063 return buf;
1066 case WPS_TOKEN_BATTERY_TIME:
1068 int t = battery_time();
1069 if (t >= 0)
1070 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1071 else
1072 return "?h ?m";
1073 return buf;
1076 #if CONFIG_CHARGING
1077 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1079 if(charger_input_state==CHARGER)
1080 return "p";
1081 else
1082 return NULL;
1084 #endif
1085 #if CONFIG_CHARGING >= CHARGING_MONITOR
1086 case WPS_TOKEN_BATTERY_CHARGING:
1088 if (charge_state == CHARGING || charge_state == TOPOFF) {
1089 return "c";
1090 } else {
1091 return NULL;
1094 #endif
1095 case WPS_TOKEN_BATTERY_SLEEPTIME:
1097 if (get_sleep_timer() == 0)
1098 return NULL;
1099 else
1101 format_time(buf, buf_size, get_sleep_timer() * 1000);
1102 return buf;
1106 case WPS_TOKEN_PLAYBACK_STATUS:
1108 int status = audio_status();
1109 int mode = 1;
1110 if (status == AUDIO_STATUS_PLAY)
1111 mode = 2;
1112 if (wps_fading_out ||
1113 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1114 mode = 3;
1115 if (status_get_ffmode() == STATUS_FASTFORWARD)
1116 mode = 4;
1117 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1118 mode = 5;
1120 if (intval) {
1121 *intval = mode;
1124 snprintf(buf, buf_size, "%d", mode-1);
1125 return buf;
1128 case WPS_TOKEN_REPEAT_MODE:
1129 if (intval)
1130 *intval = global_settings.repeat_mode + 1;
1131 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1132 return buf;
1133 #if CONFIG_RTC
1134 case WPS_TOKEN_RTC_12HOUR_CFG:
1135 if (intval)
1136 *intval = global_settings.timeformat + 1;
1137 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1138 return buf;
1140 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1141 /* d: day of month (01..31) */
1142 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1143 return buf;
1145 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1146 /* e: day of month, blank padded ( 1..31) */
1147 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1148 return buf;
1150 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1151 /* H: hour (00..23) */
1152 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1153 return buf;
1155 case WPS_TOKEN_RTC_HOUR_24:
1156 /* k: hour ( 0..23) */
1157 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1158 return buf;
1160 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1161 /* I: hour (01..12) */
1162 snprintf(buf, buf_size, "%02d",
1163 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1164 return buf;
1166 case WPS_TOKEN_RTC_HOUR_12:
1167 /* l: hour ( 1..12) */
1168 snprintf(buf, buf_size, "%2d",
1169 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1170 return buf;
1172 case WPS_TOKEN_RTC_MONTH:
1173 /* m: month (01..12) */
1174 if (intval)
1175 *intval = tm->tm_mon + 1;
1176 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1177 return buf;
1179 case WPS_TOKEN_RTC_MINUTE:
1180 /* M: minute (00..59) */
1181 snprintf(buf, buf_size, "%02d", tm->tm_min);
1182 return buf;
1184 case WPS_TOKEN_RTC_SECOND:
1185 /* S: second (00..59) */
1186 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1187 return buf;
1189 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1190 /* y: last two digits of year (00..99) */
1191 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1192 return buf;
1194 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1195 /* Y: year (1970...) */
1196 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1197 return buf;
1199 case WPS_TOKEN_RTC_AM_PM_UPPER:
1200 /* p: upper case AM or PM indicator */
1201 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1203 case WPS_TOKEN_RTC_AM_PM_LOWER:
1204 /* P: lower case am or pm indicator */
1205 return tm->tm_hour/12 == 0 ? "am" : "pm";
1207 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1208 /* a: abbreviated weekday name (Sun..Sat) */
1209 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1211 case WPS_TOKEN_RTC_MONTH_NAME:
1212 /* b: abbreviated month name (Jan..Dec) */
1213 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1215 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1216 /* u: day of week (1..7); 1 is Monday */
1217 if (intval)
1218 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1219 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1220 return buf;
1222 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1223 /* w: day of week (0..6); 0 is Sunday */
1224 if (intval)
1225 *intval = tm->tm_wday + 1;
1226 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1227 return buf;
1228 #else
1229 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1230 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1231 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1232 case WPS_TOKEN_RTC_HOUR_24:
1233 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1234 case WPS_TOKEN_RTC_HOUR_12:
1235 case WPS_TOKEN_RTC_MONTH:
1236 case WPS_TOKEN_RTC_MINUTE:
1237 case WPS_TOKEN_RTC_SECOND:
1238 case WPS_TOKEN_RTC_AM_PM_UPPER:
1239 case WPS_TOKEN_RTC_AM_PM_LOWER:
1240 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1241 return "--";
1242 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1243 return "----";
1244 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1245 case WPS_TOKEN_RTC_MONTH_NAME:
1246 return "---";
1247 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1248 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1249 return "-";
1250 #endif
1252 #ifdef HAVE_LCD_CHARCELLS
1253 case WPS_TOKEN_PROGRESSBAR:
1255 char *end = utf8encode(data->wps_progress_pat[0], buf);
1256 *end = '\0';
1257 return buf;
1260 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1261 if(is_new_player())
1263 /* we need 11 characters (full line) for
1264 progress-bar */
1265 strncpy(buf, " ", buf_size);
1267 else
1269 /* Tell the user if we have an OldPlayer */
1270 strncpy(buf, " <Old LCD> ", buf_size);
1272 return buf;
1273 #endif
1275 #ifdef HAVE_TAGCACHE
1276 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1277 if (intval) {
1278 *intval = id3->playcount + 1;
1280 snprintf(buf, buf_size, "%ld", id3->playcount);
1281 return buf;
1283 case WPS_TOKEN_DATABASE_RATING:
1284 if (intval) {
1285 *intval = id3->rating + 1;
1287 snprintf(buf, buf_size, "%d", id3->rating);
1288 return buf;
1290 case WPS_TOKEN_DATABASE_AUTOSCORE:
1291 if (intval)
1292 *intval = id3->score + 1;
1294 snprintf(buf, buf_size, "%d", id3->score);
1295 return buf;
1296 #endif
1298 #if (CONFIG_CODEC == SWCODEC)
1299 case WPS_TOKEN_CROSSFADE:
1300 if (intval)
1301 *intval = global_settings.crossfade + 1;
1302 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1303 return buf;
1305 case WPS_TOKEN_REPLAYGAIN:
1307 int val;
1309 if (global_settings.replaygain == 0)
1310 val = 1; /* off */
1311 else
1313 int type =
1314 get_replaygain_mode(id3->track_gain_string != NULL,
1315 id3->album_gain_string != NULL);
1316 if (type < 0)
1317 val = 6; /* no tag */
1318 else
1319 val = type + 2;
1321 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1322 val += 2;
1325 if (intval)
1326 *intval = val;
1328 switch (val)
1330 case 1:
1331 case 6:
1332 return "+0.00 dB";
1333 break;
1334 case 2:
1335 case 4:
1336 strncpy(buf, id3->track_gain_string, buf_size);
1337 break;
1338 case 3:
1339 case 5:
1340 strncpy(buf, id3->album_gain_string, buf_size);
1341 break;
1343 return buf;
1345 #endif /* (CONFIG_CODEC == SWCODEC) */
1347 #if (CONFIG_CODEC != MAS3507D)
1348 case WPS_TOKEN_SOUND_PITCH:
1350 int val = sound_get_pitch();
1351 snprintf(buf, buf_size, "%d.%d",
1352 val / 10, val % 10);
1353 return buf;
1355 #endif
1357 case WPS_TOKEN_MAIN_HOLD:
1358 #ifdef HAS_BUTTON_HOLD
1359 if (button_hold())
1360 #else
1361 if (is_keys_locked())
1362 #endif /*hold switch or softlock*/
1363 return "h";
1364 else
1365 return NULL;
1367 #ifdef HAS_REMOTE_BUTTON_HOLD
1368 case WPS_TOKEN_REMOTE_HOLD:
1369 if (remote_button_hold())
1370 return "r";
1371 else
1372 return NULL;
1373 #endif
1375 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1376 case WPS_TOKEN_VLED_HDD:
1377 if(led_read(HZ/2))
1378 return "h";
1379 else
1380 return NULL;
1381 #endif
1382 case WPS_TOKEN_BUTTON_VOLUME:
1383 if (data->button_time_volume &&
1384 TIME_BEFORE(current_tick, data->button_time_volume +
1385 token->value.i * TIMEOUT_UNIT))
1386 return "v";
1387 return NULL;
1389 case WPS_TOKEN_SETTING:
1391 if (intval)
1393 /* Handle contionals */
1394 const struct settings_list *s = settings+token->value.i;
1395 switch (s->flags&F_T_MASK)
1397 case F_T_INT:
1398 case F_T_UINT:
1399 if (s->flags&F_RGB)
1400 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1401 /* shouldn't overflow since colors are stored
1402 * on 16 bits ...
1403 * but this is pretty useless anyway */
1404 *intval = *(int*)s->setting + 1;
1405 else if (s->cfg_vals == NULL)
1406 /* %?St|name|<1st choice|2nd choice|...> */
1407 *intval = (*(int*)s->setting-s->int_setting->min)
1408 /s->int_setting->step + 1;
1409 else
1410 /* %?St|name|<1st choice|2nd choice|...> */
1411 /* Not sure about this one. cfg_name/vals are
1412 * indexed from 0 right? */
1413 *intval = *(int*)s->setting + 1;
1414 break;
1415 case F_T_BOOL:
1416 /* %?St|name|<if true|if false> */
1417 *intval = *(bool*)s->setting?1:2;
1418 break;
1419 case F_T_CHARPTR:
1420 /* %?St|name|<if non empty string|if empty>
1421 * The string's emptyness discards the setting's
1422 * prefix and suffix */
1423 *intval = ((char*)s->setting)[0]?1:2;
1424 break;
1425 default:
1426 /* This shouldn't happen ... but you never know */
1427 *intval = -1;
1428 break;
1431 cfg_to_string(token->value.i,buf,buf_size);
1432 return buf;
1435 default:
1436 return NULL;
1440 /* Return the index to the end token for the conditional token at index.
1441 The conditional token can be either a start token or a separator
1442 (i.e. option) token.
1444 static int find_conditional_end(struct wps_data *data, int index)
1446 int ret = index;
1447 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1448 ret = data->tokens[ret].value.i;
1450 /* ret now is the index to the end token for the conditional. */
1451 return ret;
1454 /* Evaluate the conditional that is at *token_index and return whether a skip
1455 has ocurred. *token_index is updated with the new position.
1457 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1459 if (!gwps)
1460 return false;
1462 struct wps_data *data = gwps->data;
1464 int i, cond_end;
1465 int cond_index = *token_index;
1466 char result[128];
1467 const char *value;
1468 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1469 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1471 /* treat ?xx<true> constructs as if they had 2 options. */
1472 if (num_options < 2)
1473 num_options = 2;
1475 int intval = num_options;
1476 /* get_token_value needs to know the number of options in the enum */
1477 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1478 result, sizeof(result), &intval);
1480 /* intval is now the number of the enum option we want to read,
1481 starting from 1. If intval is -1, we check if value is empty. */
1482 if (intval == -1)
1483 intval = (value && *value) ? 1 : num_options;
1484 else if (intval > num_options || intval < 1)
1485 intval = num_options;
1487 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1489 /* skip to the appropriate enum case */
1490 int next = cond_index + 2;
1491 for (i = 1; i < intval; i++)
1493 next = data->tokens[next].value.i;
1495 *token_index = next;
1497 if (prev_val == intval)
1499 /* Same conditional case as previously. Return without clearing the
1500 pictures */
1501 return false;
1504 cond_end = find_conditional_end(data, cond_index + 2);
1505 for (i = cond_index + 3; i < cond_end; i++)
1507 #ifdef HAVE_LCD_BITMAP
1508 /* clear all pictures in the conditional and nested ones */
1509 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1510 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1511 #endif
1512 #ifdef HAVE_ALBUMART
1513 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1514 draw_album_art(gwps, audio_current_aa_hid(), true);
1515 #endif
1518 return true;
1521 /* Read a (sub)line to the given alignment format buffer.
1522 linebuf is the buffer where the data is actually stored.
1523 align is the alignment format that'll be used to display the text.
1524 The return value indicates whether the line needs to be updated.
1526 static bool get_line(struct gui_wps *gwps,
1527 int line, int subline,
1528 struct align_pos *align,
1529 char *linebuf,
1530 int linebuf_size)
1532 struct wps_data *data = gwps->data;
1534 char temp_buf[128];
1535 char *buf = linebuf; /* will always point to the writing position */
1536 char *linebuf_end = linebuf + linebuf_size - 1;
1537 int i, last_token_idx;
1538 bool update = false;
1540 /* alignment-related variables */
1541 int cur_align;
1542 char* cur_align_start;
1543 cur_align_start = buf;
1544 cur_align = WPS_ALIGN_LEFT;
1545 align->left = NULL;
1546 align->center = NULL;
1547 align->right = NULL;
1549 /* Process all tokens of the desired subline */
1550 last_token_idx = wps_last_token_index(data, line, subline);
1551 for (i = wps_first_token_index(data, line, subline);
1552 i <= last_token_idx; i++)
1554 switch(data->tokens[i].type)
1556 case WPS_TOKEN_CONDITIONAL:
1557 /* place ourselves in the right conditional case */
1558 update |= evaluate_conditional(gwps, &i);
1559 break;
1561 case WPS_TOKEN_CONDITIONAL_OPTION:
1562 /* we've finished in the curent conditional case,
1563 skip to the end of the conditional structure */
1564 i = find_conditional_end(data, i);
1565 break;
1567 #ifdef HAVE_LCD_BITMAP
1568 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1570 struct gui_img *img = data->img;
1571 int n = data->tokens[i].value.i & 0xFF;
1572 int subimage = data->tokens[i].value.i >> 8;
1574 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1575 img[n].display = subimage;
1576 break;
1578 #endif
1580 case WPS_TOKEN_ALIGN_LEFT:
1581 case WPS_TOKEN_ALIGN_CENTER:
1582 case WPS_TOKEN_ALIGN_RIGHT:
1583 /* remember where the current aligned text started */
1584 switch (cur_align)
1586 case WPS_ALIGN_LEFT:
1587 align->left = cur_align_start;
1588 break;
1590 case WPS_ALIGN_CENTER:
1591 align->center = cur_align_start;
1592 break;
1594 case WPS_ALIGN_RIGHT:
1595 align->right = cur_align_start;
1596 break;
1598 /* start a new alignment */
1599 switch (data->tokens[i].type)
1601 case WPS_TOKEN_ALIGN_LEFT:
1602 cur_align = WPS_ALIGN_LEFT;
1603 break;
1604 case WPS_TOKEN_ALIGN_CENTER:
1605 cur_align = WPS_ALIGN_CENTER;
1606 break;
1607 case WPS_TOKEN_ALIGN_RIGHT:
1608 cur_align = WPS_ALIGN_RIGHT;
1609 break;
1610 default:
1611 break;
1613 *buf++ = 0;
1614 cur_align_start = buf;
1615 break;
1616 case WPS_VIEWPORT_ENABLE:
1618 char label = data->tokens[i].value.i;
1619 int j;
1620 char temp = VP_DRAW_HIDEABLE;
1621 for(j=0;j<data->num_viewports;j++)
1623 temp = VP_DRAW_HIDEABLE;
1624 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1625 (data->viewports[j].label == label))
1627 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1628 temp |= VP_DRAW_WASHIDDEN;
1629 data->viewports[j].hidden_flags = temp;
1633 break;
1634 default:
1636 /* get the value of the tag and copy it to the buffer */
1637 const char *value = get_token_value(gwps, &data->tokens[i],
1638 temp_buf, sizeof(temp_buf), NULL);
1639 if (value)
1641 update = true;
1642 while (*value && (buf < linebuf_end))
1643 *buf++ = *value++;
1645 break;
1650 /* close the current alignment */
1651 switch (cur_align)
1653 case WPS_ALIGN_LEFT:
1654 align->left = cur_align_start;
1655 break;
1657 case WPS_ALIGN_CENTER:
1658 align->center = cur_align_start;
1659 break;
1661 case WPS_ALIGN_RIGHT:
1662 align->right = cur_align_start;
1663 break;
1666 return update;
1669 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1671 struct wps_data *data = gwps->data;
1672 int i;
1673 int subline_idx = wps_subline_index(data, line, subline);
1674 int last_token_idx = wps_last_token_index(data, line, subline);
1676 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1678 for (i = wps_first_token_index(data, line, subline);
1679 i <= last_token_idx; i++)
1681 switch(data->tokens[i].type)
1683 case WPS_TOKEN_CONDITIONAL:
1684 /* place ourselves in the right conditional case */
1685 evaluate_conditional(gwps, &i);
1686 break;
1688 case WPS_TOKEN_CONDITIONAL_OPTION:
1689 /* we've finished in the curent conditional case,
1690 skip to the end of the conditional structure */
1691 i = find_conditional_end(data, i);
1692 break;
1694 case WPS_TOKEN_SUBLINE_TIMEOUT:
1695 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1696 break;
1698 default:
1699 break;
1704 /* Calculates which subline should be displayed for the specified line
1705 Returns true iff the subline must be refreshed */
1706 static bool update_curr_subline(struct gui_wps *gwps, int line)
1708 struct wps_data *data = gwps->data;
1710 int search, search_start, num_sublines;
1711 bool reset_subline;
1712 bool new_subline_refresh;
1713 bool only_one_subline;
1715 num_sublines = data->lines[line].num_sublines;
1716 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1717 new_subline_refresh = false;
1718 only_one_subline = false;
1720 /* if time to advance to next sub-line */
1721 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1722 reset_subline)
1724 /* search all sublines until the next subline with time > 0
1725 is found or we get back to the subline we started with */
1726 if (reset_subline)
1727 search_start = 0;
1728 else
1729 search_start = data->lines[line].curr_subline;
1731 for (search = 0; search < num_sublines; search++)
1733 data->lines[line].curr_subline++;
1735 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1736 if (data->lines[line].curr_subline == num_sublines)
1738 if (data->lines[line].curr_subline == 1)
1739 only_one_subline = true;
1740 data->lines[line].curr_subline = 0;
1743 /* if back where we started after search or
1744 only one subline is defined on the line */
1745 if (((search > 0) &&
1746 (data->lines[line].curr_subline == search_start)) ||
1747 only_one_subline)
1749 /* no other subline with a time > 0 exists */
1750 data->lines[line].subline_expire_time = (reset_subline ?
1751 current_tick :
1752 data->lines[line].subline_expire_time) + 100 * HZ;
1753 break;
1755 else
1757 /* get initial time multiplier for this subline */
1758 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1760 int subline_idx = wps_subline_index(data, line,
1761 data->lines[line].curr_subline);
1763 /* only use this subline if subline time > 0 */
1764 if (data->sublines[subline_idx].time_mult > 0)
1766 new_subline_refresh = true;
1767 data->lines[line].subline_expire_time = (reset_subline ?
1768 current_tick : data->lines[line].subline_expire_time) +
1769 TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
1770 break;
1776 return new_subline_refresh;
1779 /* Display a line appropriately according to its alignment format.
1780 format_align contains the text, separated between left, center and right.
1781 line is the index of the line on the screen.
1782 scroll indicates whether the line is a scrolling one or not.
1784 static void write_line(struct screen *display,
1785 struct align_pos *format_align,
1786 int line,
1787 bool scroll)
1790 int left_width = 0, left_xpos;
1791 int center_width = 0, center_xpos;
1792 int right_width = 0, right_xpos;
1793 int ypos;
1794 int space_width;
1795 int string_height;
1796 int scroll_width;
1798 /* calculate different string sizes and positions */
1799 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1800 if (format_align->left != 0) {
1801 display->getstringsize((unsigned char *)format_align->left,
1802 &left_width, &string_height);
1805 if (format_align->right != 0) {
1806 display->getstringsize((unsigned char *)format_align->right,
1807 &right_width, &string_height);
1810 if (format_align->center != 0) {
1811 display->getstringsize((unsigned char *)format_align->center,
1812 &center_width, &string_height);
1815 left_xpos = 0;
1816 right_xpos = (display->getwidth() - right_width);
1817 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1819 scroll_width = display->getwidth() - left_xpos;
1821 /* Checks for overlapping strings.
1822 If needed the overlapping strings will be merged, separated by a
1823 space */
1825 /* CASE 1: left and centered string overlap */
1826 /* there is a left string, need to merge left and center */
1827 if ((left_width != 0 && center_width != 0) &&
1828 (left_xpos + left_width + space_width > center_xpos)) {
1829 /* replace the former separator '\0' of left and
1830 center string with a space */
1831 *(--format_align->center) = ' ';
1832 /* calculate the new width and position of the merged string */
1833 left_width = left_width + space_width + center_width;
1834 /* there is no centered string anymore */
1835 center_width = 0;
1837 /* there is no left string, move center to left */
1838 if ((left_width == 0 && center_width != 0) &&
1839 (left_xpos + left_width > center_xpos)) {
1840 /* move the center string to the left string */
1841 format_align->left = format_align->center;
1842 /* calculate the new width and position of the string */
1843 left_width = center_width;
1844 /* there is no centered string anymore */
1845 center_width = 0;
1848 /* CASE 2: centered and right string overlap */
1849 /* there is a right string, need to merge center and right */
1850 if ((center_width != 0 && right_width != 0) &&
1851 (center_xpos + center_width + space_width > right_xpos)) {
1852 /* replace the former separator '\0' of center and
1853 right string with a space */
1854 *(--format_align->right) = ' ';
1855 /* move the center string to the right after merge */
1856 format_align->right = format_align->center;
1857 /* calculate the new width and position of the merged string */
1858 right_width = center_width + space_width + right_width;
1859 right_xpos = (display->getwidth() - right_width);
1860 /* there is no centered string anymore */
1861 center_width = 0;
1863 /* there is no right string, move center to right */
1864 if ((center_width != 0 && right_width == 0) &&
1865 (center_xpos + center_width > right_xpos)) {
1866 /* move the center string to the right string */
1867 format_align->right = format_align->center;
1868 /* calculate the new width and position of the string */
1869 right_width = center_width;
1870 right_xpos = (display->getwidth() - right_width);
1871 /* there is no centered string anymore */
1872 center_width = 0;
1875 /* CASE 3: left and right overlap
1876 There is no center string anymore, either there never
1877 was one or it has been merged in case 1 or 2 */
1878 /* there is a left string, need to merge left and right */
1879 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1880 (left_xpos + left_width + space_width > right_xpos)) {
1881 /* replace the former separator '\0' of left and
1882 right string with a space */
1883 *(--format_align->right) = ' ';
1884 /* calculate the new width and position of the string */
1885 left_width = left_width + space_width + right_width;
1886 /* there is no right string anymore */
1887 right_width = 0;
1889 /* there is no left string, move right to left */
1890 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1891 (left_width > right_xpos)) {
1892 /* move the right string to the left string */
1893 format_align->left = format_align->right;
1894 /* calculate the new width and position of the string */
1895 left_width = right_width;
1896 /* there is no right string anymore */
1897 right_width = 0;
1900 ypos = (line * string_height);
1903 if (scroll && ((left_width > scroll_width) ||
1904 (center_width > scroll_width) ||
1905 (right_width > scroll_width)))
1907 display->puts_scroll(0, line,
1908 (unsigned char *)format_align->left);
1910 else
1912 #ifdef HAVE_LCD_BITMAP
1913 /* clear the line first */
1914 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1915 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1916 display->set_drawmode(DRMODE_SOLID);
1917 #endif
1919 /* Nasty hack: we output an empty scrolling string,
1920 which will reset the scroller for that line */
1921 display->puts_scroll(0, line, (unsigned char *)"");
1923 /* print aligned strings */
1924 if (left_width != 0)
1926 display->putsxy(left_xpos, ypos,
1927 (unsigned char *)format_align->left);
1929 if (center_width != 0)
1931 display->putsxy(center_xpos, ypos,
1932 (unsigned char *)format_align->center);
1934 if (right_width != 0)
1936 display->putsxy(right_xpos, ypos,
1937 (unsigned char *)format_align->right);
1942 /* Refresh the WPS according to refresh_mode. */
1943 bool gui_wps_refresh(struct gui_wps *gwps,
1944 int ffwd_offset,
1945 unsigned char refresh_mode)
1947 struct wps_data *data = gwps->data;
1948 struct screen *display = gwps->display;
1949 struct wps_state *state = gwps->state;
1951 if(!gwps || !data || !state || !display)
1952 return false;
1954 int v, line, i, subline_idx;
1955 unsigned char flags;
1956 char linebuf[MAX_PATH];
1957 unsigned char vp_refresh_mode;
1959 struct align_pos align;
1960 align.left = NULL;
1961 align.center = NULL;
1962 align.right = NULL;
1964 bool update_line, new_subline_refresh;
1966 #ifdef HAVE_LCD_BITMAP
1968 /* to find out wether the peak meter is enabled we
1969 assume it wasn't until we find a line that contains
1970 the peak meter. We can't use peak_meter_enabled itself
1971 because that would mean to turn off the meter thread
1972 temporarily. (That shouldn't matter unless yield
1973 or sleep is called but who knows...)
1975 bool enable_pm = false;
1977 #endif
1979 /* reset to first subline if refresh all flag is set */
1980 if (refresh_mode == WPS_REFRESH_ALL)
1982 display->set_viewport(&data->viewports[0].vp);
1983 display->clear_viewport();
1985 for (i = 0; i <= data->num_lines; i++)
1987 data->lines[i].curr_subline = SUBLINE_RESET;
1991 #ifdef HAVE_LCD_CHARCELLS
1992 for (i = 0; i < 8; i++)
1994 if (data->wps_progress_pat[i] == 0)
1995 data->wps_progress_pat[i] = display->get_locked_pattern();
1997 #endif
1999 if (!state->id3)
2001 display->stop_scroll();
2002 return false;
2005 state->ff_rewind_count = ffwd_offset;
2007 /* disable any viewports which are conditionally displayed */
2008 for (v = 0; v < data->num_viewports; v++)
2010 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
2012 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
2013 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2014 else
2015 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
2018 for (v = 0; v < data->num_viewports; v++)
2020 struct wps_viewport *wps_vp = &(data->viewports[v]);
2021 display->set_viewport(&wps_vp->vp);
2022 vp_refresh_mode = refresh_mode;
2024 #ifdef HAVE_LCD_BITMAP
2025 /* Set images to not to be displayed */
2026 for (i = 0; i < MAX_IMAGES; i++)
2028 data->img[i].display = -1;
2030 #endif
2031 /* dont redraw the viewport if its disabled */
2032 if ((wps_vp->hidden_flags&VP_DRAW_HIDDEN))
2034 if (!(wps_vp->hidden_flags&VP_DRAW_WASHIDDEN))
2035 display->scroll_stop(&wps_vp->vp);
2036 wps_vp->hidden_flags |= VP_DRAW_WASHIDDEN;
2037 continue;
2039 else if (((wps_vp->hidden_flags&
2040 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2041 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2043 vp_refresh_mode = WPS_REFRESH_ALL;
2044 wps_vp->hidden_flags = VP_DRAW_HIDEABLE;
2046 if (vp_refresh_mode == WPS_REFRESH_ALL)
2048 display->clear_viewport();
2051 for (line = wps_vp->first_line;
2052 line <= wps_vp->last_line; line++)
2054 memset(linebuf, 0, sizeof(linebuf));
2055 update_line = false;
2057 /* get current subline for the line */
2058 new_subline_refresh = update_curr_subline(gwps, line);
2060 subline_idx = wps_subline_index(data, line,
2061 data->lines[line].curr_subline);
2062 flags = data->sublines[subline_idx].line_type;
2064 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2065 || new_subline_refresh)
2067 /* get_line tells us if we need to update the line */
2068 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2069 &align, linebuf, sizeof(linebuf));
2071 #ifdef HAVE_LCD_BITMAP
2072 /* peakmeter */
2073 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2075 /* the peakmeter should be alone on its line */
2076 update_line = false;
2078 int h = font_get(wps_vp->vp.font)->height;
2079 int peak_meter_y = (line - wps_vp->first_line)* h;
2081 /* The user might decide to have the peak meter in the last
2082 line so that it is only displayed if no status bar is
2083 visible. If so we neither want do draw nor enable the
2084 peak meter. */
2085 if (peak_meter_y + h <= display->getheight()) {
2086 /* found a line with a peak meter -> remember that we must
2087 enable it later */
2088 enable_pm = true;
2089 peak_meter_enabled = true;
2090 peak_meter_screen(gwps->display, 0, peak_meter_y,
2091 MIN(h, display->getheight() - peak_meter_y));
2093 else
2095 peak_meter_enabled = false;
2099 #else /* HAVE_LCD_CHARCELL */
2101 /* progressbar */
2102 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2104 if (data->full_line_progressbar)
2105 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2106 else
2107 draw_player_progress(gwps);
2109 #endif
2111 if (update_line &&
2112 /* conditionals clear the line which means if the %Vd is put into the default
2113 viewport there will be a blank line.
2114 To get around this we dont allow any actual drawing to happen in the
2115 deault vp if other vp's are defined */
2116 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2118 if (flags & WPS_REFRESH_SCROLL)
2120 /* if the line is a scrolling one we don't want to update
2121 too often, so that it has the time to scroll */
2122 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2123 write_line(display, &align, line - wps_vp->first_line, true);
2125 else
2126 write_line(display, &align, line - wps_vp->first_line, false);
2130 #ifdef HAVE_LCD_BITMAP
2131 /* progressbar */
2132 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2134 if (wps_vp->pb)
2136 draw_progressbar(gwps, wps_vp);
2139 /* Now display any images in this viewport */
2140 wps_display_images(gwps, &wps_vp->vp);
2141 #endif
2144 #ifdef HAVE_LCD_BITMAP
2145 data->peak_meter_enabled = enable_pm;
2146 #endif
2148 /* Restore the default viewport */
2149 display->set_viewport(NULL);
2151 display->update();
2153 #ifdef HAVE_BACKLIGHT
2154 if (global_settings.caption_backlight && state->id3)
2156 /* turn on backlight n seconds before track ends, and turn it off n
2157 seconds into the new track. n == backlight_timeout, or 5s */
2158 int n = global_settings.backlight_timeout * 1000;
2160 if ( n < 1000 )
2161 n = 5000; /* use 5s if backlight is always on or off */
2163 if (((state->id3->elapsed < 1000) ||
2164 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2165 (state->paused == false))
2166 backlight_on();
2168 #endif
2169 #ifdef HAVE_REMOTE_LCD
2170 if (global_settings.remote_caption_backlight && state->id3)
2172 /* turn on remote backlight n seconds before track ends, and turn it
2173 off n seconds into the new track. n == remote_backlight_timeout,
2174 or 5s */
2175 int n = global_settings.remote_backlight_timeout * 1000;
2177 if ( n < 1000 )
2178 n = 5000; /* use 5s if backlight is always on or off */
2180 if (((state->id3->elapsed < 1000) ||
2181 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2182 (state->paused == false))
2183 remote_backlight_on();
2185 #endif
2186 /* force a bars update if they are being displayed */
2187 viewportmanager_draw_statusbars(NULL);
2188 return true;