Update Beast installation instructions to use beastpatcher and remove instructions...
[kugel-rb.git] / apps / gui / gwps-common.c
blob0d788769bb6291cbea8748e51022cc06567fbae6
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 if(!wps_state.id3 || !wps_state.id3->length
157 || global_settings.skip_length == 0)
158 return;
159 #define STEP ((unsigned)global_settings.skip_length*1000)
160 if(direction == 1
161 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
162 #if CONFIG_CODEC == SWCODEC
163 if(global_settings.beep)
164 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
165 #endif
166 return;
168 if((direction == -1 && wps_state.id3->elapsed < STEP))
169 wps_state.id3->elapsed = 0;
170 else
171 wps_state.id3->elapsed += STEP *direction;
172 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
173 #if (CONFIG_CODEC == SWCODEC)
174 audio_pre_ff_rewind();
175 #else
176 audio_pause();
177 #endif
179 audio_ff_rewind(wps_state.id3->elapsed);
180 #if (CONFIG_CODEC != SWCODEC)
181 if (!wps_state.paused)
182 audio_resume();
183 #endif
184 #undef STEP
187 bool ffwd_rew(int button)
189 unsigned int step = 0; /* current ff/rewind step */
190 unsigned int max_step = 0; /* maximum ff/rewind step */
191 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
192 int direction = -1; /* forward=1 or backward=-1 */
193 bool exit = false;
194 bool usb = false;
195 int i = 0;
196 const long ff_rw_accel = (global_settings.ff_rewind_accel + 3);
198 if (button == ACTION_NONE)
200 status_set_ffmode(0);
201 return usb;
203 while (!exit)
205 switch ( button )
207 case ACTION_WPS_SEEKFWD:
208 direction = 1;
209 case ACTION_WPS_SEEKBACK:
210 if (wps_state.ff_rewind)
212 if (direction == 1)
214 /* fast forwarding, calc max step relative to end */
215 max_step = (wps_state.id3->length -
216 (wps_state.id3->elapsed +
217 ff_rewind_count)) *
218 FF_REWIND_MAX_PERCENT / 100;
220 else
222 /* rewinding, calc max step relative to start */
223 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
224 FF_REWIND_MAX_PERCENT / 100;
227 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
229 if (step > max_step)
230 step = max_step;
232 ff_rewind_count += step * direction;
234 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
235 step += step >> ff_rw_accel;
237 else
239 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
240 wps_state.id3 && wps_state.id3->length )
242 if (!wps_state.paused)
243 #if (CONFIG_CODEC == SWCODEC)
244 audio_pre_ff_rewind();
245 #else
246 audio_pause();
247 #endif
248 #if CONFIG_KEYPAD == PLAYER_PAD
249 FOR_NB_SCREENS(i)
250 gui_wps[i].display->stop_scroll();
251 #endif
252 if (direction > 0)
253 status_set_ffmode(STATUS_FASTFORWARD);
254 else
255 status_set_ffmode(STATUS_FASTBACKWARD);
257 wps_state.ff_rewind = true;
259 step = 1000 * global_settings.ff_rewind_min_step;
261 else
262 break;
265 if (direction > 0) {
266 if ((wps_state.id3->elapsed + ff_rewind_count) >
267 wps_state.id3->length)
268 ff_rewind_count = wps_state.id3->length -
269 wps_state.id3->elapsed;
271 else {
272 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
273 ff_rewind_count = -wps_state.id3->elapsed;
276 FOR_NB_SCREENS(i)
277 gui_wps_refresh(&gui_wps[i],
278 (wps_state.wps_time_countup == false)?
279 ff_rewind_count:-ff_rewind_count,
280 WPS_REFRESH_PLAYER_PROGRESS |
281 WPS_REFRESH_DYNAMIC);
283 break;
285 case ACTION_WPS_STOPSEEK:
286 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
287 audio_ff_rewind(wps_state.id3->elapsed);
288 ff_rewind_count = 0;
289 wps_state.ff_rewind = false;
290 status_set_ffmode(0);
291 #if (CONFIG_CODEC != SWCODEC)
292 if (!wps_state.paused)
293 audio_resume();
294 #endif
295 #ifdef HAVE_LCD_CHARCELLS
296 gui_wps_display();
297 #endif
298 exit = true;
299 break;
301 default:
302 if(default_event_handler(button) == SYS_USB_CONNECTED) {
303 status_set_ffmode(0);
304 usb = true;
305 exit = true;
307 break;
309 if (!exit)
310 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
312 return usb;
315 bool gui_wps_display(void)
317 int i;
318 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
320 global_status.resume_index = -1;
321 splash(HZ, ID2P(LANG_END_PLAYLIST));
322 return true;
324 else
326 FOR_NB_SCREENS(i)
328 /* Update the values in the first (default) viewport - in case the user
329 has modified the statusbar or colour settings */
330 #ifdef HAVE_LCD_BITMAP
331 #if LCD_DEPTH > 1
332 if (gui_wps[i].display->depth > 1)
334 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
335 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
337 #endif
338 #endif
339 gui_wps[i].display->clear_display();
340 if (!gui_wps[i].data->wps_loaded) {
341 if ( !gui_wps[i].data->num_tokens ) {
342 /* set the default wps for the main-screen */
343 if(i == 0)
345 #ifdef HAVE_LCD_BITMAP
346 #if LCD_DEPTH > 1
347 unload_wps_backdrop();
348 #endif
349 wps_data_load(gui_wps[i].data,
350 gui_wps[i].display,
351 "%s%?it<%?in<%in. |>%it|%fn>\n"
352 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
353 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
354 "\n"
355 "%al%pc/%pt%ar[%pp:%pe]\n"
356 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
357 "%pb\n"
358 "%pm\n", false);
359 #else
360 wps_data_load(gui_wps[i].data,
361 gui_wps[i].display,
362 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
363 "%pc%?ps<*|/>%pt\n", false);
364 #endif
366 #if NB_SCREENS == 2
367 /* set the default wps for the remote-screen */
368 else if(i == 1)
370 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
371 unload_remote_wps_backdrop();
372 #endif
373 wps_data_load(gui_wps[i].data,
374 gui_wps[i].display,
375 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
376 "%s%?it<%?in<%in. |>%it|%fn>\n"
377 "%al%pc/%pt%ar[%pp:%pe]\n"
378 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
379 "%pb\n", false);
381 #endif
386 yield();
387 FOR_NB_SCREENS(i)
389 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
390 #ifdef HAVE_LCD_BITMAP
391 /* temporary work around so the statusbar doesnt disappear when the WPS
392 * is first entered. This should be removed when the
393 * WPS-statusbar handling is fixed up a bit more */
394 bool draw = global_settings.statusbar;
395 if (gui_wps[i].data->wps_sb_tag)
396 draw = gui_wps[i].data->show_sb_on_wps;
397 if (draw)
398 gui_statusbar_draw(&statusbars.statusbars[i], true);
399 #endif
401 return false;
404 bool update(struct gui_wps *gwps)
406 bool track_changed = audio_has_changed_track();
407 bool retcode = false;
409 gwps->state->nid3 = audio_next_track();
410 if (track_changed)
412 gwps->display->stop_scroll();
413 gwps->state->id3 = audio_current_track();
415 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
416 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
418 /* the current cuesheet isn't the right one any more */
419 /* We need to parse the new cuesheet */
421 char cuepath[MAX_PATH];
423 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
424 parse_cuesheet(cuepath, curr_cue))
426 gwps->state->id3->cuesheet_type = 1;
427 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
430 cue_spoof_id3(curr_cue, gwps->state->id3);
433 if (gui_wps_display())
434 retcode = true;
435 else{
436 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
440 if (gwps->state->id3)
442 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
443 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
444 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
445 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
447 /* We've changed tracks within the cuesheet :
448 we need to update the ID3 info and refresh the WPS */
450 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
451 cue_spoof_id3(curr_cue, gwps->state->id3);
453 gwps->display->stop_scroll();
454 if (gui_wps_display())
455 retcode = true;
456 else
457 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
459 else
460 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
463 return retcode;
467 void display_keylock_text(bool locked)
469 int i;
470 FOR_NB_SCREENS(i)
471 gui_wps[i].display->stop_scroll();
473 splash(HZ, locked ? ID2P(LANG_KEYLOCK_ON) : ID2P(LANG_KEYLOCK_OFF));
476 #ifdef HAVE_LCD_BITMAP
478 static void draw_progressbar(struct gui_wps *gwps,
479 struct wps_viewport *wps_vp)
481 struct screen *display = gwps->display;
482 struct wps_state *state = gwps->state;
483 struct progressbar *pb = wps_vp->pb;
484 int y = pb->y;
486 if (y < 0)
488 int line_height = font_get(wps_vp->vp.font)->height;
489 /* center the pb in the line, but only if the line is higher than the pb */
490 int center = (line_height-pb->height)/2;
491 /* if Y was not set calculate by font height,Y is -line_number-1 */
492 y = (-y -1)*line_height + (0 > center ? 0 : center);
495 if (pb->have_bitmap_pb)
496 gui_bitmap_scrollbar_draw(display, pb->bm,
497 pb->x, y, pb->width, pb->bm.height,
498 state->id3->length ? state->id3->length : 1, 0,
499 state->id3->length ? state->id3->elapsed
500 + state->ff_rewind_count : 0,
501 HORIZONTAL);
502 else
503 gui_scrollbar_draw(display, pb->x, y, pb->width, pb->height,
504 state->id3->length ? state->id3->length : 1, 0,
505 state->id3->length ? state->id3->elapsed
506 + state->ff_rewind_count : 0,
507 HORIZONTAL);
508 #ifdef AB_REPEAT_ENABLE
509 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
510 ab_draw_markers(display, state->id3->length,
511 pb->x, pb->x + pb->width, y, pb->height);
512 #endif
514 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
515 cue_draw_markers(display, state->id3->length,
516 pb->x, pb->x + pb->width, y+1, pb->height-2);
519 /* clears the area where the image was shown */
520 static void clear_image_pos(struct gui_wps *gwps, int n)
522 if(!gwps)
523 return;
524 struct wps_data *data = gwps->data;
525 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
526 gwps->display->fillrect(data->img[n].x, data->img[n].y,
527 data->img[n].bm.width, data->img[n].subimage_height);
528 gwps->display->set_drawmode(DRMODE_SOLID);
531 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
533 struct screen *display = gwps->display;
534 struct wps_data *data = gwps->data;
535 if(data->img[n].always_display)
536 display->set_drawmode(DRMODE_FG);
537 else
538 display->set_drawmode(DRMODE_SOLID);
540 #if LCD_DEPTH > 1
541 if(data->img[n].bm.format == FORMAT_MONO) {
542 #endif
543 display->mono_bitmap_part(data->img[n].bm.data,
544 0, data->img[n].subimage_height * subimage,
545 data->img[n].bm.width, data->img[n].x,
546 data->img[n].y, data->img[n].bm.width,
547 data->img[n].subimage_height);
548 #if LCD_DEPTH > 1
549 } else {
550 display->transparent_bitmap_part((fb_data *)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);
556 #endif
559 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
561 if(!gwps || !gwps->data || !gwps->display)
562 return;
564 int n;
565 struct wps_data *data = gwps->data;
566 struct screen *display = gwps->display;
568 for (n = 0; n < MAX_IMAGES; n++)
570 if (data->img[n].loaded)
572 if (data->img[n].display >= 0)
574 wps_draw_image(gwps, n, data->img[n].display);
575 } else if (data->img[n].always_display && data->img[n].vp == vp)
577 wps_draw_image(gwps, n, 0);
581 display->set_drawmode(DRMODE_SOLID);
584 #else /* HAVE_LCD_CHARCELL */
586 static bool draw_player_progress(struct gui_wps *gwps)
588 struct wps_state *state = gwps->state;
589 struct screen *display = gwps->display;
590 unsigned char progress_pattern[7];
591 int pos = 0;
592 int i;
594 if (!state->id3)
595 return false;
597 if (state->id3->length)
598 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
599 / state->id3->length;
601 for (i = 0; i < 7; i++, pos -= 5)
603 if (pos <= 0)
604 progress_pattern[i] = 0x1fu;
605 else if (pos >= 5)
606 progress_pattern[i] = 0x00u;
607 else
608 progress_pattern[i] = 0x1fu >> pos;
611 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
612 return true;
615 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
617 static const unsigned char numbers[10][4] = {
618 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
619 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
620 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
621 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
622 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
623 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
624 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
625 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
626 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
627 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
630 struct wps_state *state = gwps->state;
631 struct screen *display = gwps->display;
632 struct wps_data *data = gwps->data;
633 unsigned char progress_pattern[7];
634 char timestr[10];
635 int time;
636 int time_idx = 0;
637 int pos = 0;
638 int pat_idx = 1;
639 int digit, i, j;
640 bool softchar;
642 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
643 return;
645 time = state->id3->elapsed + state->ff_rewind_count;
646 if (state->id3->length)
647 pos = 55 * time / state->id3->length;
649 memset(timestr, 0, sizeof(timestr));
650 format_time(timestr, sizeof(timestr)-2, time);
651 timestr[strlen(timestr)] = ':'; /* always safe */
653 for (i = 0; i < 11; i++, pos -= 5)
655 softchar = false;
656 memset(progress_pattern, 0, sizeof(progress_pattern));
658 if ((digit = timestr[time_idx]))
660 softchar = true;
661 digit -= '0';
663 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
665 memcpy(progress_pattern, numbers[digit], 4);
666 time_idx += 2;
668 else /* tens, shifted right */
670 for (j = 0; j < 4; j++)
671 progress_pattern[j] = numbers[digit][j] >> 1;
673 if (time_idx > 0) /* not the first group, add colon in front */
675 progress_pattern[1] |= 0x10u;
676 progress_pattern[3] |= 0x10u;
678 time_idx++;
681 if (pos >= 5)
682 progress_pattern[5] = progress_pattern[6] = 0x1fu;
685 if (pos > 0 && pos < 5)
687 softchar = true;
688 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
691 if (softchar && pat_idx < 8)
693 display->define_pattern(data->wps_progress_pat[pat_idx],
694 progress_pattern);
695 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
696 pat_idx++;
698 else if (pos <= 0)
699 buf = utf8encode(' ', buf);
700 else
701 buf = utf8encode(0xe115, buf); /* 2/7 _ */
703 *buf = '\0';
706 #endif /* HAVE_LCD_CHARCELL */
708 static char* get_codectype(const struct mp3entry* id3)
710 if (id3->codectype < AFMT_NUM_CODECS) {
711 return (char*)audio_formats[id3->codectype].label;
712 } else {
713 return NULL;
717 /* Extract a part from a path.
719 * buf - buffer extract part to.
720 * buf_size - size of buffer.
721 * path - path to extract from.
722 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
723 * parent of parent, etc.
725 * Returns buf if the desired level was found, NULL otherwise.
727 static char* get_dir(char* buf, int buf_size, const char* path, int level)
729 const char* sep;
730 const char* last_sep;
731 int len;
733 sep = path + strlen(path);
734 last_sep = sep;
736 while (sep > path)
738 if ('/' == *(--sep))
740 if (!level)
741 break;
743 level--;
744 last_sep = sep - 1;
748 if (level || (last_sep <= sep))
749 return NULL;
751 len = MIN(last_sep - sep, buf_size - 1);
752 strncpy(buf, sep + 1, len);
753 buf[len] = 0;
754 return buf;
757 /* Return the tag found at index i and write its value in buf.
758 The return value is buf if the tag had a value, or NULL if not.
760 intval is used with conditionals/enums: when this function is called,
761 intval should contain the number of options in the conditional/enum.
762 When this function returns, intval is -1 if the tag is non numeric or,
763 if the tag is numeric, *intval is the enum case we want to go to (between 1
764 and the original value of *intval, inclusive).
765 When not treating a conditional/enum, intval should be NULL.
767 static const char *get_token_value(struct gui_wps *gwps,
768 struct wps_token *token,
769 char *buf, int buf_size,
770 int *intval)
772 if (!gwps)
773 return NULL;
775 struct wps_data *data = gwps->data;
776 struct wps_state *state = gwps->state;
778 if (!data || !state)
779 return NULL;
781 struct mp3entry *id3;
783 if (token->next)
784 id3 = state->nid3;
785 else
786 id3 = state->id3;
788 if (!id3)
789 return NULL;
791 #if CONFIG_RTC
792 struct tm* tm = NULL;
794 /* if the token is an RTC one, update the time
795 and do the necessary checks */
796 if (token->type >= WPS_TOKENS_RTC_BEGIN
797 && token->type <= WPS_TOKENS_RTC_END)
799 tm = get_time();
801 if (!valid_time(tm))
802 return NULL;
804 #endif
806 int limit = 1;
807 if (intval)
809 limit = *intval;
810 *intval = -1;
813 switch (token->type)
815 case WPS_TOKEN_CHARACTER:
816 return &(token->value.c);
818 case WPS_TOKEN_STRING:
819 return data->strings[token->value.i];
821 case WPS_TOKEN_TRACK_TIME_ELAPSED:
822 format_time(buf, buf_size,
823 id3->elapsed + state->ff_rewind_count);
824 return buf;
826 case WPS_TOKEN_TRACK_TIME_REMAINING:
827 format_time(buf, buf_size,
828 id3->length - id3->elapsed -
829 state->ff_rewind_count);
830 return buf;
832 case WPS_TOKEN_TRACK_LENGTH:
833 format_time(buf, buf_size, id3->length);
834 return buf;
836 case WPS_TOKEN_PLAYLIST_ENTRIES:
837 snprintf(buf, buf_size, "%d", playlist_amount());
838 return buf;
840 case WPS_TOKEN_PLAYLIST_NAME:
841 return playlist_name(NULL, buf, buf_size);
843 case WPS_TOKEN_PLAYLIST_POSITION:
844 snprintf(buf, buf_size, "%d", playlist_get_display_index());
845 return buf;
847 case WPS_TOKEN_PLAYLIST_SHUFFLE:
848 if ( global_settings.playlist_shuffle )
849 return "s";
850 else
851 return NULL;
852 break;
854 case WPS_TOKEN_VOLUME:
855 snprintf(buf, buf_size, "%d", global_settings.volume);
856 if (intval)
858 if (global_settings.volume == sound_min(SOUND_VOLUME))
860 *intval = 1;
862 else if (global_settings.volume == 0)
864 *intval = limit - 1;
866 else if (global_settings.volume > 0)
868 *intval = limit;
870 else
872 *intval = (limit - 3) * (global_settings.volume
873 - sound_min(SOUND_VOLUME) - 1)
874 / (-1 - sound_min(SOUND_VOLUME)) + 2;
877 return buf;
879 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
880 if (id3->length <= 0)
881 return NULL;
883 if (intval)
885 *intval = limit * (id3->elapsed + state->ff_rewind_count)
886 / id3->length + 1;
888 snprintf(buf, buf_size, "%d",
889 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
890 return buf;
892 case WPS_TOKEN_METADATA_ARTIST:
893 return id3->artist;
895 case WPS_TOKEN_METADATA_COMPOSER:
896 return id3->composer;
898 case WPS_TOKEN_METADATA_ALBUM:
899 return id3->album;
901 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
902 return id3->albumartist;
904 case WPS_TOKEN_METADATA_GROUPING:
905 return id3->grouping;
907 case WPS_TOKEN_METADATA_GENRE:
908 return id3->genre_string;
910 case WPS_TOKEN_METADATA_DISC_NUMBER:
911 if (id3->disc_string)
912 return id3->disc_string;
913 if (id3->discnum) {
914 snprintf(buf, buf_size, "%d", id3->discnum);
915 return buf;
917 return NULL;
919 case WPS_TOKEN_METADATA_TRACK_NUMBER:
920 if (id3->track_string)
921 return id3->track_string;
923 if (id3->tracknum) {
924 snprintf(buf, buf_size, "%d", id3->tracknum);
925 return buf;
927 return NULL;
929 case WPS_TOKEN_METADATA_TRACK_TITLE:
930 return id3->title;
932 case WPS_TOKEN_METADATA_VERSION:
933 switch (id3->id3version)
935 case ID3_VER_1_0:
936 return "1";
938 case ID3_VER_1_1:
939 return "1.1";
941 case ID3_VER_2_2:
942 return "2.2";
944 case ID3_VER_2_3:
945 return "2.3";
947 case ID3_VER_2_4:
948 return "2.4";
950 default:
951 return NULL;
954 case WPS_TOKEN_METADATA_YEAR:
955 if( id3->year_string )
956 return id3->year_string;
958 if (id3->year) {
959 snprintf(buf, buf_size, "%d", id3->year);
960 return buf;
962 return NULL;
964 case WPS_TOKEN_METADATA_COMMENT:
965 return id3->comment;
967 #ifdef HAVE_ALBUMART
968 case WPS_TOKEN_ALBUMART_DISPLAY:
969 draw_album_art(gwps, audio_current_aa_hid(), false);
970 return NULL;
972 case WPS_TOKEN_ALBUMART_FOUND:
973 if (audio_current_aa_hid() >= 0) {
974 return "C";
976 return NULL;
977 #endif
979 case WPS_TOKEN_FILE_BITRATE:
980 if(id3->bitrate)
981 snprintf(buf, buf_size, "%d", id3->bitrate);
982 else
983 return "?";
984 return buf;
986 case WPS_TOKEN_FILE_CODEC:
987 if (intval)
989 if(id3->codectype == AFMT_UNKNOWN)
990 *intval = AFMT_NUM_CODECS;
991 else
992 *intval = id3->codectype;
994 return get_codectype(id3);
996 case WPS_TOKEN_FILE_FREQUENCY:
997 snprintf(buf, buf_size, "%ld", id3->frequency);
998 return buf;
1000 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
1001 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1002 if ((id3->frequency % 1000) < 100)
1003 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1004 else
1005 snprintf(buf, buf_size, "%ld.%d",
1006 id3->frequency / 1000,
1007 (id3->frequency % 1000) / 100);
1008 return buf;
1010 case WPS_TOKEN_FILE_NAME:
1011 if (get_dir(buf, buf_size, id3->path, 0)) {
1012 /* Remove extension */
1013 char* sep = strrchr(buf, '.');
1014 if (NULL != sep) {
1015 *sep = 0;
1017 return buf;
1019 else {
1020 return NULL;
1023 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1024 return get_dir(buf, buf_size, id3->path, 0);
1026 case WPS_TOKEN_FILE_PATH:
1027 return id3->path;
1029 case WPS_TOKEN_FILE_SIZE:
1030 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1031 return buf;
1033 case WPS_TOKEN_FILE_VBR:
1034 return id3->vbr ? "(avg)" : NULL;
1036 case WPS_TOKEN_FILE_DIRECTORY:
1037 return get_dir(buf, buf_size, id3->path, token->value.i);
1039 case WPS_TOKEN_BATTERY_PERCENT:
1041 int l = battery_level();
1043 if (intval)
1045 limit = MAX(limit, 2);
1046 if (l > -1) {
1047 /* First enum is used for "unknown level". */
1048 *intval = (limit - 1) * l / 100 + 2;
1049 } else {
1050 *intval = 1;
1054 if (l > -1) {
1055 snprintf(buf, buf_size, "%d", l);
1056 return buf;
1057 } else {
1058 return "?";
1062 case WPS_TOKEN_BATTERY_VOLTS:
1064 unsigned int v = battery_voltage();
1065 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1066 return buf;
1069 case WPS_TOKEN_BATTERY_TIME:
1071 int t = battery_time();
1072 if (t >= 0)
1073 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1074 else
1075 return "?h ?m";
1076 return buf;
1079 #if CONFIG_CHARGING
1080 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1082 if(charger_input_state==CHARGER)
1083 return "p";
1084 else
1085 return NULL;
1087 #endif
1088 #if CONFIG_CHARGING >= CHARGING_MONITOR
1089 case WPS_TOKEN_BATTERY_CHARGING:
1091 if (charge_state == CHARGING || charge_state == TOPOFF) {
1092 return "c";
1093 } else {
1094 return NULL;
1097 #endif
1098 case WPS_TOKEN_BATTERY_SLEEPTIME:
1100 if (get_sleep_timer() == 0)
1101 return NULL;
1102 else
1104 format_time(buf, buf_size, get_sleep_timer() * 1000);
1105 return buf;
1109 case WPS_TOKEN_PLAYBACK_STATUS:
1111 int status = audio_status();
1112 int mode = 1;
1113 if (status == AUDIO_STATUS_PLAY)
1114 mode = 2;
1115 if (wps_fading_out ||
1116 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1117 mode = 3;
1118 if (status_get_ffmode() == STATUS_FASTFORWARD)
1119 mode = 4;
1120 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1121 mode = 5;
1123 if (intval) {
1124 *intval = mode;
1127 snprintf(buf, buf_size, "%d", mode-1);
1128 return buf;
1131 case WPS_TOKEN_REPEAT_MODE:
1132 if (intval)
1133 *intval = global_settings.repeat_mode + 1;
1134 snprintf(buf, buf_size, "%d", global_settings.repeat_mode);
1135 return buf;
1136 #if CONFIG_RTC
1137 case WPS_TOKEN_RTC_12HOUR_CFG:
1138 if (intval)
1139 *intval = global_settings.timeformat + 1;
1140 snprintf(buf, buf_size, "%d", global_settings.timeformat);
1141 return buf;
1143 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1144 /* d: day of month (01..31) */
1145 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1146 return buf;
1148 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1149 /* e: day of month, blank padded ( 1..31) */
1150 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1151 return buf;
1153 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1154 /* H: hour (00..23) */
1155 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1156 return buf;
1158 case WPS_TOKEN_RTC_HOUR_24:
1159 /* k: hour ( 0..23) */
1160 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1161 return buf;
1163 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1164 /* I: hour (01..12) */
1165 snprintf(buf, buf_size, "%02d",
1166 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1167 return buf;
1169 case WPS_TOKEN_RTC_HOUR_12:
1170 /* l: hour ( 1..12) */
1171 snprintf(buf, buf_size, "%2d",
1172 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1173 return buf;
1175 case WPS_TOKEN_RTC_MONTH:
1176 /* m: month (01..12) */
1177 if (intval)
1178 *intval = tm->tm_mon + 1;
1179 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1180 return buf;
1182 case WPS_TOKEN_RTC_MINUTE:
1183 /* M: minute (00..59) */
1184 snprintf(buf, buf_size, "%02d", tm->tm_min);
1185 return buf;
1187 case WPS_TOKEN_RTC_SECOND:
1188 /* S: second (00..59) */
1189 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1190 return buf;
1192 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1193 /* y: last two digits of year (00..99) */
1194 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1195 return buf;
1197 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1198 /* Y: year (1970...) */
1199 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1200 return buf;
1202 case WPS_TOKEN_RTC_AM_PM_UPPER:
1203 /* p: upper case AM or PM indicator */
1204 return tm->tm_hour/12 == 0 ? "AM" : "PM";
1206 case WPS_TOKEN_RTC_AM_PM_LOWER:
1207 /* P: lower case am or pm indicator */
1208 return tm->tm_hour/12 == 0 ? "am" : "pm";
1210 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1211 /* a: abbreviated weekday name (Sun..Sat) */
1212 return str(LANG_WEEKDAY_SUNDAY + tm->tm_wday);
1214 case WPS_TOKEN_RTC_MONTH_NAME:
1215 /* b: abbreviated month name (Jan..Dec) */
1216 return str(LANG_MONTH_JANUARY + tm->tm_mon);
1218 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1219 /* u: day of week (1..7); 1 is Monday */
1220 if (intval)
1221 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1222 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1223 return buf;
1225 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1226 /* w: day of week (0..6); 0 is Sunday */
1227 if (intval)
1228 *intval = tm->tm_wday + 1;
1229 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1230 return buf;
1231 #else
1232 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1233 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1234 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1235 case WPS_TOKEN_RTC_HOUR_24:
1236 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1237 case WPS_TOKEN_RTC_HOUR_12:
1238 case WPS_TOKEN_RTC_MONTH:
1239 case WPS_TOKEN_RTC_MINUTE:
1240 case WPS_TOKEN_RTC_SECOND:
1241 case WPS_TOKEN_RTC_AM_PM_UPPER:
1242 case WPS_TOKEN_RTC_AM_PM_LOWER:
1243 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1244 return "--";
1245 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1246 return "----";
1247 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1248 case WPS_TOKEN_RTC_MONTH_NAME:
1249 return "---";
1250 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1251 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1252 return "-";
1253 #endif
1255 #ifdef HAVE_LCD_CHARCELLS
1256 case WPS_TOKEN_PROGRESSBAR:
1258 char *end = utf8encode(data->wps_progress_pat[0], buf);
1259 *end = '\0';
1260 return buf;
1263 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1264 if(is_new_player())
1266 /* we need 11 characters (full line) for
1267 progress-bar */
1268 strncpy(buf, " ", buf_size);
1270 else
1272 /* Tell the user if we have an OldPlayer */
1273 strncpy(buf, " <Old LCD> ", buf_size);
1275 return buf;
1276 #endif
1278 #ifdef HAVE_TAGCACHE
1279 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1280 if (intval) {
1281 *intval = id3->playcount + 1;
1283 snprintf(buf, buf_size, "%ld", id3->playcount);
1284 return buf;
1286 case WPS_TOKEN_DATABASE_RATING:
1287 if (intval) {
1288 *intval = id3->rating + 1;
1290 snprintf(buf, buf_size, "%d", id3->rating);
1291 return buf;
1293 case WPS_TOKEN_DATABASE_AUTOSCORE:
1294 if (intval)
1295 *intval = id3->score + 1;
1297 snprintf(buf, buf_size, "%d", id3->score);
1298 return buf;
1299 #endif
1301 #if (CONFIG_CODEC == SWCODEC)
1302 case WPS_TOKEN_CROSSFADE:
1303 if (intval)
1304 *intval = global_settings.crossfade + 1;
1305 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1306 return buf;
1308 case WPS_TOKEN_REPLAYGAIN:
1310 int val;
1312 if (global_settings.replaygain == 0)
1313 val = 1; /* off */
1314 else
1316 int type =
1317 get_replaygain_mode(id3->track_gain_string != NULL,
1318 id3->album_gain_string != NULL);
1319 if (type < 0)
1320 val = 6; /* no tag */
1321 else
1322 val = type + 2;
1324 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1325 val += 2;
1328 if (intval)
1329 *intval = val;
1331 switch (val)
1333 case 1:
1334 case 6:
1335 return "+0.00 dB";
1336 break;
1337 case 2:
1338 case 4:
1339 strncpy(buf, id3->track_gain_string, buf_size);
1340 break;
1341 case 3:
1342 case 5:
1343 strncpy(buf, id3->album_gain_string, buf_size);
1344 break;
1346 return buf;
1348 #endif /* (CONFIG_CODEC == SWCODEC) */
1350 #if (CONFIG_CODEC != MAS3507D)
1351 case WPS_TOKEN_SOUND_PITCH:
1353 int val = sound_get_pitch();
1354 snprintf(buf, buf_size, "%d.%d",
1355 val / 10, val % 10);
1356 return buf;
1358 #endif
1360 case WPS_TOKEN_MAIN_HOLD:
1361 #ifdef HAS_BUTTON_HOLD
1362 if (button_hold())
1363 #else
1364 if (is_keys_locked())
1365 #endif /*hold switch or softlock*/
1366 return "h";
1367 else
1368 return NULL;
1370 #ifdef HAS_REMOTE_BUTTON_HOLD
1371 case WPS_TOKEN_REMOTE_HOLD:
1372 if (remote_button_hold())
1373 return "r";
1374 else
1375 return NULL;
1376 #endif
1378 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1379 case WPS_TOKEN_VLED_HDD:
1380 if(led_read(HZ/2))
1381 return "h";
1382 else
1383 return NULL;
1384 #endif
1385 case WPS_TOKEN_BUTTON_VOLUME:
1386 if (data->button_time_volume &&
1387 TIME_BEFORE(current_tick, data->button_time_volume +
1388 token->value.i * TIMEOUT_UNIT))
1389 return "v";
1390 return NULL;
1392 case WPS_TOKEN_SETTING:
1394 if (intval)
1396 /* Handle contionals */
1397 const struct settings_list *s = settings+token->value.i;
1398 switch (s->flags&F_T_MASK)
1400 case F_T_INT:
1401 case F_T_UINT:
1402 if (s->flags&F_RGB)
1403 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1404 /* shouldn't overflow since colors are stored
1405 * on 16 bits ...
1406 * but this is pretty useless anyway */
1407 *intval = *(int*)s->setting + 1;
1408 else if (s->cfg_vals == NULL)
1409 /* %?St|name|<1st choice|2nd choice|...> */
1410 *intval = (*(int*)s->setting-s->int_setting->min)
1411 /s->int_setting->step + 1;
1412 else
1413 /* %?St|name|<1st choice|2nd choice|...> */
1414 /* Not sure about this one. cfg_name/vals are
1415 * indexed from 0 right? */
1416 *intval = *(int*)s->setting + 1;
1417 break;
1418 case F_T_BOOL:
1419 /* %?St|name|<if true|if false> */
1420 *intval = *(bool*)s->setting?1:2;
1421 break;
1422 case F_T_CHARPTR:
1423 /* %?St|name|<if non empty string|if empty>
1424 * The string's emptyness discards the setting's
1425 * prefix and suffix */
1426 *intval = ((char*)s->setting)[0]?1:2;
1427 break;
1428 default:
1429 /* This shouldn't happen ... but you never know */
1430 *intval = -1;
1431 break;
1434 cfg_to_string(token->value.i,buf,buf_size);
1435 return buf;
1438 default:
1439 return NULL;
1443 /* Return the index to the end token for the conditional token at index.
1444 The conditional token can be either a start token or a separator
1445 (i.e. option) token.
1447 static int find_conditional_end(struct wps_data *data, int index)
1449 int ret = index;
1450 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1451 ret = data->tokens[ret].value.i;
1453 /* ret now is the index to the end token for the conditional. */
1454 return ret;
1457 /* Evaluate the conditional that is at *token_index and return whether a skip
1458 has ocurred. *token_index is updated with the new position.
1460 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1462 if (!gwps)
1463 return false;
1465 struct wps_data *data = gwps->data;
1467 int i, cond_end;
1468 int cond_index = *token_index;
1469 char result[128];
1470 const char *value;
1471 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1472 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1474 /* treat ?xx<true> constructs as if they had 2 options. */
1475 if (num_options < 2)
1476 num_options = 2;
1478 int intval = num_options;
1479 /* get_token_value needs to know the number of options in the enum */
1480 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1481 result, sizeof(result), &intval);
1483 /* intval is now the number of the enum option we want to read,
1484 starting from 1. If intval is -1, we check if value is empty. */
1485 if (intval == -1)
1486 intval = (value && *value) ? 1 : num_options;
1487 else if (intval > num_options || intval < 1)
1488 intval = num_options;
1490 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1492 /* skip to the appropriate enum case */
1493 int next = cond_index + 2;
1494 for (i = 1; i < intval; i++)
1496 next = data->tokens[next].value.i;
1498 *token_index = next;
1500 if (prev_val == intval)
1502 /* Same conditional case as previously. Return without clearing the
1503 pictures */
1504 return false;
1507 cond_end = find_conditional_end(data, cond_index + 2);
1508 for (i = cond_index + 3; i < cond_end; i++)
1510 #ifdef HAVE_LCD_BITMAP
1511 /* clear all pictures in the conditional and nested ones */
1512 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1513 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1514 #endif
1515 #ifdef HAVE_ALBUMART
1516 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1517 draw_album_art(gwps, audio_current_aa_hid(), true);
1518 #endif
1521 return true;
1524 /* Read a (sub)line to the given alignment format buffer.
1525 linebuf is the buffer where the data is actually stored.
1526 align is the alignment format that'll be used to display the text.
1527 The return value indicates whether the line needs to be updated.
1529 static bool get_line(struct gui_wps *gwps,
1530 int line, int subline,
1531 struct align_pos *align,
1532 char *linebuf,
1533 int linebuf_size)
1535 struct wps_data *data = gwps->data;
1537 char temp_buf[128];
1538 char *buf = linebuf; /* will always point to the writing position */
1539 char *linebuf_end = linebuf + linebuf_size - 1;
1540 int i, last_token_idx;
1541 bool update = false;
1543 /* alignment-related variables */
1544 int cur_align;
1545 char* cur_align_start;
1546 cur_align_start = buf;
1547 cur_align = WPS_ALIGN_LEFT;
1548 align->left = NULL;
1549 align->center = NULL;
1550 align->right = NULL;
1552 /* Process all tokens of the desired subline */
1553 last_token_idx = wps_last_token_index(data, line, subline);
1554 for (i = wps_first_token_index(data, line, subline);
1555 i <= last_token_idx; i++)
1557 switch(data->tokens[i].type)
1559 case WPS_TOKEN_CONDITIONAL:
1560 /* place ourselves in the right conditional case */
1561 update |= evaluate_conditional(gwps, &i);
1562 break;
1564 case WPS_TOKEN_CONDITIONAL_OPTION:
1565 /* we've finished in the curent conditional case,
1566 skip to the end of the conditional structure */
1567 i = find_conditional_end(data, i);
1568 break;
1570 #ifdef HAVE_LCD_BITMAP
1571 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1573 struct gui_img *img = data->img;
1574 int n = data->tokens[i].value.i & 0xFF;
1575 int subimage = data->tokens[i].value.i >> 8;
1577 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1578 img[n].display = subimage;
1579 break;
1581 #endif
1583 case WPS_TOKEN_ALIGN_LEFT:
1584 case WPS_TOKEN_ALIGN_CENTER:
1585 case WPS_TOKEN_ALIGN_RIGHT:
1586 /* remember where the current aligned text started */
1587 switch (cur_align)
1589 case WPS_ALIGN_LEFT:
1590 align->left = cur_align_start;
1591 break;
1593 case WPS_ALIGN_CENTER:
1594 align->center = cur_align_start;
1595 break;
1597 case WPS_ALIGN_RIGHT:
1598 align->right = cur_align_start;
1599 break;
1601 /* start a new alignment */
1602 switch (data->tokens[i].type)
1604 case WPS_TOKEN_ALIGN_LEFT:
1605 cur_align = WPS_ALIGN_LEFT;
1606 break;
1607 case WPS_TOKEN_ALIGN_CENTER:
1608 cur_align = WPS_ALIGN_CENTER;
1609 break;
1610 case WPS_TOKEN_ALIGN_RIGHT:
1611 cur_align = WPS_ALIGN_RIGHT;
1612 break;
1613 default:
1614 break;
1616 *buf++ = 0;
1617 cur_align_start = buf;
1618 break;
1619 case WPS_VIEWPORT_ENABLE:
1621 char label = data->tokens[i].value.i;
1622 int j;
1623 char temp = VP_DRAW_HIDEABLE;
1624 for(j=0;j<data->num_viewports;j++)
1626 temp = VP_DRAW_HIDEABLE;
1627 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1628 (data->viewports[j].label == label))
1630 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1631 temp |= VP_DRAW_WASHIDDEN;
1632 data->viewports[j].hidden_flags = temp;
1636 break;
1637 default:
1639 /* get the value of the tag and copy it to the buffer */
1640 const char *value = get_token_value(gwps, &data->tokens[i],
1641 temp_buf, sizeof(temp_buf), NULL);
1642 if (value)
1644 update = true;
1645 while (*value && (buf < linebuf_end))
1646 *buf++ = *value++;
1648 break;
1653 /* close the current alignment */
1654 switch (cur_align)
1656 case WPS_ALIGN_LEFT:
1657 align->left = cur_align_start;
1658 break;
1660 case WPS_ALIGN_CENTER:
1661 align->center = cur_align_start;
1662 break;
1664 case WPS_ALIGN_RIGHT:
1665 align->right = cur_align_start;
1666 break;
1669 return update;
1672 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1674 struct wps_data *data = gwps->data;
1675 int i;
1676 int subline_idx = wps_subline_index(data, line, subline);
1677 int last_token_idx = wps_last_token_index(data, line, subline);
1679 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1681 for (i = wps_first_token_index(data, line, subline);
1682 i <= last_token_idx; i++)
1684 switch(data->tokens[i].type)
1686 case WPS_TOKEN_CONDITIONAL:
1687 /* place ourselves in the right conditional case */
1688 evaluate_conditional(gwps, &i);
1689 break;
1691 case WPS_TOKEN_CONDITIONAL_OPTION:
1692 /* we've finished in the curent conditional case,
1693 skip to the end of the conditional structure */
1694 i = find_conditional_end(data, i);
1695 break;
1697 case WPS_TOKEN_SUBLINE_TIMEOUT:
1698 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1699 break;
1701 default:
1702 break;
1707 /* Calculates which subline should be displayed for the specified line
1708 Returns true iff the subline must be refreshed */
1709 static bool update_curr_subline(struct gui_wps *gwps, int line)
1711 struct wps_data *data = gwps->data;
1713 int search, search_start, num_sublines;
1714 bool reset_subline;
1715 bool new_subline_refresh;
1716 bool only_one_subline;
1718 num_sublines = data->lines[line].num_sublines;
1719 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1720 new_subline_refresh = false;
1721 only_one_subline = false;
1723 /* if time to advance to next sub-line */
1724 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1725 reset_subline)
1727 /* search all sublines until the next subline with time > 0
1728 is found or we get back to the subline we started with */
1729 if (reset_subline)
1730 search_start = 0;
1731 else
1732 search_start = data->lines[line].curr_subline;
1734 for (search = 0; search < num_sublines; search++)
1736 data->lines[line].curr_subline++;
1738 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1739 if (data->lines[line].curr_subline == num_sublines)
1741 if (data->lines[line].curr_subline == 1)
1742 only_one_subline = true;
1743 data->lines[line].curr_subline = 0;
1746 /* if back where we started after search or
1747 only one subline is defined on the line */
1748 if (((search > 0) &&
1749 (data->lines[line].curr_subline == search_start)) ||
1750 only_one_subline)
1752 /* no other subline with a time > 0 exists */
1753 data->lines[line].subline_expire_time = (reset_subline ?
1754 current_tick :
1755 data->lines[line].subline_expire_time) + 100 * HZ;
1756 break;
1758 else
1760 /* get initial time multiplier for this subline */
1761 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1763 int subline_idx = wps_subline_index(data, line,
1764 data->lines[line].curr_subline);
1766 /* only use this subline if subline time > 0 */
1767 if (data->sublines[subline_idx].time_mult > 0)
1769 new_subline_refresh = true;
1770 data->lines[line].subline_expire_time = (reset_subline ?
1771 current_tick : data->lines[line].subline_expire_time) +
1772 TIMEOUT_UNIT*data->sublines[subline_idx].time_mult;
1773 break;
1779 return new_subline_refresh;
1782 /* Display a line appropriately according to its alignment format.
1783 format_align contains the text, separated between left, center and right.
1784 line is the index of the line on the screen.
1785 scroll indicates whether the line is a scrolling one or not.
1787 static void write_line(struct screen *display,
1788 struct align_pos *format_align,
1789 int line,
1790 bool scroll)
1793 int left_width = 0, left_xpos;
1794 int center_width = 0, center_xpos;
1795 int right_width = 0, right_xpos;
1796 int ypos;
1797 int space_width;
1798 int string_height;
1799 int scroll_width;
1801 /* calculate different string sizes and positions */
1802 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1803 if (format_align->left != 0) {
1804 display->getstringsize((unsigned char *)format_align->left,
1805 &left_width, &string_height);
1808 if (format_align->right != 0) {
1809 display->getstringsize((unsigned char *)format_align->right,
1810 &right_width, &string_height);
1813 if (format_align->center != 0) {
1814 display->getstringsize((unsigned char *)format_align->center,
1815 &center_width, &string_height);
1818 left_xpos = 0;
1819 right_xpos = (display->getwidth() - right_width);
1820 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1822 scroll_width = display->getwidth() - left_xpos;
1824 /* Checks for overlapping strings.
1825 If needed the overlapping strings will be merged, separated by a
1826 space */
1828 /* CASE 1: left and centered string overlap */
1829 /* there is a left string, need to merge left and center */
1830 if ((left_width != 0 && center_width != 0) &&
1831 (left_xpos + left_width + space_width > center_xpos)) {
1832 /* replace the former separator '\0' of left and
1833 center string with a space */
1834 *(--format_align->center) = ' ';
1835 /* calculate the new width and position of the merged string */
1836 left_width = left_width + space_width + center_width;
1837 /* there is no centered string anymore */
1838 center_width = 0;
1840 /* there is no left string, move center to left */
1841 if ((left_width == 0 && center_width != 0) &&
1842 (left_xpos + left_width > center_xpos)) {
1843 /* move the center string to the left string */
1844 format_align->left = format_align->center;
1845 /* calculate the new width and position of the string */
1846 left_width = center_width;
1847 /* there is no centered string anymore */
1848 center_width = 0;
1851 /* CASE 2: centered and right string overlap */
1852 /* there is a right string, need to merge center and right */
1853 if ((center_width != 0 && right_width != 0) &&
1854 (center_xpos + center_width + space_width > right_xpos)) {
1855 /* replace the former separator '\0' of center and
1856 right string with a space */
1857 *(--format_align->right) = ' ';
1858 /* move the center string to the right after merge */
1859 format_align->right = format_align->center;
1860 /* calculate the new width and position of the merged string */
1861 right_width = center_width + space_width + right_width;
1862 right_xpos = (display->getwidth() - right_width);
1863 /* there is no centered string anymore */
1864 center_width = 0;
1866 /* there is no right string, move center to right */
1867 if ((center_width != 0 && right_width == 0) &&
1868 (center_xpos + center_width > right_xpos)) {
1869 /* move the center string to the right string */
1870 format_align->right = format_align->center;
1871 /* calculate the new width and position of the string */
1872 right_width = center_width;
1873 right_xpos = (display->getwidth() - right_width);
1874 /* there is no centered string anymore */
1875 center_width = 0;
1878 /* CASE 3: left and right overlap
1879 There is no center string anymore, either there never
1880 was one or it has been merged in case 1 or 2 */
1881 /* there is a left string, need to merge left and right */
1882 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1883 (left_xpos + left_width + space_width > right_xpos)) {
1884 /* replace the former separator '\0' of left and
1885 right string with a space */
1886 *(--format_align->right) = ' ';
1887 /* calculate the new width and position of the string */
1888 left_width = left_width + space_width + right_width;
1889 /* there is no right string anymore */
1890 right_width = 0;
1892 /* there is no left string, move right to left */
1893 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1894 (left_width > right_xpos)) {
1895 /* move the right string to the left string */
1896 format_align->left = format_align->right;
1897 /* calculate the new width and position of the string */
1898 left_width = right_width;
1899 /* there is no right string anymore */
1900 right_width = 0;
1903 ypos = (line * string_height);
1906 if (scroll && ((left_width > scroll_width) ||
1907 (center_width > scroll_width) ||
1908 (right_width > scroll_width)))
1910 display->puts_scroll(0, line,
1911 (unsigned char *)format_align->left);
1913 else
1915 #ifdef HAVE_LCD_BITMAP
1916 /* clear the line first */
1917 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1918 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1919 display->set_drawmode(DRMODE_SOLID);
1920 #endif
1922 /* Nasty hack: we output an empty scrolling string,
1923 which will reset the scroller for that line */
1924 display->puts_scroll(0, line, (unsigned char *)"");
1926 /* print aligned strings */
1927 if (left_width != 0)
1929 display->putsxy(left_xpos, ypos,
1930 (unsigned char *)format_align->left);
1932 if (center_width != 0)
1934 display->putsxy(center_xpos, ypos,
1935 (unsigned char *)format_align->center);
1937 if (right_width != 0)
1939 display->putsxy(right_xpos, ypos,
1940 (unsigned char *)format_align->right);
1945 /* Refresh the WPS according to refresh_mode. */
1946 bool gui_wps_refresh(struct gui_wps *gwps,
1947 int ffwd_offset,
1948 unsigned char refresh_mode)
1950 struct wps_data *data = gwps->data;
1951 struct screen *display = gwps->display;
1952 struct wps_state *state = gwps->state;
1954 if(!gwps || !data || !state || !display)
1955 return false;
1957 int v, line, i, subline_idx;
1958 unsigned char flags;
1959 char linebuf[MAX_PATH];
1960 unsigned char vp_refresh_mode;
1962 struct align_pos align;
1963 align.left = NULL;
1964 align.center = NULL;
1965 align.right = NULL;
1967 bool update_line, new_subline_refresh;
1969 #ifdef HAVE_LCD_BITMAP
1971 /* to find out wether the peak meter is enabled we
1972 assume it wasn't until we find a line that contains
1973 the peak meter. We can't use peak_meter_enabled itself
1974 because that would mean to turn off the meter thread
1975 temporarily. (That shouldn't matter unless yield
1976 or sleep is called but who knows...)
1978 bool enable_pm = false;
1980 #endif
1982 /* reset to first subline if refresh all flag is set */
1983 if (refresh_mode == WPS_REFRESH_ALL)
1985 display->set_viewport(&data->viewports[0].vp);
1986 display->clear_viewport();
1988 for (i = 0; i <= data->num_lines; i++)
1990 data->lines[i].curr_subline = SUBLINE_RESET;
1994 #ifdef HAVE_LCD_CHARCELLS
1995 for (i = 0; i < 8; i++)
1997 if (data->wps_progress_pat[i] == 0)
1998 data->wps_progress_pat[i] = display->get_locked_pattern();
2000 #endif
2002 if (!state->id3)
2004 display->stop_scroll();
2005 return false;
2008 state->ff_rewind_count = ffwd_offset;
2010 /* disable any viewports which are conditionally displayed */
2011 for (v = 0; v < data->num_viewports; v++)
2013 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
2015 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
2016 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2017 else
2018 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
2021 for (v = 0; v < data->num_viewports; v++)
2023 struct wps_viewport *wps_vp = &(data->viewports[v]);
2024 display->set_viewport(&wps_vp->vp);
2025 vp_refresh_mode = refresh_mode;
2027 #ifdef HAVE_LCD_BITMAP
2028 /* Set images to not to be displayed */
2029 for (i = 0; i < MAX_IMAGES; i++)
2031 data->img[i].display = -1;
2033 #endif
2034 /* dont redraw the viewport if its disabled */
2035 if ((wps_vp->hidden_flags&VP_DRAW_HIDDEN))
2037 if (!(wps_vp->hidden_flags&VP_DRAW_WASHIDDEN))
2038 display->scroll_stop(&wps_vp->vp);
2039 wps_vp->hidden_flags |= VP_DRAW_WASHIDDEN;
2040 continue;
2042 else if (((wps_vp->hidden_flags&
2043 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2044 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2046 vp_refresh_mode = WPS_REFRESH_ALL;
2047 wps_vp->hidden_flags = VP_DRAW_HIDEABLE;
2049 if (vp_refresh_mode == WPS_REFRESH_ALL)
2051 display->clear_viewport();
2054 for (line = wps_vp->first_line;
2055 line <= wps_vp->last_line; line++)
2057 memset(linebuf, 0, sizeof(linebuf));
2058 update_line = false;
2060 /* get current subline for the line */
2061 new_subline_refresh = update_curr_subline(gwps, line);
2063 subline_idx = wps_subline_index(data, line,
2064 data->lines[line].curr_subline);
2065 flags = data->sublines[subline_idx].line_type;
2067 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2068 || new_subline_refresh)
2070 /* get_line tells us if we need to update the line */
2071 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2072 &align, linebuf, sizeof(linebuf));
2074 #ifdef HAVE_LCD_BITMAP
2075 /* peakmeter */
2076 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2078 /* the peakmeter should be alone on its line */
2079 update_line = false;
2081 int h = font_get(wps_vp->vp.font)->height;
2082 int peak_meter_y = (line - wps_vp->first_line)* h;
2084 /* The user might decide to have the peak meter in the last
2085 line so that it is only displayed if no status bar is
2086 visible. If so we neither want do draw nor enable the
2087 peak meter. */
2088 if (peak_meter_y + h <= display->getheight()) {
2089 /* found a line with a peak meter -> remember that we must
2090 enable it later */
2091 enable_pm = true;
2092 peak_meter_enabled = true;
2093 peak_meter_screen(gwps->display, 0, peak_meter_y,
2094 MIN(h, display->getheight() - peak_meter_y));
2096 else
2098 peak_meter_enabled = false;
2102 #else /* HAVE_LCD_CHARCELL */
2104 /* progressbar */
2105 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2107 if (data->full_line_progressbar)
2108 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2109 else
2110 draw_player_progress(gwps);
2112 #endif
2114 if (update_line &&
2115 /* conditionals clear the line which means if the %Vd is put into the default
2116 viewport there will be a blank line.
2117 To get around this we dont allow any actual drawing to happen in the
2118 deault vp if other vp's are defined */
2119 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2121 if (flags & WPS_REFRESH_SCROLL)
2123 /* if the line is a scrolling one we don't want to update
2124 too often, so that it has the time to scroll */
2125 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2126 write_line(display, &align, line - wps_vp->first_line, true);
2128 else
2129 write_line(display, &align, line - wps_vp->first_line, false);
2133 #ifdef HAVE_LCD_BITMAP
2134 /* progressbar */
2135 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2137 if (wps_vp->pb)
2139 draw_progressbar(gwps, wps_vp);
2142 /* Now display any images in this viewport */
2143 wps_display_images(gwps, &wps_vp->vp);
2144 #endif
2147 #ifdef HAVE_LCD_BITMAP
2148 data->peak_meter_enabled = enable_pm;
2149 #endif
2151 /* Restore the default viewport */
2152 display->set_viewport(NULL);
2154 display->update();
2156 #ifdef HAVE_BACKLIGHT
2157 if (global_settings.caption_backlight && state->id3)
2159 /* turn on backlight n seconds before track ends, and turn it off n
2160 seconds into the new track. n == backlight_timeout, or 5s */
2161 int n = global_settings.backlight_timeout * 1000;
2163 if ( n < 1000 )
2164 n = 5000; /* use 5s if backlight is always on or off */
2166 if (((state->id3->elapsed < 1000) ||
2167 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2168 (state->paused == false))
2169 backlight_on();
2171 #endif
2172 #ifdef HAVE_REMOTE_LCD
2173 if (global_settings.remote_caption_backlight && state->id3)
2175 /* turn on remote backlight n seconds before track ends, and turn it
2176 off n seconds into the new track. n == remote_backlight_timeout,
2177 or 5s */
2178 int n = global_settings.remote_backlight_timeout * 1000;
2180 if ( n < 1000 )
2181 n = 5000; /* use 5s if backlight is always on or off */
2183 if (((state->id3->elapsed < 1000) ||
2184 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2185 (state->paused == false))
2186 remote_backlight_on();
2188 #endif
2189 /* force a bars update if they are being displayed */
2190 viewportmanager_draw_statusbars(NULL);
2191 return true;