Codec lib directories renamed, except for demac.
[kugel-rb.git] / apps / gui / gwps-common.c
bloba81b8555f7b7698b2552170d62279cab4144366e
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 "rbunicode.h"
30 #include "rtc.h"
31 #include "audio.h"
32 #include "status.h"
33 #include "power.h"
34 #include "powermgmt.h"
35 #include "sound.h"
36 #include "debug.h"
37 #ifdef HAVE_LCD_CHARCELLS
38 #include "hwcompat.h"
39 #endif
40 #include "abrepeat.h"
41 #include "mp3_playback.h"
42 #include "backlight.h"
43 #include "lang.h"
44 #include "misc.h"
45 #include "splash.h"
46 #include "scrollbar.h"
47 #include "led.h"
48 #include "lcd.h"
49 #ifdef HAVE_LCD_BITMAP
50 #include "peakmeter.h"
51 /* Image stuff */
52 #include "bmp.h"
53 #include "albumart.h"
54 #endif
55 #include "dsp.h"
56 #include "action.h"
57 #include "cuesheet.h"
58 #include "playlist.h"
59 #if CONFIG_CODEC == SWCODEC
60 #include "playback.h"
61 #endif
62 #include "backdrop.h"
64 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
65 /* 3% of 30min file == 54s step size */
66 #define MIN_FF_REWIND_STEP 500
68 /* draws the statusbar on the given wps-screen */
69 #ifdef HAVE_LCD_BITMAP
70 static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
72 bool draw = global_settings.statusbar;
74 if (wps->data->wps_sb_tag)
75 draw = wps->data->show_sb_on_wps;
77 if (draw)
78 gui_statusbar_draw(wps->statusbar, force);
80 #else
81 #define gui_wps_statusbar_draw(wps, force) \
82 gui_statusbar_draw((wps)->statusbar, (force))
83 #endif
84 #include "pcmbuf.h"
86 /* fades the volume */
87 bool wps_fading_out = false;
88 void fade(bool fade_in, bool updatewps)
90 int fp_global_vol = global_settings.volume << 8;
91 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
92 int fp_step = (fp_global_vol - fp_min_vol) / 30;
93 int i;
94 wps_fading_out = !fade_in;
95 if (fade_in) {
96 /* fade in */
97 int fp_volume = fp_min_vol;
99 /* zero out the sound */
100 sound_set_volume(fp_min_vol >> 8);
102 sleep(HZ/10); /* let audio thread run */
103 audio_resume();
105 while (fp_volume < fp_global_vol - fp_step) {
106 fp_volume += fp_step;
107 sound_set_volume(fp_volume >> 8);
108 if (updatewps)
110 FOR_NB_SCREENS(i)
111 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
113 sleep(1);
115 sound_set_volume(global_settings.volume);
117 else {
118 /* fade out */
119 int fp_volume = fp_global_vol;
121 while (fp_volume > fp_min_vol + fp_step) {
122 fp_volume -= fp_step;
123 sound_set_volume(fp_volume >> 8);
124 if (updatewps)
126 FOR_NB_SCREENS(i)
127 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
129 sleep(1);
131 audio_pause();
132 wps_fading_out = false;
133 #if CONFIG_CODEC != SWCODEC
134 #ifndef SIMULATOR
135 /* let audio thread run and wait for the mas to run out of data */
136 while (!mp3_pause_done())
137 #endif
138 sleep(HZ/10);
139 #endif
141 /* reset volume to what it was before the fade */
142 sound_set_volume(global_settings.volume);
146 /* return true if screen restore is needed
147 return false otherwise
149 bool update_onvol_change(struct gui_wps * gwps)
151 gui_wps_statusbar_draw(gwps, false);
152 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
154 #ifdef HAVE_LCD_CHARCELLS
155 splashf(0, "Vol: %3d dB",
156 sound_val2phys(SOUND_VOLUME, global_settings.volume));
157 return true;
158 #endif
159 return false;
162 void play_hop(int direction)
164 if(!wps_state.id3 || !wps_state.id3->length
165 || global_settings.skip_length == 0)
166 return;
167 #define STEP ((unsigned)global_settings.skip_length*1000)
168 if(direction == 1
169 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
170 #if CONFIG_CODEC == SWCODEC
171 if(global_settings.beep)
172 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
173 #endif
174 return;
176 if((direction == -1 && wps_state.id3->elapsed < STEP))
177 wps_state.id3->elapsed = 0;
178 else
179 wps_state.id3->elapsed += STEP *direction;
180 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
181 #if (CONFIG_CODEC == SWCODEC)
182 audio_pre_ff_rewind();
183 #else
184 audio_pause();
185 #endif
187 audio_ff_rewind(wps_state.id3->elapsed);
188 #if (CONFIG_CODEC != SWCODEC)
189 if (!wps_state.paused)
190 audio_resume();
191 #endif
192 #undef STEP
195 bool ffwd_rew(int button)
197 unsigned int step = 0; /* current ff/rewind step */
198 unsigned int max_step = 0; /* maximum ff/rewind step */
199 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
200 int direction = -1; /* forward=1 or backward=-1 */
201 bool exit = false;
202 bool usb = false;
203 int i = 0;
204 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
206 if (button == ACTION_NONE)
208 status_set_ffmode(0);
209 return usb;
211 while (!exit)
213 switch ( button )
215 case ACTION_WPS_SEEKFWD:
216 direction = 1;
217 case ACTION_WPS_SEEKBACK:
218 if (wps_state.ff_rewind)
220 if (direction == 1)
222 /* fast forwarding, calc max step relative to end */
223 max_step = (wps_state.id3->length -
224 (wps_state.id3->elapsed +
225 ff_rewind_count)) *
226 FF_REWIND_MAX_PERCENT / 100;
228 else
230 /* rewinding, calc max step relative to start */
231 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
232 FF_REWIND_MAX_PERCENT / 100;
235 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
237 if (step > max_step)
238 step = max_step;
240 ff_rewind_count += step * direction;
242 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
243 step += step >> ff_rw_accel;
245 else
247 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
248 wps_state.id3 && wps_state.id3->length )
250 if (!wps_state.paused)
251 #if (CONFIG_CODEC == SWCODEC)
252 audio_pre_ff_rewind();
253 #else
254 audio_pause();
255 #endif
256 #if CONFIG_KEYPAD == PLAYER_PAD
257 FOR_NB_SCREENS(i)
258 gui_wps[i].display->stop_scroll();
259 #endif
260 if (direction > 0)
261 status_set_ffmode(STATUS_FASTFORWARD);
262 else
263 status_set_ffmode(STATUS_FASTBACKWARD);
265 wps_state.ff_rewind = true;
267 step = 1000 * global_settings.ff_rewind_min_step;
269 else
270 break;
273 if (direction > 0) {
274 if ((wps_state.id3->elapsed + ff_rewind_count) >
275 wps_state.id3->length)
276 ff_rewind_count = wps_state.id3->length -
277 wps_state.id3->elapsed;
279 else {
280 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
281 ff_rewind_count = -wps_state.id3->elapsed;
284 FOR_NB_SCREENS(i)
285 gui_wps_refresh(&gui_wps[i],
286 (wps_state.wps_time_countup == false)?
287 ff_rewind_count:-ff_rewind_count,
288 WPS_REFRESH_PLAYER_PROGRESS |
289 WPS_REFRESH_DYNAMIC);
291 break;
293 case ACTION_WPS_STOPSEEK:
294 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
295 audio_ff_rewind(wps_state.id3->elapsed);
296 ff_rewind_count = 0;
297 wps_state.ff_rewind = false;
298 status_set_ffmode(0);
299 #if (CONFIG_CODEC != SWCODEC)
300 if (!wps_state.paused)
301 audio_resume();
302 #endif
303 #ifdef HAVE_LCD_CHARCELLS
304 gui_wps_display();
305 #endif
306 exit = true;
307 break;
309 default:
310 if(default_event_handler(button) == SYS_USB_CONNECTED) {
311 status_set_ffmode(0);
312 usb = true;
313 exit = true;
315 break;
317 if (!exit)
318 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
320 return usb;
323 bool gui_wps_display(void)
325 int i;
326 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
328 global_status.resume_index = -1;
329 #ifdef HAVE_LCD_BITMAP
330 gui_syncstatusbar_draw(&statusbars, true);
331 #endif
332 splash(HZ, ID2P(LANG_END_PLAYLIST));
333 return true;
335 else
337 FOR_NB_SCREENS(i)
339 /* Update the values in the first (default) viewport - in case the user
340 has modified the statusbar or colour settings */
341 #ifdef HAVE_LCD_BITMAP
342 #if LCD_DEPTH > 1
343 if (gui_wps[i].display->depth > 1)
345 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
346 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
348 #endif
349 #endif
351 gui_wps[i].display->clear_display();
352 if (!gui_wps[i].data->wps_loaded) {
353 if ( !gui_wps[i].data->num_tokens ) {
354 /* set the default wps for the main-screen */
355 if(i == 0)
357 #ifdef HAVE_LCD_BITMAP
358 #if LCD_DEPTH > 1
359 unload_wps_backdrop();
360 #endif
361 wps_data_load(gui_wps[i].data,
362 gui_wps[i].display,
363 "%s%?it<%?in<%in. |>%it|%fn>\n"
364 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
365 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
366 "\n"
367 "%al%pc/%pt%ar[%pp:%pe]\n"
368 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
369 "%pb\n"
370 "%pm\n", false);
371 #else
372 wps_data_load(gui_wps[i].data,
373 gui_wps[i].display,
374 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
375 "%pc%?ps<*|/>%pt\n", false);
376 #endif
378 #if NB_SCREENS == 2
379 /* set the default wps for the remote-screen */
380 else if(i == 1)
382 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
383 unload_remote_wps_backdrop();
384 #endif
385 wps_data_load(gui_wps[i].data,
386 gui_wps[i].display,
387 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
388 "%s%?it<%?in<%in. |>%it|%fn>\n"
389 "%al%pc/%pt%ar[%pp:%pe]\n"
390 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
391 "%pb\n", false);
393 #endif
398 yield();
399 FOR_NB_SCREENS(i)
401 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
403 return false;
406 bool update(struct gui_wps *gwps)
408 bool track_changed = audio_has_changed_track();
409 bool retcode = false;
411 gwps->state->nid3 = audio_next_track();
412 if (track_changed)
414 gwps->display->stop_scroll();
415 gwps->state->id3 = audio_current_track();
417 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
418 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
420 /* the current cuesheet isn't the right one any more */
422 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
423 /* We have the new cuesheet in memory (temp_cue),
424 let's make it the current one ! */
425 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
427 else {
428 /* We need to parse the new cuesheet */
430 char cuepath[MAX_PATH];
432 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
433 parse_cuesheet(cuepath, curr_cue))
435 gwps->state->id3->cuesheet_type = 1;
436 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
440 cue_spoof_id3(curr_cue, gwps->state->id3);
443 if (gui_wps_display())
444 retcode = true;
445 else{
446 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
449 if (gwps->state->id3)
451 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
452 sizeof(gwps->state->current_track_path));
453 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
457 if (gwps->state->id3)
459 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
460 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
461 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
462 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
464 /* We've changed tracks within the cuesheet :
465 we need to update the ID3 info and refresh the WPS */
467 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
468 cue_spoof_id3(curr_cue, gwps->state->id3);
470 gwps->display->stop_scroll();
471 if (gui_wps_display())
472 retcode = true;
473 else
474 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
476 else
477 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
480 gui_wps_statusbar_draw(gwps, false);
482 return retcode;
486 void display_keylock_text(bool locked)
488 int i;
489 FOR_NB_SCREENS(i)
490 gui_wps[i].display->stop_scroll();
492 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
495 #ifdef HAVE_LCD_BITMAP
497 static void draw_progressbar(struct gui_wps *gwps,
498 struct progressbar *pb)
500 struct screen *display = gwps->display;
501 struct wps_state *state = gwps->state;
502 if (pb->have_bitmap_pb)
503 gui_bitmap_scrollbar_draw(display, pb->bm,
504 pb->x, pb->y, pb->width, pb->bm.height,
505 state->id3->length ? state->id3->length : 1, 0,
506 state->id3->length ? state->id3->elapsed
507 + state->ff_rewind_count : 0,
508 HORIZONTAL);
509 else
510 gui_scrollbar_draw(display, pb->x, pb->y, pb->width, pb->height,
511 state->id3->length ? state->id3->length : 1, 0,
512 state->id3->length ? state->id3->elapsed
513 + state->ff_rewind_count : 0,
514 HORIZONTAL);
515 #ifdef AB_REPEAT_ENABLE
516 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
517 ab_draw_markers(display, state->id3->length,
518 pb->x, pb->x + pb->width, pb->y, pb->height);
519 #endif
521 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
522 cue_draw_markers(display, state->id3->length,
523 pb->x, pb->x + pb->width, pb->y+1, pb->height-2);
526 /* clears the area where the image was shown */
527 static void clear_image_pos(struct gui_wps *gwps, int n)
529 if(!gwps)
530 return;
531 struct wps_data *data = gwps->data;
532 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
533 gwps->display->fillrect(data->img[n].x, data->img[n].y,
534 data->img[n].bm.width, data->img[n].subimage_height);
535 gwps->display->set_drawmode(DRMODE_SOLID);
538 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
540 struct screen *display = gwps->display;
541 struct wps_data *data = gwps->data;
542 if(data->img[n].always_display)
543 display->set_drawmode(DRMODE_FG);
544 else
545 display->set_drawmode(DRMODE_SOLID);
547 #if LCD_DEPTH > 1
548 if(data->img[n].bm.format == FORMAT_MONO) {
549 #endif
550 display->mono_bitmap_part(data->img[n].bm.data,
551 0, data->img[n].subimage_height * subimage,
552 data->img[n].bm.width, data->img[n].x,
553 data->img[n].y, data->img[n].bm.width,
554 data->img[n].subimage_height);
555 #if LCD_DEPTH > 1
556 } else {
557 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
558 0, data->img[n].subimage_height * subimage,
559 data->img[n].bm.width, data->img[n].x,
560 data->img[n].y, data->img[n].bm.width,
561 data->img[n].subimage_height);
563 #endif
566 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
568 if(!gwps || !gwps->data || !gwps->display)
569 return;
571 int n;
572 struct wps_data *data = gwps->data;
573 struct screen *display = gwps->display;
575 for (n = 0; n < MAX_IMAGES; n++)
577 if (data->img[n].loaded)
579 if (data->img[n].display >= 0)
581 wps_draw_image(gwps, n, data->img[n].display);
582 } else if (data->img[n].always_display && data->img[n].vp == vp)
584 wps_draw_image(gwps, n, 0);
588 display->set_drawmode(DRMODE_SOLID);
591 #else /* HAVE_LCD_CHARCELL */
593 static bool draw_player_progress(struct gui_wps *gwps)
595 struct wps_state *state = gwps->state;
596 struct screen *display = gwps->display;
597 unsigned char progress_pattern[7];
598 int pos = 0;
599 int i;
601 if (!state->id3)
602 return false;
604 if (state->id3->length)
605 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
606 / state->id3->length;
608 for (i = 0; i < 7; i++, pos -= 5)
610 if (pos <= 0)
611 progress_pattern[i] = 0x1f;
612 else if (pos >= 5)
613 progress_pattern[i] = 0x00;
614 else
615 progress_pattern[i] = 0x1f >> pos;
618 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
619 return true;
622 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
624 static const unsigned char numbers[10][4] = {
625 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
626 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
627 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
628 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
629 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
630 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
631 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
632 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
633 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
634 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
637 struct wps_state *state = gwps->state;
638 struct screen *display = gwps->display;
639 struct wps_data *data = gwps->data;
640 unsigned char progress_pattern[7];
641 char timestr[10];
642 int time;
643 int time_idx = 0;
644 int pos = 0;
645 int pat_idx = 1;
646 int digit, i, j;
647 bool softchar;
649 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
650 return;
652 time = state->id3->elapsed + state->ff_rewind_count;
653 if (state->id3->length)
654 pos = 55 * time / state->id3->length;
656 memset(timestr, 0, sizeof(timestr));
657 format_time(timestr, sizeof(timestr)-2, time);
658 timestr[strlen(timestr)] = ':'; /* always safe */
660 for (i = 0; i < 11; i++, pos -= 5)
662 softchar = false;
663 memset(progress_pattern, 0, sizeof(progress_pattern));
665 if ((digit = timestr[time_idx]))
667 softchar = true;
668 digit -= '0';
670 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
672 memcpy(progress_pattern, numbers[digit], 4);
673 time_idx += 2;
675 else /* tens, shifted right */
677 for (j = 0; j < 4; j++)
678 progress_pattern[j] = numbers[digit][j] >> 1;
680 if (time_idx > 0) /* not the first group, add colon in front */
682 progress_pattern[1] |= 0x10;
683 progress_pattern[3] |= 0x10;
685 time_idx++;
688 if (pos >= 5)
689 progress_pattern[5] = progress_pattern[6] = 0x1f;
692 if (pos > 0 && pos < 5)
694 softchar = true;
695 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
698 if (softchar && pat_idx < 8)
700 display->define_pattern(data->wps_progress_pat[pat_idx],
701 progress_pattern);
702 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
703 pat_idx++;
705 else if (pos <= 0)
706 buf = utf8encode(' ', buf);
707 else
708 buf = utf8encode(0xe115, buf); /* 2/7 _ */
710 *buf = '\0';
713 #endif /* HAVE_LCD_CHARCELL */
715 static char* get_codectype(const struct mp3entry* id3)
717 if (id3->codectype < AFMT_NUM_CODECS) {
718 return (char*)audio_formats[id3->codectype].label;
719 } else {
720 return NULL;
724 /* Extract a part from a path.
726 * buf - buffer extract part to.
727 * buf_size - size of buffer.
728 * path - path to extract from.
729 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
730 * parent of parent, etc.
732 * Returns buf if the desired level was found, NULL otherwise.
734 static char* get_dir(char* buf, int buf_size, const char* path, int level)
736 const char* sep;
737 const char* last_sep;
738 int len;
740 sep = path + strlen(path);
741 last_sep = sep;
743 while (sep > path)
745 if ('/' == *(--sep))
747 if (!level)
748 break;
750 level--;
751 last_sep = sep - 1;
755 if (level || (last_sep <= sep))
756 return NULL;
758 len = MIN(last_sep - sep, buf_size - 1);
759 strncpy(buf, sep + 1, len);
760 buf[len] = 0;
761 return buf;
764 /* Return the tag found at index i and write its value in buf.
765 The return value is buf if the tag had a value, or NULL if not.
767 intval is used with conditionals/enums: when this function is called,
768 intval should contain the number of options in the conditional/enum.
769 When this function returns, intval is -1 if the tag is non numeric or,
770 if the tag is numeric, *intval is the enum case we want to go to (between 1
771 and the original value of *intval, inclusive).
772 When not treating a conditional/enum, intval should be NULL.
774 static const char *get_token_value(struct gui_wps *gwps,
775 struct wps_token *token,
776 char *buf, int buf_size,
777 int *intval)
779 if (!gwps)
780 return NULL;
782 struct wps_data *data = gwps->data;
783 struct wps_state *state = gwps->state;
785 if (!data || !state)
786 return NULL;
788 struct mp3entry *id3;
790 if (token->next)
791 id3 = state->nid3;
792 else
793 id3 = state->id3;
795 if (!id3)
796 return NULL;
798 #if CONFIG_RTC
799 struct tm* tm = NULL;
801 /* if the token is an RTC one, update the time
802 and do the necessary checks */
803 if (token->type >= WPS_TOKENS_RTC_BEGIN
804 && token->type <= WPS_TOKENS_RTC_END)
806 tm = get_time();
808 if (!valid_time(tm))
809 return NULL;
811 #endif
813 int limit = 1;
814 if (intval)
816 limit = *intval;
817 *intval = -1;
820 switch (token->type)
822 case WPS_TOKEN_CHARACTER:
823 return &(token->value.c);
825 case WPS_TOKEN_STRING:
826 return data->strings[token->value.i];
828 case WPS_TOKEN_TRACK_TIME_ELAPSED:
829 format_time(buf, buf_size,
830 id3->elapsed + state->ff_rewind_count);
831 return buf;
833 case WPS_TOKEN_TRACK_TIME_REMAINING:
834 format_time(buf, buf_size,
835 id3->length - id3->elapsed -
836 state->ff_rewind_count);
837 return buf;
839 case WPS_TOKEN_TRACK_LENGTH:
840 format_time(buf, buf_size, id3->length);
841 return buf;
843 case WPS_TOKEN_PLAYLIST_ENTRIES:
844 snprintf(buf, buf_size, "%d", playlist_amount());
845 return buf;
847 case WPS_TOKEN_PLAYLIST_NAME:
848 return playlist_name(NULL, buf, buf_size);
850 case WPS_TOKEN_PLAYLIST_POSITION:
851 snprintf(buf, buf_size, "%d", playlist_get_display_index());
852 return buf;
854 case WPS_TOKEN_PLAYLIST_SHUFFLE:
855 if ( global_settings.playlist_shuffle )
856 return "s";
857 else
858 return NULL;
859 break;
861 case WPS_TOKEN_VOLUME:
862 snprintf(buf, buf_size, "%d", global_settings.volume);
863 if (intval)
865 if (global_settings.volume == sound_min(SOUND_VOLUME))
867 *intval = 1;
869 else if (global_settings.volume == 0)
871 *intval = limit - 1;
873 else if (global_settings.volume > 0)
875 *intval = limit;
877 else
879 *intval = (limit - 3) * (global_settings.volume
880 - sound_min(SOUND_VOLUME) - 1)
881 / (-1 - sound_min(SOUND_VOLUME)) + 2;
884 return buf;
886 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
887 if (id3->length <= 0)
888 return NULL;
890 if (intval)
892 *intval = limit * (id3->elapsed + state->ff_rewind_count)
893 / id3->length + 1;
895 snprintf(buf, buf_size, "%d",
896 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
897 return buf;
899 case WPS_TOKEN_METADATA_ARTIST:
900 return id3->artist;
902 case WPS_TOKEN_METADATA_COMPOSER:
903 return id3->composer;
905 case WPS_TOKEN_METADATA_ALBUM:
906 return id3->album;
908 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
909 return id3->albumartist;
911 case WPS_TOKEN_METADATA_GROUPING:
912 return id3->grouping;
914 case WPS_TOKEN_METADATA_GENRE:
915 return id3->genre_string;
917 case WPS_TOKEN_METADATA_DISC_NUMBER:
918 if (id3->disc_string)
919 return id3->disc_string;
920 if (id3->discnum) {
921 snprintf(buf, buf_size, "%d", id3->discnum);
922 return buf;
924 return NULL;
926 case WPS_TOKEN_METADATA_TRACK_NUMBER:
927 if (id3->track_string)
928 return id3->track_string;
930 if (id3->tracknum) {
931 snprintf(buf, buf_size, "%d", id3->tracknum);
932 return buf;
934 return NULL;
936 case WPS_TOKEN_METADATA_TRACK_TITLE:
937 return id3->title;
939 case WPS_TOKEN_METADATA_VERSION:
940 switch (id3->id3version)
942 case ID3_VER_1_0:
943 return "1";
945 case ID3_VER_1_1:
946 return "1.1";
948 case ID3_VER_2_2:
949 return "2.2";
951 case ID3_VER_2_3:
952 return "2.3";
954 case ID3_VER_2_4:
955 return "2.4";
957 default:
958 return NULL;
961 case WPS_TOKEN_METADATA_YEAR:
962 if( id3->year_string )
963 return id3->year_string;
965 if (id3->year) {
966 snprintf(buf, buf_size, "%d", id3->year);
967 return buf;
969 return NULL;
971 case WPS_TOKEN_METADATA_COMMENT:
972 return id3->comment;
974 #ifdef HAVE_ALBUMART
975 case WPS_TOKEN_ALBUMART_DISPLAY:
976 draw_album_art(gwps, audio_current_aa_hid(), false);
977 return NULL;
979 case WPS_TOKEN_ALBUMART_FOUND:
980 if (audio_current_aa_hid() >= 0) {
981 return "C";
983 return NULL;
984 #endif
986 case WPS_TOKEN_FILE_BITRATE:
987 if(id3->bitrate)
988 snprintf(buf, buf_size, "%d", id3->bitrate);
989 else
990 return "?";
991 return buf;
993 case WPS_TOKEN_FILE_CODEC:
994 if (intval)
996 if(id3->codectype == AFMT_UNKNOWN)
997 *intval = AFMT_NUM_CODECS;
998 else
999 *intval = id3->codectype;
1001 return get_codectype(id3);
1003 case WPS_TOKEN_FILE_FREQUENCY:
1004 snprintf(buf, buf_size, "%ld", id3->frequency);
1005 return buf;
1007 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
1008 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1009 if ((id3->frequency % 1000) < 100)
1010 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1011 else
1012 snprintf(buf, buf_size, "%ld.%d",
1013 id3->frequency / 1000,
1014 (id3->frequency % 1000) / 100);
1015 return buf;
1017 case WPS_TOKEN_FILE_NAME:
1018 if (get_dir(buf, buf_size, id3->path, 0)) {
1019 /* Remove extension */
1020 char* sep = strrchr(buf, '.');
1021 if (NULL != sep) {
1022 *sep = 0;
1024 return buf;
1026 else {
1027 return NULL;
1030 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1031 return get_dir(buf, buf_size, id3->path, 0);
1033 case WPS_TOKEN_FILE_PATH:
1034 return id3->path;
1036 case WPS_TOKEN_FILE_SIZE:
1037 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1038 return buf;
1040 case WPS_TOKEN_FILE_VBR:
1041 return id3->vbr ? "(avg)" : NULL;
1043 case WPS_TOKEN_FILE_DIRECTORY:
1044 return get_dir(buf, buf_size, id3->path, token->value.i);
1046 case WPS_TOKEN_BATTERY_PERCENT:
1048 int l = battery_level();
1050 if (intval)
1052 limit = MAX(limit, 2);
1053 if (l > -1) {
1054 /* First enum is used for "unknown level". */
1055 *intval = (limit - 1) * l / 100 + 2;
1056 } else {
1057 *intval = 1;
1061 if (l > -1) {
1062 snprintf(buf, buf_size, "%d", l);
1063 return buf;
1064 } else {
1065 return "?";
1069 case WPS_TOKEN_BATTERY_VOLTS:
1071 unsigned int v = battery_voltage();
1072 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1073 return buf;
1076 case WPS_TOKEN_BATTERY_TIME:
1078 int t = battery_time();
1079 if (t >= 0)
1080 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1081 else
1082 return "?h ?m";
1083 return buf;
1086 #if CONFIG_CHARGING
1087 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1089 if(charger_input_state==CHARGER)
1090 return "p";
1091 else
1092 return NULL;
1094 #endif
1095 #if CONFIG_CHARGING >= CHARGING_MONITOR
1096 case WPS_TOKEN_BATTERY_CHARGING:
1098 if (charge_state == CHARGING || charge_state == TOPOFF) {
1099 return "c";
1100 } else {
1101 return NULL;
1104 #endif
1105 case WPS_TOKEN_BATTERY_SLEEPTIME:
1107 if (get_sleep_timer() == 0)
1108 return NULL;
1109 else
1111 format_time(buf, buf_size, get_sleep_timer() * 1000);
1112 return buf;
1116 case WPS_TOKEN_PLAYBACK_STATUS:
1118 int status = audio_status();
1119 int mode = 1;
1120 if (status == AUDIO_STATUS_PLAY)
1121 mode = 2;
1122 if (wps_fading_out ||
1123 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1124 mode = 3;
1125 if (status_get_ffmode() == STATUS_FASTFORWARD)
1126 mode = 4;
1127 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1128 mode = 5;
1130 if (intval) {
1131 *intval = mode;
1134 snprintf(buf, buf_size, "%d", mode-1);
1135 return buf;
1138 case WPS_TOKEN_REPEAT_MODE:
1139 if (intval)
1140 *intval = global_settings.repeat_mode + 1;
1141 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1142 return buf;
1143 case WPS_TOKEN_RTC_12HOUR_CFG:
1144 if (intval)
1145 *intval = global_settings.timeformat + 1;
1146 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1147 return buf;
1148 #if CONFIG_RTC
1149 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1150 /* d: day of month (01..31) */
1151 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1152 return buf;
1154 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1155 /* e: day of month, blank padded ( 1..31) */
1156 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1157 return buf;
1159 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1160 /* H: hour (00..23) */
1161 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1162 return buf;
1164 case WPS_TOKEN_RTC_HOUR_24:
1165 /* k: hour ( 0..23) */
1166 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1167 return buf;
1169 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1170 /* I: hour (01..12) */
1171 snprintf(buf, buf_size, "%02d",
1172 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1173 return buf;
1175 case WPS_TOKEN_RTC_HOUR_12:
1176 /* l: hour ( 1..12) */
1177 snprintf(buf, buf_size, "%2d",
1178 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1179 return buf;
1181 case WPS_TOKEN_RTC_MONTH:
1182 /* m: month (01..12) */
1183 if (intval)
1184 *intval = tm->tm_mon + 1;
1185 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1186 return buf;
1188 case WPS_TOKEN_RTC_MINUTE:
1189 /* M: minute (00..59) */
1190 snprintf(buf, buf_size, "%02d", tm->tm_min);
1191 return buf;
1193 case WPS_TOKEN_RTC_SECOND:
1194 /* S: second (00..59) */
1195 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1196 return buf;
1198 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1199 /* y: last two digits of year (00..99) */
1200 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1201 return buf;
1203 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1204 /* Y: year (1970...) */
1205 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1206 return buf;
1208 case WPS_TOKEN_RTC_AM_PM_UPPER:
1209 /* p: upper case AM or PM indicator */
1210 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1212 case WPS_TOKEN_RTC_AM_PM_LOWER:
1213 /* P: lower case am or pm indicator */
1214 return tm->tm_hour/12 == 0 ? "am" : "pm";
1216 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1217 /* a: abbreviated weekday name (Sun..Sat) */
1218 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1220 case WPS_TOKEN_RTC_MONTH_NAME:
1221 /* b: abbreviated month name (Jan..Dec) */
1222 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1224 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1225 /* u: day of week (1..7); 1 is Monday */
1226 if (intval)
1227 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1228 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1229 return buf;
1231 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1232 /* w: day of week (0..6); 0 is Sunday */
1233 if (intval)
1234 *intval = tm->tm_wday + 1;
1235 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1236 return buf;
1237 #else
1238 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1239 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1240 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1241 case WPS_TOKEN_RTC_HOUR_24:
1242 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1243 case WPS_TOKEN_RTC_HOUR_12:
1244 case WPS_TOKEN_RTC_MONTH:
1245 case WPS_TOKEN_RTC_MINUTE:
1246 case WPS_TOKEN_RTC_SECOND:
1247 case WPS_TOKEN_RTC_AM_PM_UPPER:
1248 case WPS_TOKEN_RTC_AM_PM_LOWER:
1249 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1250 return "--";
1251 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1252 return "----";
1253 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1254 case WPS_TOKEN_RTC_MONTH_NAME:
1255 return "---";
1256 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1257 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1258 return "-";
1259 #endif
1261 #ifdef HAVE_LCD_CHARCELLS
1262 case WPS_TOKEN_PROGRESSBAR:
1264 char *end = utf8encode(data->wps_progress_pat[0], buf);
1265 *end = '\0';
1266 return buf;
1269 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1270 if(is_new_player())
1272 /* we need 11 characters (full line) for
1273 progress-bar */
1274 strncpy(buf, " ", buf_size);
1276 else
1278 /* Tell the user if we have an OldPlayer */
1279 strncpy(buf, " <Old LCD> ", buf_size);
1281 return buf;
1282 #endif
1284 #ifdef HAVE_TAGCACHE
1285 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1286 if (intval) {
1287 *intval = id3->playcount + 1;
1289 snprintf(buf, buf_size, "%ld", id3->playcount);
1290 return buf;
1292 case WPS_TOKEN_DATABASE_RATING:
1293 if (intval) {
1294 *intval = id3->rating + 1;
1296 snprintf(buf, buf_size, "%d", id3->rating);
1297 return buf;
1299 case WPS_TOKEN_DATABASE_AUTOSCORE:
1300 if (intval)
1301 *intval = id3->score + 1;
1303 snprintf(buf, buf_size, "%d", id3->score);
1304 return buf;
1305 #endif
1307 #if (CONFIG_CODEC == SWCODEC)
1308 case WPS_TOKEN_CROSSFADE:
1309 if (intval)
1310 *intval = global_settings.crossfade + 1;
1311 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1312 return buf;
1314 case WPS_TOKEN_REPLAYGAIN:
1316 int val;
1318 if (global_settings.replaygain == 0)
1319 val = 1; /* off */
1320 else
1322 int type =
1323 get_replaygain_mode(id3->track_gain_string != NULL,
1324 id3->album_gain_string != NULL);
1325 if (type < 0)
1326 val = 6; /* no tag */
1327 else
1328 val = type + 2;
1330 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1331 val += 2;
1334 if (intval)
1335 *intval = val;
1337 switch (val)
1339 case 1:
1340 case 6:
1341 return "+0.00 dB";
1342 break;
1343 case 2:
1344 case 4:
1345 strncpy(buf, id3->track_gain_string, buf_size);
1346 break;
1347 case 3:
1348 case 5:
1349 strncpy(buf, id3->album_gain_string, buf_size);
1350 break;
1352 return buf;
1354 #endif /* (CONFIG_CODEC == SWCODEC) */
1356 #if (CONFIG_CODEC != MAS3507D)
1357 case WPS_TOKEN_SOUND_PITCH:
1359 int val = sound_get_pitch();
1360 snprintf(buf, buf_size, "%d.%d",
1361 val / 10, val % 10);
1362 return buf;
1364 #endif
1366 case WPS_TOKEN_MAIN_HOLD:
1367 #ifdef HAS_BUTTON_HOLD
1368 if (button_hold())
1369 #else
1370 if (is_keys_locked())
1371 #endif /*hold switch or softlock*/
1372 return "h";
1373 else
1374 return NULL;
1376 #ifdef HAS_REMOTE_BUTTON_HOLD
1377 case WPS_TOKEN_REMOTE_HOLD:
1378 if (remote_button_hold())
1379 return "r";
1380 else
1381 return NULL;
1382 #endif
1384 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1385 case WPS_TOKEN_VLED_HDD:
1386 if(led_read(HZ/2))
1387 return "h";
1388 else
1389 return NULL;
1390 #endif
1391 case WPS_TOKEN_BUTTON_VOLUME:
1392 if (data->button_time_volume &&
1393 TIME_BEFORE(current_tick, data->button_time_volume +
1394 token->value.i))
1395 return "v";
1396 return NULL;
1397 default:
1398 return NULL;
1402 /* Return the index to the end token for the conditional token at index.
1403 The conditional token can be either a start token or a separator
1404 (i.e. option) token.
1406 static int find_conditional_end(struct wps_data *data, int index)
1408 int ret = index;
1409 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1410 ret = data->tokens[ret].value.i;
1412 /* ret now is the index to the end token for the conditional. */
1413 return ret;
1416 /* Evaluate the conditional that is at *token_index and return whether a skip
1417 has ocurred. *token_index is updated with the new position.
1419 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1421 if (!gwps)
1422 return false;
1424 struct wps_data *data = gwps->data;
1426 int i, cond_end;
1427 int cond_index = *token_index;
1428 char result[128];
1429 const char *value;
1430 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1431 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1433 /* treat ?xx<true> constructs as if they had 2 options. */
1434 if (num_options < 2)
1435 num_options = 2;
1437 int intval = num_options;
1438 /* get_token_value needs to know the number of options in the enum */
1439 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1440 result, sizeof(result), &intval);
1442 /* intval is now the number of the enum option we want to read,
1443 starting from 1. If intval is -1, we check if value is empty. */
1444 if (intval == -1)
1445 intval = (value && *value) ? 1 : num_options;
1446 else if (intval > num_options || intval < 1)
1447 intval = num_options;
1449 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1451 /* skip to the appropriate enum case */
1452 int next = cond_index + 2;
1453 for (i = 1; i < intval; i++)
1455 next = data->tokens[next].value.i;
1457 *token_index = next;
1459 if (prev_val == intval)
1461 /* Same conditional case as previously. Return without clearing the
1462 pictures */
1463 return false;
1466 cond_end = find_conditional_end(data, cond_index + 2);
1467 for (i = cond_index + 3; i < cond_end; i++)
1469 #ifdef HAVE_LCD_BITMAP
1470 /* clear all pictures in the conditional and nested ones */
1471 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1472 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1473 #endif
1474 #ifdef HAVE_ALBUMART
1475 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1476 draw_album_art(gwps, audio_current_aa_hid(), true);
1477 #endif
1480 return true;
1483 /* Read a (sub)line to the given alignment format buffer.
1484 linebuf is the buffer where the data is actually stored.
1485 align is the alignment format that'll be used to display the text.
1486 The return value indicates whether the line needs to be updated.
1488 static bool get_line(struct gui_wps *gwps,
1489 int line, int subline,
1490 struct align_pos *align,
1491 char *linebuf,
1492 int linebuf_size)
1494 struct wps_data *data = gwps->data;
1496 char temp_buf[128];
1497 char *buf = linebuf; /* will always point to the writing position */
1498 char *linebuf_end = linebuf + linebuf_size - 1;
1499 int i, last_token_idx;
1500 bool update = false;
1502 /* alignment-related variables */
1503 int cur_align;
1504 char* cur_align_start;
1505 cur_align_start = buf;
1506 cur_align = WPS_ALIGN_LEFT;
1507 align->left = NULL;
1508 align->center = NULL;
1509 align->right = NULL;
1511 /* Process all tokens of the desired subline */
1512 last_token_idx = wps_last_token_index(data, line, subline);
1513 for (i = wps_first_token_index(data, line, subline);
1514 i <= last_token_idx; i++)
1516 switch(data->tokens[i].type)
1518 case WPS_TOKEN_CONDITIONAL:
1519 /* place ourselves in the right conditional case */
1520 update |= evaluate_conditional(gwps, &i);
1521 break;
1523 case WPS_TOKEN_CONDITIONAL_OPTION:
1524 /* we've finished in the curent conditional case,
1525 skip to the end of the conditional structure */
1526 i = find_conditional_end(data, i);
1527 break;
1529 #ifdef HAVE_LCD_BITMAP
1530 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1532 struct gui_img *img = data->img;
1533 int n = data->tokens[i].value.i & 0xFF;
1534 int subimage = data->tokens[i].value.i >> 8;
1536 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1537 img[n].display = subimage;
1538 break;
1540 #endif
1542 case WPS_TOKEN_ALIGN_LEFT:
1543 case WPS_TOKEN_ALIGN_CENTER:
1544 case WPS_TOKEN_ALIGN_RIGHT:
1545 /* remember where the current aligned text started */
1546 switch (cur_align)
1548 case WPS_ALIGN_LEFT:
1549 align->left = cur_align_start;
1550 break;
1552 case WPS_ALIGN_CENTER:
1553 align->center = cur_align_start;
1554 break;
1556 case WPS_ALIGN_RIGHT:
1557 align->right = cur_align_start;
1558 break;
1560 /* start a new alignment */
1561 switch (data->tokens[i].type)
1563 case WPS_TOKEN_ALIGN_LEFT:
1564 cur_align = WPS_ALIGN_LEFT;
1565 break;
1566 case WPS_TOKEN_ALIGN_CENTER:
1567 cur_align = WPS_ALIGN_CENTER;
1568 break;
1569 case WPS_TOKEN_ALIGN_RIGHT:
1570 cur_align = WPS_ALIGN_RIGHT;
1571 break;
1572 default:
1573 break;
1575 *buf++ = 0;
1576 cur_align_start = buf;
1577 break;
1578 case WPS_VIEWPORT_ENABLE:
1580 char label = data->tokens[i].value.i;
1581 int j;
1582 char temp = VP_DRAW_HIDEABLE;
1583 for(j=0;j<data->num_viewports;j++)
1585 temp = VP_DRAW_HIDEABLE;
1586 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1587 (data->viewports[j].label == label))
1589 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1590 temp |= VP_DRAW_WASHIDDEN;
1591 data->viewports[j].hidden_flags = temp;
1595 break;
1596 default:
1598 /* get the value of the tag and copy it to the buffer */
1599 const char *value = get_token_value(gwps, &data->tokens[i],
1600 temp_buf, sizeof(temp_buf), NULL);
1601 if (value)
1603 update = true;
1604 while (*value && (buf < linebuf_end))
1605 *buf++ = *value++;
1607 break;
1612 /* close the current alignment */
1613 switch (cur_align)
1615 case WPS_ALIGN_LEFT:
1616 align->left = cur_align_start;
1617 break;
1619 case WPS_ALIGN_CENTER:
1620 align->center = cur_align_start;
1621 break;
1623 case WPS_ALIGN_RIGHT:
1624 align->right = cur_align_start;
1625 break;
1628 return update;
1631 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1633 struct wps_data *data = gwps->data;
1634 int i;
1635 int subline_idx = wps_subline_index(data, line, subline);
1636 int last_token_idx = wps_last_token_index(data, line, subline);
1638 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1640 for (i = wps_first_token_index(data, line, subline);
1641 i <= last_token_idx; i++)
1643 switch(data->tokens[i].type)
1645 case WPS_TOKEN_CONDITIONAL:
1646 /* place ourselves in the right conditional case */
1647 evaluate_conditional(gwps, &i);
1648 break;
1650 case WPS_TOKEN_CONDITIONAL_OPTION:
1651 /* we've finished in the curent conditional case,
1652 skip to the end of the conditional structure */
1653 i = find_conditional_end(data, i);
1654 break;
1656 case WPS_TOKEN_SUBLINE_TIMEOUT:
1657 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1658 break;
1660 default:
1661 break;
1666 /* Calculates which subline should be displayed for the specified line
1667 Returns true iff the subline must be refreshed */
1668 static bool update_curr_subline(struct gui_wps *gwps, int line)
1670 struct wps_data *data = gwps->data;
1672 int search, search_start, num_sublines;
1673 bool reset_subline;
1674 bool new_subline_refresh;
1675 bool only_one_subline;
1677 num_sublines = data->lines[line].num_sublines;
1678 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1679 new_subline_refresh = false;
1680 only_one_subline = false;
1682 /* if time to advance to next sub-line */
1683 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1684 reset_subline)
1686 /* search all sublines until the next subline with time > 0
1687 is found or we get back to the subline we started with */
1688 if (reset_subline)
1689 search_start = 0;
1690 else
1691 search_start = data->lines[line].curr_subline;
1693 for (search = 0; search < num_sublines; search++)
1695 data->lines[line].curr_subline++;
1697 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1698 if (data->lines[line].curr_subline == num_sublines)
1700 if (data->lines[line].curr_subline == 1)
1701 only_one_subline = true;
1702 data->lines[line].curr_subline = 0;
1705 /* if back where we started after search or
1706 only one subline is defined on the line */
1707 if (((search > 0) &&
1708 (data->lines[line].curr_subline == search_start)) ||
1709 only_one_subline)
1711 /* no other subline with a time > 0 exists */
1712 data->lines[line].subline_expire_time = (reset_subline ?
1713 current_tick :
1714 data->lines[line].subline_expire_time) + 100 * HZ;
1715 break;
1717 else
1719 /* get initial time multiplier for this subline */
1720 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1722 int subline_idx = wps_subline_index(data, line,
1723 data->lines[line].curr_subline);
1725 /* only use this subline if subline time > 0 */
1726 if (data->sublines[subline_idx].time_mult > 0)
1728 new_subline_refresh = true;
1729 data->lines[line].subline_expire_time = (reset_subline ?
1730 current_tick : data->lines[line].subline_expire_time) +
1731 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1732 break;
1738 return new_subline_refresh;
1741 /* Display a line appropriately according to its alignment format.
1742 format_align contains the text, separated between left, center and right.
1743 line is the index of the line on the screen.
1744 scroll indicates whether the line is a scrolling one or not.
1746 static void write_line(struct screen *display,
1747 struct align_pos *format_align,
1748 int line,
1749 bool scroll)
1752 int left_width = 0, left_xpos;
1753 int center_width = 0, center_xpos;
1754 int right_width = 0, right_xpos;
1755 int ypos;
1756 int space_width;
1757 int string_height;
1758 int scroll_width;
1760 /* calculate different string sizes and positions */
1761 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1762 if (format_align->left != 0) {
1763 display->getstringsize((unsigned char *)format_align->left,
1764 &left_width, &string_height);
1767 if (format_align->right != 0) {
1768 display->getstringsize((unsigned char *)format_align->right,
1769 &right_width, &string_height);
1772 if (format_align->center != 0) {
1773 display->getstringsize((unsigned char *)format_align->center,
1774 &center_width, &string_height);
1777 left_xpos = 0;
1778 right_xpos = (display->getwidth() - right_width);
1779 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1781 scroll_width = display->getwidth() - left_xpos;
1783 /* Checks for overlapping strings.
1784 If needed the overlapping strings will be merged, separated by a
1785 space */
1787 /* CASE 1: left and centered string overlap */
1788 /* there is a left string, need to merge left and center */
1789 if ((left_width != 0 && center_width != 0) &&
1790 (left_xpos + left_width + space_width > center_xpos)) {
1791 /* replace the former separator '\0' of left and
1792 center string with a space */
1793 *(--format_align->center) = ' ';
1794 /* calculate the new width and position of the merged string */
1795 left_width = left_width + space_width + center_width;
1796 /* there is no centered string anymore */
1797 center_width = 0;
1799 /* there is no left string, move center to left */
1800 if ((left_width == 0 && center_width != 0) &&
1801 (left_xpos + left_width > center_xpos)) {
1802 /* move the center string to the left string */
1803 format_align->left = format_align->center;
1804 /* calculate the new width and position of the string */
1805 left_width = center_width;
1806 /* there is no centered string anymore */
1807 center_width = 0;
1810 /* CASE 2: centered and right string overlap */
1811 /* there is a right string, need to merge center and right */
1812 if ((center_width != 0 && right_width != 0) &&
1813 (center_xpos + center_width + space_width > right_xpos)) {
1814 /* replace the former separator '\0' of center and
1815 right string with a space */
1816 *(--format_align->right) = ' ';
1817 /* move the center string to the right after merge */
1818 format_align->right = format_align->center;
1819 /* calculate the new width and position of the merged string */
1820 right_width = center_width + space_width + right_width;
1821 right_xpos = (display->getwidth() - right_width);
1822 /* there is no centered string anymore */
1823 center_width = 0;
1825 /* there is no right string, move center to right */
1826 if ((center_width != 0 && right_width == 0) &&
1827 (center_xpos + center_width > right_xpos)) {
1828 /* move the center string to the right string */
1829 format_align->right = format_align->center;
1830 /* calculate the new width and position of the string */
1831 right_width = center_width;
1832 right_xpos = (display->getwidth() - right_width);
1833 /* there is no centered string anymore */
1834 center_width = 0;
1837 /* CASE 3: left and right overlap
1838 There is no center string anymore, either there never
1839 was one or it has been merged in case 1 or 2 */
1840 /* there is a left string, need to merge left and right */
1841 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1842 (left_xpos + left_width + space_width > right_xpos)) {
1843 /* replace the former separator '\0' of left and
1844 right string with a space */
1845 *(--format_align->right) = ' ';
1846 /* calculate the new width and position of the string */
1847 left_width = left_width + space_width + right_width;
1848 /* there is no right string anymore */
1849 right_width = 0;
1851 /* there is no left string, move right to left */
1852 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1853 (left_width > right_xpos)) {
1854 /* move the right string to the left string */
1855 format_align->left = format_align->right;
1856 /* calculate the new width and position of the string */
1857 left_width = right_width;
1858 /* there is no right string anymore */
1859 right_width = 0;
1862 ypos = (line * string_height);
1865 if (scroll && ((left_width > scroll_width) ||
1866 (center_width > scroll_width) ||
1867 (right_width > scroll_width)))
1869 display->puts_scroll(0, line,
1870 (unsigned char *)format_align->left);
1872 else
1874 #ifdef HAVE_LCD_BITMAP
1875 /* clear the line first */
1876 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1877 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1878 display->set_drawmode(DRMODE_SOLID);
1879 #endif
1881 /* Nasty hack: we output an empty scrolling string,
1882 which will reset the scroller for that line */
1883 display->puts_scroll(0, line, (unsigned char *)"");
1885 /* print aligned strings */
1886 if (left_width != 0)
1888 display->putsxy(left_xpos, ypos,
1889 (unsigned char *)format_align->left);
1891 if (center_width != 0)
1893 display->putsxy(center_xpos, ypos,
1894 (unsigned char *)format_align->center);
1896 if (right_width != 0)
1898 display->putsxy(right_xpos, ypos,
1899 (unsigned char *)format_align->right);
1904 /* Refresh the WPS according to refresh_mode. */
1905 bool gui_wps_refresh(struct gui_wps *gwps,
1906 int ffwd_offset,
1907 unsigned char refresh_mode)
1909 struct wps_data *data = gwps->data;
1910 struct screen *display = gwps->display;
1911 struct wps_state *state = gwps->state;
1913 if(!gwps || !data || !state || !display)
1914 return false;
1916 int v, line, i, subline_idx;
1917 unsigned char flags;
1918 char linebuf[MAX_PATH];
1919 unsigned char vp_refresh_mode;
1921 struct align_pos align;
1922 align.left = NULL;
1923 align.center = NULL;
1924 align.right = NULL;
1926 bool update_line, new_subline_refresh;
1928 #ifdef HAVE_LCD_BITMAP
1929 gui_wps_statusbar_draw(gwps, true);
1931 /* to find out wether the peak meter is enabled we
1932 assume it wasn't until we find a line that contains
1933 the peak meter. We can't use peak_meter_enabled itself
1934 because that would mean to turn off the meter thread
1935 temporarily. (That shouldn't matter unless yield
1936 or sleep is called but who knows...)
1938 bool enable_pm = false;
1940 #endif
1942 /* reset to first subline if refresh all flag is set */
1943 if (refresh_mode == WPS_REFRESH_ALL)
1945 display->set_viewport(&data->viewports[0].vp);
1946 display->clear_viewport();
1948 for (i = 0; i <= data->num_lines; i++)
1950 data->lines[i].curr_subline = SUBLINE_RESET;
1954 #ifdef HAVE_LCD_CHARCELLS
1955 for (i = 0; i < 8; i++)
1957 if (data->wps_progress_pat[i] == 0)
1958 data->wps_progress_pat[i] = display->get_locked_pattern();
1960 #endif
1962 if (!state->id3)
1964 display->stop_scroll();
1965 return false;
1968 state->ff_rewind_count = ffwd_offset;
1970 /* disable any viewports which are conditionally displayed */
1971 for (v = 0; v < data->num_viewports; v++)
1973 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
1975 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
1976 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1977 else
1978 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
1981 for (v = 0; v < data->num_viewports; v++)
1983 display->set_viewport(&data->viewports[v].vp);
1984 vp_refresh_mode = refresh_mode;
1986 #ifdef HAVE_LCD_BITMAP
1987 /* Set images to not to be displayed */
1988 for (i = 0; i < MAX_IMAGES; i++)
1990 data->img[i].display = -1;
1992 #endif
1993 /* dont redraw the viewport if its disabled */
1994 if ((data->viewports[v].hidden_flags&VP_DRAW_HIDDEN))
1996 if (!(data->viewports[v].hidden_flags&VP_DRAW_WASHIDDEN))
1997 display->scroll_stop(&data->viewports[v].vp);
1998 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1999 continue;
2001 else if (((data->viewports[v].hidden_flags&
2002 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2003 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2005 vp_refresh_mode = WPS_REFRESH_ALL;
2006 data->viewports[v].hidden_flags = VP_DRAW_HIDEABLE;
2008 if (vp_refresh_mode == WPS_REFRESH_ALL)
2010 display->clear_viewport();
2013 for (line = data->viewports[v].first_line;
2014 line <= data->viewports[v].last_line; line++)
2016 memset(linebuf, 0, sizeof(linebuf));
2017 update_line = false;
2019 /* get current subline for the line */
2020 new_subline_refresh = update_curr_subline(gwps, line);
2022 subline_idx = wps_subline_index(data, line,
2023 data->lines[line].curr_subline);
2024 flags = data->sublines[subline_idx].line_type;
2026 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2027 || new_subline_refresh)
2029 /* get_line tells us if we need to update the line */
2030 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2031 &align, linebuf, sizeof(linebuf));
2033 #ifdef HAVE_LCD_BITMAP
2034 /* peakmeter */
2035 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2037 /* the peakmeter should be alone on its line */
2038 update_line = false;
2040 int h = font_get(data->viewports[v].vp.font)->height;
2041 int peak_meter_y = (line - data->viewports[v].first_line)* h;
2043 /* The user might decide to have the peak meter in the last
2044 line so that it is only displayed if no status bar is
2045 visible. If so we neither want do draw nor enable the
2046 peak meter. */
2047 if (peak_meter_y + h <= display->getheight()) {
2048 /* found a line with a peak meter -> remember that we must
2049 enable it later */
2050 enable_pm = true;
2051 peak_meter_enabled = true;
2052 peak_meter_screen(gwps->display, 0, peak_meter_y,
2053 MIN(h, display->getheight() - peak_meter_y));
2055 else
2057 peak_meter_enabled = false;
2061 #else /* HAVE_LCD_CHARCELL */
2063 /* progressbar */
2064 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2066 if (data->full_line_progressbar)
2067 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2068 else
2069 draw_player_progress(gwps);
2071 #endif
2073 if (update_line &&
2074 /* conditionals clear the line which means if the %Vd is put into the default
2075 viewport there will be a blank line.
2076 To get around this we dont allow any actual drawing to happen in the
2077 deault vp if other vp's are defined */
2078 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2080 if (flags & WPS_REFRESH_SCROLL)
2082 /* if the line is a scrolling one we don't want to update
2083 too often, so that it has the time to scroll */
2084 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2085 write_line(display, &align, line - data->viewports[v].first_line, true);
2087 else
2088 write_line(display, &align, line - data->viewports[v].first_line, false);
2092 #ifdef HAVE_LCD_BITMAP
2093 /* progressbar */
2094 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2096 if (data->viewports[v].pb)
2097 draw_progressbar(gwps, data->viewports[v].pb);
2099 /* Now display any images in this viewport */
2100 wps_display_images(gwps, &data->viewports[v].vp);
2101 #endif
2104 #ifdef HAVE_LCD_BITMAP
2105 data->peak_meter_enabled = enable_pm;
2106 #endif
2108 /* Restore the default viewport */
2109 display->set_viewport(NULL);
2111 display->update();
2113 #ifdef HAVE_BACKLIGHT
2114 if (global_settings.caption_backlight && state->id3)
2116 /* turn on backlight n seconds before track ends, and turn it off n
2117 seconds into the new track. n == backlight_timeout, or 5s */
2118 int n = global_settings.backlight_timeout * 1000;
2120 if ( n < 1000 )
2121 n = 5000; /* use 5s if backlight is always on or off */
2123 if (((state->id3->elapsed < 1000) ||
2124 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2125 (state->paused == false))
2126 backlight_on();
2128 #endif
2129 #ifdef HAVE_REMOTE_LCD
2130 if (global_settings.remote_caption_backlight && state->id3)
2132 /* turn on remote backlight n seconds before track ends, and turn it
2133 off n seconds into the new track. n == remote_backlight_timeout,
2134 or 5s */
2135 int n = global_settings.remote_backlight_timeout * 1000;
2137 if ( n < 1000 )
2138 n = 5000; /* use 5s if backlight is always on or off */
2140 if (((state->id3->elapsed < 1000) ||
2141 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2142 (state->paused == false))
2143 remote_backlight_on();
2145 #endif
2147 return true;