quick fix for FS#8729
[Rockbox.git] / apps / gui / gwps-common.c
blobb53d68d6ba90b0d67c2484e1c85a1c4d9fbbf7ed
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 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
19 ****************************************************************************/
20 #include "gwps-common.h"
21 #include "font.h"
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include "system.h"
26 #include "settings.h"
27 #include "rbunicode.h"
28 #include "rtc.h"
29 #include "audio.h"
30 #include "status.h"
31 #include "power.h"
32 #include "powermgmt.h"
33 #include "sound.h"
34 #include "debug.h"
35 #ifdef HAVE_LCD_CHARCELLS
36 #include "hwcompat.h"
37 #endif
38 #include "abrepeat.h"
39 #include "mp3_playback.h"
40 #include "backlight.h"
41 #include "lang.h"
42 #include "misc.h"
43 #include "splash.h"
44 #include "scrollbar.h"
45 #include "led.h"
46 #include "lcd.h"
47 #ifdef HAVE_LCD_BITMAP
48 #include "peakmeter.h"
49 /* Image stuff */
50 #include "bmp.h"
51 #include "albumart.h"
52 #endif
53 #include "dsp.h"
54 #include "action.h"
55 #include "cuesheet.h"
56 #include "playlist.h"
57 #if CONFIG_CODEC == SWCODEC
58 #include "playback.h"
59 #endif
60 #include "backdrop.h"
62 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
63 /* 3% of 30min file == 54s step size */
64 #define MIN_FF_REWIND_STEP 500
66 /* draws the statusbar on the given wps-screen */
67 #ifdef HAVE_LCD_BITMAP
68 static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
70 bool draw = global_settings.statusbar;
72 if (wps->data->wps_sb_tag)
73 draw = wps->data->show_sb_on_wps;
75 if (draw)
76 gui_statusbar_draw(wps->statusbar, force);
78 #else
79 #define gui_wps_statusbar_draw(wps, force) \
80 gui_statusbar_draw((wps)->statusbar, (force))
81 #endif
82 #include "pcmbuf.h"
84 /* fades the volume */
85 bool wps_fading_out = false;
86 void fade(bool fade_in, bool updatewps)
88 int fp_global_vol = global_settings.volume << 8;
89 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
90 int fp_step = (fp_global_vol - fp_min_vol) / 30;
91 int i;
92 wps_fading_out = !fade_in;
93 if (fade_in) {
94 /* fade in */
95 int fp_volume = fp_min_vol;
97 /* zero out the sound */
98 sound_set_volume(fp_min_vol >> 8);
100 sleep(HZ/10); /* let audio thread run */
101 audio_resume();
103 while (fp_volume < fp_global_vol - fp_step) {
104 fp_volume += fp_step;
105 sound_set_volume(fp_volume >> 8);
106 if (updatewps)
108 FOR_NB_SCREENS(i)
109 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
111 sleep(1);
113 sound_set_volume(global_settings.volume);
115 else {
116 /* fade out */
117 int fp_volume = fp_global_vol;
119 while (fp_volume > fp_min_vol + fp_step) {
120 fp_volume -= fp_step;
121 sound_set_volume(fp_volume >> 8);
122 if (updatewps)
124 FOR_NB_SCREENS(i)
125 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
127 sleep(1);
129 audio_pause();
130 wps_fading_out = false;
131 #if CONFIG_CODEC != SWCODEC
132 #ifndef SIMULATOR
133 /* let audio thread run and wait for the mas to run out of data */
134 while (!mp3_pause_done())
135 #endif
136 sleep(HZ/10);
137 #endif
139 /* reset volume to what it was before the fade */
140 sound_set_volume(global_settings.volume);
144 /* return true if screen restore is needed
145 return false otherwise
147 bool update_onvol_change(struct gui_wps * gwps)
149 gui_wps_statusbar_draw(gwps, false);
150 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
152 #ifdef HAVE_LCD_CHARCELLS
153 gui_splash(gwps->display, 0, "Vol: %3d dB",
154 sound_val2phys(SOUND_VOLUME, global_settings.volume));
155 return true;
156 #endif
157 return false;
160 void play_hop(int direction)
162 if(!wps_state.id3 || !wps_state.id3->length
163 || global_settings.study_hop_step == 0)
164 return;
165 #define STEP ((unsigned)global_settings.study_hop_step *1000)
166 if(direction == 1
167 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
168 #if CONFIG_CODEC == SWCODEC
169 if(global_settings.beep)
170 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
171 #endif
172 return;
174 if((direction == -1 && wps_state.id3->elapsed < STEP))
175 wps_state.id3->elapsed = 0;
176 else
177 wps_state.id3->elapsed += STEP *direction;
178 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
179 #if (CONFIG_CODEC == SWCODEC)
180 audio_pre_ff_rewind();
181 #else
182 audio_pause();
183 #endif
185 audio_ff_rewind(wps_state.id3->elapsed);
186 #if (CONFIG_CODEC != SWCODEC)
187 if (!wps_state.paused)
188 audio_resume();
189 #endif
190 #undef STEP
193 bool ffwd_rew(int button)
195 unsigned int step = 0; /* current ff/rewind step */
196 unsigned int max_step = 0; /* maximum ff/rewind step */
197 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
198 int direction = -1; /* forward=1 or backward=-1 */
199 long accel_tick = 0; /* next time at which to bump the step size */
200 bool exit = false;
201 bool usb = false;
202 int i = 0;
204 if (button == ACTION_NONE)
206 status_set_ffmode(0);
207 return usb;
209 while (!exit)
211 switch ( button )
213 case ACTION_WPS_SEEKFWD:
214 direction = 1;
215 case ACTION_WPS_SEEKBACK:
216 if (wps_state.ff_rewind)
218 if (direction == 1)
220 /* fast forwarding, calc max step relative to end */
221 max_step = (wps_state.id3->length -
222 (wps_state.id3->elapsed +
223 ff_rewind_count)) *
224 FF_REWIND_MAX_PERCENT / 100;
226 else
228 /* rewinding, calc max step relative to start */
229 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
230 FF_REWIND_MAX_PERCENT / 100;
233 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
235 if (step > max_step)
236 step = max_step;
238 ff_rewind_count += step * direction;
240 if (global_settings.ff_rewind_accel != 0 &&
241 current_tick >= accel_tick)
243 step *= 2;
244 accel_tick = current_tick +
245 global_settings.ff_rewind_accel*HZ;
248 else
250 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
251 wps_state.id3 && wps_state.id3->length )
253 if (!wps_state.paused)
254 #if (CONFIG_CODEC == SWCODEC)
255 audio_pre_ff_rewind();
256 #else
257 audio_pause();
258 #endif
259 #if CONFIG_KEYPAD == PLAYER_PAD
260 FOR_NB_SCREENS(i)
261 gui_wps[i].display->stop_scroll();
262 #endif
263 if (direction > 0)
264 status_set_ffmode(STATUS_FASTFORWARD);
265 else
266 status_set_ffmode(STATUS_FASTBACKWARD);
268 wps_state.ff_rewind = true;
270 step = 1000 * global_settings.ff_rewind_min_step;
272 accel_tick = current_tick +
273 global_settings.ff_rewind_accel*HZ;
275 else
276 break;
279 if (direction > 0) {
280 if ((wps_state.id3->elapsed + ff_rewind_count) >
281 wps_state.id3->length)
282 ff_rewind_count = wps_state.id3->length -
283 wps_state.id3->elapsed;
285 else {
286 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
287 ff_rewind_count = -wps_state.id3->elapsed;
290 FOR_NB_SCREENS(i)
291 gui_wps_refresh(&gui_wps[i],
292 (wps_state.wps_time_countup == false)?
293 ff_rewind_count:-ff_rewind_count,
294 WPS_REFRESH_PLAYER_PROGRESS |
295 WPS_REFRESH_DYNAMIC);
297 break;
299 case ACTION_WPS_STOPSEEK:
300 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
301 audio_ff_rewind(wps_state.id3->elapsed);
302 ff_rewind_count = 0;
303 wps_state.ff_rewind = false;
304 status_set_ffmode(0);
305 #if (CONFIG_CODEC != SWCODEC)
306 if (!wps_state.paused)
307 audio_resume();
308 #endif
309 #ifdef HAVE_LCD_CHARCELLS
310 gui_wps_display();
311 #endif
312 exit = true;
313 break;
315 default:
316 if(default_event_handler(button) == SYS_USB_CONNECTED) {
317 status_set_ffmode(0);
318 usb = true;
319 exit = true;
321 break;
323 if (!exit)
324 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
326 return usb;
329 bool gui_wps_display(void)
331 int i;
332 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
334 global_status.resume_index = -1;
335 #ifdef HAVE_LCD_BITMAP
336 gui_syncstatusbar_draw(&statusbars, true);
337 #endif
338 gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST));
339 return true;
341 else
343 FOR_NB_SCREENS(i)
345 /* Update the values in the first (default) viewport - in case the user
346 has modified the statusbar or colour settings */
347 #ifdef HAVE_LCD_BITMAP
348 #if LCD_DEPTH > 1
349 if (gui_wps[i].display->depth > 1)
351 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
352 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
354 #endif
355 #endif
357 gui_wps[i].display->clear_display();
358 if (!gui_wps[i].data->wps_loaded) {
359 if ( !gui_wps[i].data->num_tokens ) {
360 /* set the default wps for the main-screen */
361 if(i == 0)
363 #ifdef HAVE_LCD_BITMAP
364 #if LCD_DEPTH > 1
365 unload_wps_backdrop();
366 #endif
367 wps_data_load(gui_wps[i].data,
368 gui_wps[i].display,
369 "%s%?it<%?in<%in. |>%it|%fn>\n"
370 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
371 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
372 "\n"
373 "%al%pc/%pt%ar[%pp:%pe]\n"
374 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
375 "%pb\n"
376 "%pm\n", false);
377 #else
378 wps_data_load(gui_wps[i].data,
379 gui_wps[i].display,
380 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
381 "%pc%?ps<*|/>%pt\n", false);
382 #endif
384 #if NB_SCREENS == 2
385 /* set the default wps for the remote-screen */
386 else if(i == 1)
388 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
389 unload_remote_wps_backdrop();
390 #endif
391 wps_data_load(gui_wps[i].data,
392 gui_wps[i].display,
393 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
394 "%s%?it<%?in<%in. |>%it|%fn>\n"
395 "%al%pc/%pt%ar[%pp:%pe]\n"
396 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
397 "%pb\n", false);
399 #endif
404 yield();
405 FOR_NB_SCREENS(i)
407 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
409 return false;
412 bool update(struct gui_wps *gwps)
414 bool track_changed = audio_has_changed_track();
415 bool retcode = false;
417 gwps->state->nid3 = audio_next_track();
418 if (track_changed)
420 gwps->display->stop_scroll();
421 gwps->state->id3 = audio_current_track();
423 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
424 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
426 /* the current cuesheet isn't the right one any more */
428 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
429 /* We have the new cuesheet in memory (temp_cue),
430 let's make it the current one ! */
431 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
433 else {
434 /* We need to parse the new cuesheet */
436 char cuepath[MAX_PATH];
438 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
439 parse_cuesheet(cuepath, curr_cue))
441 gwps->state->id3->cuesheet_type = 1;
442 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
446 cue_spoof_id3(curr_cue, gwps->state->id3);
449 if (gui_wps_display())
450 retcode = true;
451 else{
452 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
455 if (gwps->state->id3)
457 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
458 sizeof(gwps->state->current_track_path));
459 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
463 if (gwps->state->id3)
465 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
466 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
467 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
468 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
470 /* We've changed tracks within the cuesheet :
471 we need to update the ID3 info and refresh the WPS */
473 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
474 cue_spoof_id3(curr_cue, gwps->state->id3);
476 gwps->display->stop_scroll();
477 if (gui_wps_display())
478 retcode = true;
479 else
480 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
482 else
483 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
486 gui_wps_statusbar_draw(gwps, false);
488 return retcode;
492 void display_keylock_text(bool locked)
494 char* s;
495 int i;
496 FOR_NB_SCREENS(i)
497 gui_wps[i].display->stop_scroll();
499 if(locked)
500 s = str(LANG_KEYLOCK_ON);
501 else
502 s = str(LANG_KEYLOCK_OFF);
503 gui_syncsplash(HZ, s);
506 #ifdef HAVE_LCD_BITMAP
508 static void draw_progressbar(struct gui_wps *gwps,
509 struct progressbar *pb)
511 struct screen *display = gwps->display;
512 struct wps_state *state = gwps->state;
513 if (pb->have_bitmap_pb)
514 gui_bitmap_scrollbar_draw(display, pb->bm,
515 pb->x, pb->y, pb->width, pb->bm.height,
516 state->id3->length ? state->id3->length : 1, 0,
517 state->id3->length ? state->id3->elapsed
518 + state->ff_rewind_count : 0,
519 HORIZONTAL);
520 else
521 gui_scrollbar_draw(display, pb->x, pb->y, pb->width, pb->height,
522 state->id3->length ? state->id3->length : 1, 0,
523 state->id3->length ? state->id3->elapsed
524 + state->ff_rewind_count : 0,
525 HORIZONTAL);
526 #ifdef AB_REPEAT_ENABLE
527 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
528 ab_draw_markers(display, state->id3->length,
529 pb->x, pb->x + pb->width, pb->y, pb->height);
530 #endif
532 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
533 cue_draw_markers(display, state->id3->length,
534 pb->x, pb->x + pb->width, pb->y+1, pb->height-2);
537 /* clears the area where the image was shown */
538 static void clear_image_pos(struct gui_wps *gwps, int n)
540 if(!gwps)
541 return;
542 struct wps_data *data = gwps->data;
543 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
544 gwps->display->fillrect(data->img[n].x, data->img[n].y,
545 data->img[n].bm.width, data->img[n].subimage_height);
546 gwps->display->set_drawmode(DRMODE_SOLID);
549 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
551 struct screen *display = gwps->display;
552 struct wps_data *data = gwps->data;
553 if(data->img[n].always_display)
554 display->set_drawmode(DRMODE_FG);
555 else
556 display->set_drawmode(DRMODE_SOLID);
558 #if LCD_DEPTH > 1
559 if(data->img[n].bm.format == FORMAT_MONO) {
560 #endif
561 display->mono_bitmap_part(data->img[n].bm.data,
562 0, data->img[n].subimage_height * subimage,
563 data->img[n].bm.width, data->img[n].x,
564 data->img[n].y, data->img[n].bm.width,
565 data->img[n].subimage_height);
566 #if LCD_DEPTH > 1
567 } else {
568 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
569 0, data->img[n].subimage_height * subimage,
570 data->img[n].bm.width, data->img[n].x,
571 data->img[n].y, data->img[n].bm.width,
572 data->img[n].subimage_height);
574 #endif
577 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
579 if(!gwps || !gwps->data || !gwps->display)
580 return;
582 int n;
583 struct wps_data *data = gwps->data;
584 struct screen *display = gwps->display;
586 for (n = 0; n < MAX_IMAGES; n++)
588 if (data->img[n].loaded)
590 if (data->img[n].display >= 0)
592 wps_draw_image(gwps, n, data->img[n].display);
593 } else if (data->img[n].always_display && data->img[n].vp == vp)
595 wps_draw_image(gwps, n, 0);
599 display->set_drawmode(DRMODE_SOLID);
602 #else /* HAVE_LCD_CHARCELL */
604 static bool draw_player_progress(struct gui_wps *gwps)
606 struct wps_state *state = gwps->state;
607 struct screen *display = gwps->display;
608 unsigned char progress_pattern[7];
609 int pos = 0;
610 int i;
612 if (!state->id3)
613 return false;
615 if (state->id3->length)
616 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
617 / state->id3->length;
619 for (i = 0; i < 7; i++, pos -= 5)
621 if (pos <= 0)
622 progress_pattern[i] = 0x1f;
623 else if (pos >= 5)
624 progress_pattern[i] = 0x00;
625 else
626 progress_pattern[i] = 0x1f >> pos;
629 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
630 return true;
633 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
635 static const unsigned char numbers[10][4] = {
636 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
637 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
638 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
639 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
640 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
641 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
642 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
643 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
644 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
645 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
648 struct wps_state *state = gwps->state;
649 struct screen *display = gwps->display;
650 struct wps_data *data = gwps->data;
651 unsigned char progress_pattern[7];
652 char timestr[10];
653 int time;
654 int time_idx = 0;
655 int pos = 0;
656 int pat_idx = 1;
657 int digit, i, j;
658 bool softchar;
660 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
661 return;
663 time = state->id3->elapsed + state->ff_rewind_count;
664 if (state->id3->length)
665 pos = 55 * time / state->id3->length;
667 memset(timestr, 0, sizeof(timestr));
668 format_time(timestr, sizeof(timestr)-2, time);
669 timestr[strlen(timestr)] = ':'; /* always safe */
671 for (i = 0; i < 11; i++, pos -= 5)
673 softchar = false;
674 memset(progress_pattern, 0, sizeof(progress_pattern));
676 if ((digit = timestr[time_idx]))
678 softchar = true;
679 digit -= '0';
681 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
683 memcpy(progress_pattern, numbers[digit], 4);
684 time_idx += 2;
686 else /* tens, shifted right */
688 for (j = 0; j < 4; j++)
689 progress_pattern[j] = numbers[digit][j] >> 1;
691 if (time_idx > 0) /* not the first group, add colon in front */
693 progress_pattern[1] |= 0x10;
694 progress_pattern[3] |= 0x10;
696 time_idx++;
699 if (pos >= 5)
700 progress_pattern[5] = progress_pattern[6] = 0x1f;
703 if (pos > 0 && pos < 5)
705 softchar = true;
706 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
709 if (softchar && pat_idx < 8)
711 display->define_pattern(data->wps_progress_pat[pat_idx],
712 progress_pattern);
713 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
714 pat_idx++;
716 else if (pos <= 0)
717 buf = utf8encode(' ', buf);
718 else
719 buf = utf8encode(0xe115, buf); /* 2/7 _ */
721 *buf = '\0';
724 #endif /* HAVE_LCD_CHARCELL */
726 static char* get_codectype(const struct mp3entry* id3)
728 if (id3->codectype < AFMT_NUM_CODECS) {
729 return (char*)audio_formats[id3->codectype].label;
730 } else {
731 return NULL;
735 /* Extract a part from a path.
737 * buf - buffer extract part to.
738 * buf_size - size of buffer.
739 * path - path to extract from.
740 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
741 * parent of parent, etc.
743 * Returns buf if the desired level was found, NULL otherwise.
745 static char* get_dir(char* buf, int buf_size, const char* path, int level)
747 const char* sep;
748 const char* last_sep;
749 int len;
751 sep = path + strlen(path);
752 last_sep = sep;
754 while (sep > path)
756 if ('/' == *(--sep))
758 if (!level)
759 break;
761 level--;
762 last_sep = sep - 1;
766 if (level || (last_sep <= sep))
767 return NULL;
769 len = MIN(last_sep - sep, buf_size - 1);
770 strncpy(buf, sep + 1, len);
771 buf[len] = 0;
772 return buf;
775 /* Return the tag found at index i and write its value in buf.
776 The return value is buf if the tag had a value, or NULL if not.
778 intval is used with conditionals/enums: when this function is called,
779 intval should contain the number of options in the conditional/enum.
780 When this function returns, intval is -1 if the tag is non numeric or,
781 if the tag is numeric, intval is the enum case we want to go to.
782 When not treating a conditional/enum, intval should be NULL.
784 static char *get_token_value(struct gui_wps *gwps,
785 struct wps_token *token,
786 char *buf, int buf_size,
787 int *intval)
789 if (!gwps)
790 return NULL;
792 struct wps_data *data = gwps->data;
793 struct wps_state *state = gwps->state;
795 if (!data || !state)
796 return NULL;
798 struct mp3entry *id3;
800 if (token->next)
801 id3 = state->nid3;
802 else
803 id3 = state->id3;
805 if (!id3)
806 return NULL;
808 #if CONFIG_RTC
809 struct tm* tm = NULL;
811 /* if the token is an RTC one, update the time
812 and do the necessary checks */
813 if (token->type >= WPS_TOKENS_RTC_BEGIN
814 && token->type <= WPS_TOKENS_RTC_END)
816 tm = get_time();
818 if (!valid_time(tm))
819 return NULL;
821 #endif
823 int limit = 1;
824 if (intval)
826 limit = *intval;
827 *intval = -1;
830 switch (token->type)
832 case WPS_TOKEN_CHARACTER:
833 return &(token->value.c);
835 case WPS_TOKEN_STRING:
836 return data->strings[token->value.i];
838 case WPS_TOKEN_TRACK_TIME_ELAPSED:
839 format_time(buf, buf_size,
840 id3->elapsed + state->ff_rewind_count);
841 return buf;
843 case WPS_TOKEN_TRACK_TIME_REMAINING:
844 format_time(buf, buf_size,
845 id3->length - id3->elapsed -
846 state->ff_rewind_count);
847 return buf;
849 case WPS_TOKEN_TRACK_LENGTH:
850 format_time(buf, buf_size, id3->length);
851 return buf;
853 case WPS_TOKEN_PLAYLIST_ENTRIES:
854 snprintf(buf, buf_size, "%d", playlist_amount());
855 return buf;
857 case WPS_TOKEN_PLAYLIST_NAME:
858 return playlist_name(NULL, buf, buf_size);
860 case WPS_TOKEN_PLAYLIST_POSITION:
861 snprintf(buf, buf_size, "%d", playlist_get_display_index());
862 return buf;
864 case WPS_TOKEN_PLAYLIST_SHUFFLE:
865 if ( global_settings.playlist_shuffle )
866 return "s";
867 else
868 return NULL;
869 break;
871 case WPS_TOKEN_VOLUME:
872 snprintf(buf, buf_size, "%d", global_settings.volume);
873 if (intval)
875 if (global_settings.volume == sound_min(SOUND_VOLUME))
877 *intval = 1;
879 else if (global_settings.volume == 0)
881 *intval = limit - 1;
883 else if (global_settings.volume > 0)
885 *intval = limit;
887 else
889 *intval = (limit - 3) * (global_settings.volume
890 - sound_min(SOUND_VOLUME) - 1)
891 / (-1 - sound_min(SOUND_VOLUME)) + 2;
894 return buf;
896 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
897 if (id3->length <= 0)
898 return NULL;
900 if (intval)
902 *intval = limit * (id3->elapsed + state->ff_rewind_count)
903 / id3->length + 1;
905 snprintf(buf, buf_size, "%d",
906 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
907 return buf;
909 case WPS_TOKEN_METADATA_ARTIST:
910 return id3->artist;
912 case WPS_TOKEN_METADATA_COMPOSER:
913 return id3->composer;
915 case WPS_TOKEN_METADATA_ALBUM:
916 return id3->album;
918 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
919 return id3->albumartist;
921 case WPS_TOKEN_METADATA_GROUPING:
922 return id3->grouping;
924 case WPS_TOKEN_METADATA_GENRE:
925 return id3->genre_string;
927 case WPS_TOKEN_METADATA_DISC_NUMBER:
928 if (id3->disc_string)
929 return id3->disc_string;
930 if (id3->discnum) {
931 snprintf(buf, buf_size, "%d", id3->discnum);
932 return buf;
934 return NULL;
936 case WPS_TOKEN_METADATA_TRACK_NUMBER:
937 if (id3->track_string)
938 return id3->track_string;
940 if (id3->tracknum) {
941 snprintf(buf, buf_size, "%d", id3->tracknum);
942 return buf;
944 return NULL;
946 case WPS_TOKEN_METADATA_TRACK_TITLE:
947 return id3->title;
949 case WPS_TOKEN_METADATA_VERSION:
950 switch (id3->id3version)
952 case ID3_VER_1_0:
953 return "1";
955 case ID3_VER_1_1:
956 return "1.1";
958 case ID3_VER_2_2:
959 return "2.2";
961 case ID3_VER_2_3:
962 return "2.3";
964 case ID3_VER_2_4:
965 return "2.4";
967 default:
968 return NULL;
971 case WPS_TOKEN_METADATA_YEAR:
972 if( id3->year_string )
973 return id3->year_string;
975 if (id3->year) {
976 snprintf(buf, buf_size, "%d", id3->year);
977 return buf;
979 return NULL;
981 case WPS_TOKEN_METADATA_COMMENT:
982 return id3->comment;
984 #ifdef HAVE_ALBUMART
985 case WPS_TOKEN_ALBUMART_DISPLAY:
986 draw_album_art(gwps, audio_current_aa_hid(), false);
987 return NULL;
989 case WPS_TOKEN_ALBUMART_FOUND:
990 if (audio_current_aa_hid() >= 0) {
991 snprintf(buf, buf_size, "C");
992 return buf;
994 return NULL;
995 #endif
997 case WPS_TOKEN_FILE_BITRATE:
998 if(id3->bitrate)
999 snprintf(buf, buf_size, "%d", id3->bitrate);
1000 else
1001 snprintf(buf, buf_size, "?");
1002 return buf;
1004 case WPS_TOKEN_FILE_CODEC:
1005 if (intval)
1007 if(id3->codectype == AFMT_UNKNOWN)
1008 *intval = AFMT_NUM_CODECS;
1009 else
1010 *intval = id3->codectype;
1012 return get_codectype(id3);
1014 case WPS_TOKEN_FILE_FREQUENCY:
1015 snprintf(buf, buf_size, "%ld", id3->frequency);
1016 return buf;
1018 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
1019 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1020 if ((id3->frequency % 1000) < 100)
1021 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1022 else
1023 snprintf(buf, buf_size, "%ld.%d",
1024 id3->frequency / 1000,
1025 (id3->frequency % 1000) / 100);
1026 return buf;
1028 case WPS_TOKEN_FILE_NAME:
1029 if (get_dir(buf, buf_size, id3->path, 0)) {
1030 /* Remove extension */
1031 char* sep = strrchr(buf, '.');
1032 if (NULL != sep) {
1033 *sep = 0;
1035 return buf;
1037 else {
1038 return NULL;
1041 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1042 return get_dir(buf, buf_size, id3->path, 0);
1044 case WPS_TOKEN_FILE_PATH:
1045 return id3->path;
1047 case WPS_TOKEN_FILE_SIZE:
1048 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1049 return buf;
1051 case WPS_TOKEN_FILE_VBR:
1052 return id3->vbr ? "(avg)" : NULL;
1054 case WPS_TOKEN_FILE_DIRECTORY:
1055 return get_dir(buf, buf_size, id3->path, token->value.i);
1057 case WPS_TOKEN_BATTERY_PERCENT:
1059 int l = battery_level();
1061 if (intval)
1063 limit = MAX(limit, 2);
1064 if (l > -1) {
1065 /* First enum is used for "unknown level". */
1066 *intval = (limit - 1) * l / 100 + 2;
1067 } else {
1068 *intval = 1;
1072 if (l > -1) {
1073 snprintf(buf, buf_size, "%d", l);
1074 return buf;
1075 } else {
1076 return "?";
1080 case WPS_TOKEN_BATTERY_VOLTS:
1082 unsigned int v = battery_voltage();
1083 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1084 return buf;
1087 case WPS_TOKEN_BATTERY_TIME:
1089 int t = battery_time();
1090 if (t >= 0)
1091 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1092 else
1093 strncpy(buf, "?h ?m", buf_size);
1094 return buf;
1097 #if CONFIG_CHARGING
1098 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1100 if(charger_input_state==CHARGER)
1101 return "p";
1102 else
1103 return NULL;
1105 #endif
1106 #if CONFIG_CHARGING >= CHARGING_MONITOR
1107 case WPS_TOKEN_BATTERY_CHARGING:
1109 if (charge_state == CHARGING || charge_state == TOPOFF) {
1110 return "c";
1111 } else {
1112 return NULL;
1115 #endif
1116 case WPS_TOKEN_BATTERY_SLEEPTIME:
1118 if (get_sleep_timer() == 0)
1119 return NULL;
1120 else
1122 format_time(buf, buf_size, get_sleep_timer() * 1000);
1123 return buf;
1127 case WPS_TOKEN_PLAYBACK_STATUS:
1129 int status = audio_status();
1130 int mode = 1;
1131 if (status == AUDIO_STATUS_PLAY)
1132 mode = 2;
1133 if (wps_fading_out ||
1134 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1135 mode = 3;
1136 if (status_get_ffmode() == STATUS_FASTFORWARD)
1137 mode = 4;
1138 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1139 mode = 5;
1141 if (intval) {
1142 *intval = mode;
1145 snprintf(buf, buf_size, "%d", mode-1);
1146 return buf;
1149 case WPS_TOKEN_REPEAT_MODE:
1150 if (intval)
1151 *intval = global_settings.repeat_mode + 1;
1152 snprintf(buf, buf_size, "%d", *intval);
1153 return buf;
1154 case WPS_TOKEN_RTC_12HOUR_CFG:
1155 if (intval)
1156 *intval = global_settings.timeformat + 1;
1157 snprintf(buf, buf_size, "%d", *intval);
1158 return buf;
1159 #if CONFIG_RTC
1160 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1161 /* d: day of month (01..31) */
1162 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1163 return buf;
1165 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1166 /* e: day of month, blank padded ( 1..31) */
1167 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1168 return buf;
1170 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1171 /* H: hour (00..23) */
1172 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1173 return buf;
1175 case WPS_TOKEN_RTC_HOUR_24:
1176 /* k: hour ( 0..23) */
1177 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1178 return buf;
1180 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1181 /* I: hour (01..12) */
1182 snprintf(buf, buf_size, "%02d",
1183 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1184 return buf;
1186 case WPS_TOKEN_RTC_HOUR_12:
1187 /* l: hour ( 1..12) */
1188 snprintf(buf, buf_size, "%2d",
1189 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1190 return buf;
1192 case WPS_TOKEN_RTC_MONTH:
1193 /* m: month (01..12) */
1194 if (intval)
1195 *intval = tm->tm_mon + 1;
1196 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1197 return buf;
1199 case WPS_TOKEN_RTC_MINUTE:
1200 /* M: minute (00..59) */
1201 snprintf(buf, buf_size, "%02d", tm->tm_min);
1202 return buf;
1204 case WPS_TOKEN_RTC_SECOND:
1205 /* S: second (00..59) */
1206 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1207 return buf;
1209 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1210 /* y: last two digits of year (00..99) */
1211 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1212 return buf;
1214 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1215 /* Y: year (1970...) */
1216 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1217 return buf;
1219 case WPS_TOKEN_RTC_AM_PM_UPPER:
1220 /* p: upper case AM or PM indicator */
1221 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1222 return buf;
1224 case WPS_TOKEN_RTC_AM_PM_LOWER:
1225 /* P: lower case am or pm indicator */
1226 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1227 return buf;
1229 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1230 /* a: abbreviated weekday name (Sun..Sat) */
1231 snprintf(buf, buf_size, "%s",str(LANG_WEEKDAY_SUNDAY + tm->tm_wday));
1232 return buf;
1234 case WPS_TOKEN_RTC_MONTH_NAME:
1235 /* b: abbreviated month name (Jan..Dec) */
1236 snprintf(buf, buf_size, "%s",str(LANG_MONTH_JANUARY + tm->tm_mon));
1237 return buf;
1239 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1240 /* u: day of week (1..7); 1 is Monday */
1241 if (intval)
1242 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1243 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1244 return buf;
1246 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1247 /* w: day of week (0..6); 0 is Sunday */
1248 if (intval)
1249 *intval = tm->tm_wday + 1;
1250 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1251 return buf;
1252 #else
1253 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1254 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1255 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1256 case WPS_TOKEN_RTC_HOUR_24:
1257 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1258 case WPS_TOKEN_RTC_HOUR_12:
1259 case WPS_TOKEN_RTC_MONTH:
1260 case WPS_TOKEN_RTC_MINUTE:
1261 case WPS_TOKEN_RTC_SECOND:
1262 case WPS_TOKEN_RTC_AM_PM_UPPER:
1263 case WPS_TOKEN_RTC_AM_PM_LOWER:
1264 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1265 strncpy(buf, "--", buf_size);
1266 return buf;
1267 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1268 strncpy(buf, "----", buf_size);
1269 return buf;
1270 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1271 case WPS_TOKEN_RTC_MONTH_NAME:
1272 strncpy(buf, "---", buf_size);
1273 return buf;
1274 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1275 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1276 strncpy(buf, "-", buf_size);
1277 return buf;
1278 #endif
1280 #ifdef HAVE_LCD_CHARCELLS
1281 case WPS_TOKEN_PROGRESSBAR:
1283 char *end = utf8encode(data->wps_progress_pat[0], buf);
1284 *end = '\0';
1285 return buf;
1288 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1289 if(is_new_player())
1291 /* we need 11 characters (full line) for
1292 progress-bar */
1293 snprintf(buf, buf_size, " ");
1295 else
1297 /* Tell the user if we have an OldPlayer */
1298 snprintf(buf, buf_size, " <Old LCD> ");
1300 return buf;
1301 #endif
1303 #ifdef HAVE_TAGCACHE
1304 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1305 if (intval) {
1306 *intval = id3->playcount + 1;
1308 snprintf(buf, buf_size, "%ld", id3->playcount);
1309 return buf;
1311 case WPS_TOKEN_DATABASE_RATING:
1312 if (intval) {
1313 *intval = id3->rating + 1;
1315 snprintf(buf, buf_size, "%d", id3->rating);
1316 return buf;
1318 case WPS_TOKEN_DATABASE_AUTOSCORE:
1319 if (intval)
1320 *intval = id3->score + 1;
1322 snprintf(buf, buf_size, "%d", id3->score);
1323 return buf;
1324 #endif
1326 #if (CONFIG_CODEC == SWCODEC)
1327 case WPS_TOKEN_CROSSFADE:
1328 if (intval)
1329 *intval = global_settings.crossfade + 1;
1330 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1331 return buf;
1333 case WPS_TOKEN_REPLAYGAIN:
1335 int val;
1337 if (global_settings.replaygain == 0)
1338 val = 1; /* off */
1339 else
1341 int type =
1342 get_replaygain_mode(id3->track_gain_string != NULL,
1343 id3->album_gain_string != NULL);
1344 if (type < 0)
1345 val = 6; /* no tag */
1346 else
1347 val = type + 2;
1349 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1350 val += 2;
1353 if (intval)
1354 *intval = val;
1356 switch (val)
1358 case 1:
1359 case 6:
1360 return "+0.00 dB";
1361 break;
1362 case 2:
1363 case 4:
1364 strncpy(buf, id3->track_gain_string, buf_size);
1365 break;
1366 case 3:
1367 case 5:
1368 strncpy(buf, id3->album_gain_string, buf_size);
1369 break;
1371 return buf;
1373 #endif /* (CONFIG_CODEC == SWCODEC) */
1375 #if (CONFIG_CODEC != MAS3507D)
1376 case WPS_TOKEN_SOUND_PITCH:
1378 int val = sound_get_pitch();
1379 snprintf(buf, buf_size, "%d.%d",
1380 val / 10, val % 10);
1381 return buf;
1383 #endif
1385 case WPS_TOKEN_MAIN_HOLD:
1386 #ifdef HAS_BUTTON_HOLD
1387 if (button_hold())
1388 #else
1389 if (is_keys_locked())
1390 #endif /*hold switch or softlock*/
1391 return "h";
1392 else
1393 return NULL;
1395 #ifdef HAS_REMOTE_BUTTON_HOLD
1396 case WPS_TOKEN_REMOTE_HOLD:
1397 if (remote_button_hold())
1398 return "r";
1399 else
1400 return NULL;
1401 #endif
1403 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1404 case WPS_TOKEN_VLED_HDD:
1405 if(led_read(HZ/2))
1406 return "h";
1407 else
1408 return NULL;
1409 #endif
1410 default:
1411 return NULL;
1415 /* Return the index to the end token for the conditional token at index.
1416 The conditional token can be either a start token or a separator
1417 (i.e. option) token.
1419 static int find_conditional_end(struct wps_data *data, int index)
1421 int ret = index;
1422 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1423 ret = data->tokens[ret].value.i;
1425 /* ret now is the index to the end token for the conditional. */
1426 return ret;
1429 /* Return the index of the appropriate case for the conditional
1430 that starts at cond_index.
1432 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1434 if (!gwps)
1435 return false;
1437 struct wps_data *data = gwps->data;
1439 int i, cond_end;
1440 int cond_index = *token_index;
1441 char result[128], *value;
1442 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1443 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1445 /* treat ?xx<true> constructs as if they had 2 options. */
1446 if (num_options < 2)
1447 num_options = 2;
1449 int intval = num_options;
1450 /* get_token_value needs to know the number of options in the enum */
1451 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1452 result, sizeof(result), &intval);
1454 /* intval is now the number of the enum option we want to read,
1455 starting from 1. If intval is -1, we check if value is empty. */
1456 if (intval == -1)
1457 intval = (value && *value) ? 1 : num_options;
1458 else if (intval > num_options || intval < 1)
1459 intval = num_options;
1461 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1463 /* skip to the right enum case */
1464 int next = cond_index + 2;
1465 for (i = 1; i < intval; i++)
1467 next = data->tokens[next].value.i;
1469 *token_index = next;
1471 if (prev_val == intval)
1473 /* Same conditional case as previously. Return without clearing the
1474 pictures */
1475 return false;
1478 cond_end = find_conditional_end(data, cond_index + 2);
1479 for (i = cond_index + 3; i < cond_end; i++)
1481 #ifdef HAVE_LCD_BITMAP
1482 /* clear all pictures in the conditional and nested ones */
1483 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1484 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1485 #endif
1486 #ifdef HAVE_ALBUMART
1487 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1488 draw_album_art(gwps, audio_current_aa_hid(), true);
1489 #endif
1492 return true;
1495 /* Read a (sub)line to the given alignment format buffer.
1496 linebuf is the buffer where the data is actually stored.
1497 align is the alignment format that'll be used to display the text.
1498 The return value indicates whether the line needs to be updated.
1500 static bool get_line(struct gui_wps *gwps,
1501 int line, int subline,
1502 struct align_pos *align,
1503 char *linebuf,
1504 int linebuf_size)
1506 struct wps_data *data = gwps->data;
1508 char temp_buf[128];
1509 char *buf = linebuf; /* will always point to the writing position */
1510 char *linebuf_end = linebuf + linebuf_size - 1;
1511 int i, last_token_idx;
1512 bool update = false;
1514 /* alignment-related variables */
1515 int cur_align;
1516 char* cur_align_start;
1517 cur_align_start = buf;
1518 cur_align = WPS_ALIGN_LEFT;
1519 align->left = NULL;
1520 align->center = NULL;
1521 align->right = NULL;
1523 /* Process all tokens of the desired subline */
1524 last_token_idx = wps_last_token_index(data, line, subline);
1525 for (i = wps_first_token_index(data, line, subline);
1526 i <= last_token_idx; i++)
1528 switch(data->tokens[i].type)
1530 case WPS_TOKEN_CONDITIONAL:
1531 /* place ourselves in the right conditional case */
1532 update |= evaluate_conditional(gwps, &i);
1533 break;
1535 case WPS_TOKEN_CONDITIONAL_OPTION:
1536 /* we've finished in the curent conditional case,
1537 skip to the end of the conditional structure */
1538 i = find_conditional_end(data, i);
1539 break;
1541 #ifdef HAVE_LCD_BITMAP
1542 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1544 struct gui_img *img = data->img;
1545 int n = data->tokens[i].value.i & 0xFF;
1546 int subimage = data->tokens[i].value.i >> 8;
1548 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1549 img[n].display = subimage;
1550 break;
1552 #endif
1554 case WPS_TOKEN_ALIGN_LEFT:
1555 case WPS_TOKEN_ALIGN_CENTER:
1556 case WPS_TOKEN_ALIGN_RIGHT:
1557 /* remember where the current aligned text started */
1558 switch (cur_align)
1560 case WPS_ALIGN_LEFT:
1561 align->left = cur_align_start;
1562 break;
1564 case WPS_ALIGN_CENTER:
1565 align->center = cur_align_start;
1566 break;
1568 case WPS_ALIGN_RIGHT:
1569 align->right = cur_align_start;
1570 break;
1572 /* start a new alignment */
1573 switch (data->tokens[i].type)
1575 case WPS_TOKEN_ALIGN_LEFT:
1576 cur_align = WPS_ALIGN_LEFT;
1577 break;
1578 case WPS_TOKEN_ALIGN_CENTER:
1579 cur_align = WPS_ALIGN_CENTER;
1580 break;
1581 case WPS_TOKEN_ALIGN_RIGHT:
1582 cur_align = WPS_ALIGN_RIGHT;
1583 break;
1584 default:
1585 break;
1587 *buf++ = 0;
1588 cur_align_start = buf;
1589 break;
1590 case WPS_VIEWPORT_ENABLE:
1592 char label = data->tokens[i].value.i;
1593 int j;
1594 char temp = VP_DRAW_HIDEABLE;
1595 for(j=0;j<data->num_viewports;j++)
1597 temp = VP_DRAW_HIDEABLE;
1598 if ((data->viewports[j].hidden_flags&VP_DRAW_HIDEABLE) &&
1599 (data->viewports[j].label == label))
1601 if (data->viewports[j].hidden_flags&VP_DRAW_WASHIDDEN)
1602 temp |= VP_DRAW_WASHIDDEN;
1603 data->viewports[j].hidden_flags = temp;
1607 break;
1608 default:
1610 /* get the value of the tag and copy it to the buffer */
1611 char *value = get_token_value(gwps, &data->tokens[i],
1612 temp_buf, sizeof(temp_buf), NULL);
1613 if (value)
1615 update = true;
1616 while (*value && (buf < linebuf_end))
1617 *buf++ = *value++;
1619 break;
1624 /* close the current alignment */
1625 switch (cur_align)
1627 case WPS_ALIGN_LEFT:
1628 align->left = cur_align_start;
1629 break;
1631 case WPS_ALIGN_CENTER:
1632 align->center = cur_align_start;
1633 break;
1635 case WPS_ALIGN_RIGHT:
1636 align->right = cur_align_start;
1637 break;
1640 return update;
1643 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1645 struct wps_data *data = gwps->data;
1646 int i;
1647 int subline_idx = wps_subline_index(data, line, subline);
1648 int last_token_idx = wps_last_token_index(data, line, subline);
1650 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1652 for (i = wps_first_token_index(data, line, subline);
1653 i <= last_token_idx; i++)
1655 switch(data->tokens[i].type)
1657 case WPS_TOKEN_CONDITIONAL:
1658 /* place ourselves in the right conditional case */
1659 evaluate_conditional(gwps, &i);
1660 break;
1662 case WPS_TOKEN_CONDITIONAL_OPTION:
1663 /* we've finished in the curent conditional case,
1664 skip to the end of the conditional structure */
1665 i = find_conditional_end(data, i);
1666 break;
1668 case WPS_TOKEN_SUBLINE_TIMEOUT:
1669 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1670 break;
1672 default:
1673 break;
1678 /* Calculates which subline should be displayed for the specified line
1679 Returns true iff the subline must be refreshed */
1680 static bool update_curr_subline(struct gui_wps *gwps, int line)
1682 struct wps_data *data = gwps->data;
1684 int search, search_start, num_sublines;
1685 bool reset_subline;
1686 bool new_subline_refresh;
1687 bool only_one_subline;
1689 num_sublines = data->lines[line].num_sublines;
1690 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1691 new_subline_refresh = false;
1692 only_one_subline = false;
1694 /* if time to advance to next sub-line */
1695 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1696 reset_subline)
1698 /* search all sublines until the next subline with time > 0
1699 is found or we get back to the subline we started with */
1700 if (reset_subline)
1701 search_start = 0;
1702 else
1703 search_start = data->lines[line].curr_subline;
1705 for (search = 0; search < num_sublines; search++)
1707 data->lines[line].curr_subline++;
1709 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1710 if (data->lines[line].curr_subline == num_sublines)
1712 if (data->lines[line].curr_subline == 1)
1713 only_one_subline = true;
1714 data->lines[line].curr_subline = 0;
1717 /* if back where we started after search or
1718 only one subline is defined on the line */
1719 if (((search > 0) &&
1720 (data->lines[line].curr_subline == search_start)) ||
1721 only_one_subline)
1723 /* no other subline with a time > 0 exists */
1724 data->lines[line].subline_expire_time = (reset_subline ?
1725 current_tick :
1726 data->lines[line].subline_expire_time) + 100 * HZ;
1727 break;
1729 else
1731 /* get initial time multiplier for this subline */
1732 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1734 int subline_idx = wps_subline_index(data, line,
1735 data->lines[line].curr_subline);
1737 /* only use this subline if subline time > 0 */
1738 if (data->sublines[subline_idx].time_mult > 0)
1740 new_subline_refresh = true;
1741 data->lines[line].subline_expire_time = (reset_subline ?
1742 current_tick : data->lines[line].subline_expire_time) +
1743 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1744 break;
1750 return new_subline_refresh;
1753 /* Display a line appropriately according to its alignment format.
1754 format_align contains the text, separated between left, center and right.
1755 line is the index of the line on the screen.
1756 scroll indicates whether the line is a scrolling one or not.
1758 static void write_line(struct screen *display,
1759 struct align_pos *format_align,
1760 int line,
1761 bool scroll)
1764 int left_width = 0, left_xpos;
1765 int center_width = 0, center_xpos;
1766 int right_width = 0, right_xpos;
1767 int ypos;
1768 int space_width;
1769 int string_height;
1770 int scroll_width;
1772 /* calculate different string sizes and positions */
1773 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1774 if (format_align->left != 0) {
1775 display->getstringsize((unsigned char *)format_align->left,
1776 &left_width, &string_height);
1779 if (format_align->right != 0) {
1780 display->getstringsize((unsigned char *)format_align->right,
1781 &right_width, &string_height);
1784 if (format_align->center != 0) {
1785 display->getstringsize((unsigned char *)format_align->center,
1786 &center_width, &string_height);
1789 left_xpos = 0;
1790 right_xpos = (display->getwidth() - right_width);
1791 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1793 scroll_width = display->getwidth() - left_xpos;
1795 /* Checks for overlapping strings.
1796 If needed the overlapping strings will be merged, separated by a
1797 space */
1799 /* CASE 1: left and centered string overlap */
1800 /* there is a left string, need to merge left and center */
1801 if ((left_width != 0 && center_width != 0) &&
1802 (left_xpos + left_width + space_width > center_xpos)) {
1803 /* replace the former separator '\0' of left and
1804 center string with a space */
1805 *(--format_align->center) = ' ';
1806 /* calculate the new width and position of the merged string */
1807 left_width = left_width + space_width + center_width;
1808 /* there is no centered string anymore */
1809 center_width = 0;
1811 /* there is no left string, move center to left */
1812 if ((left_width == 0 && center_width != 0) &&
1813 (left_xpos + left_width > center_xpos)) {
1814 /* move the center string to the left string */
1815 format_align->left = format_align->center;
1816 /* calculate the new width and position of the string */
1817 left_width = center_width;
1818 /* there is no centered string anymore */
1819 center_width = 0;
1822 /* CASE 2: centered and right string overlap */
1823 /* there is a right string, need to merge center and right */
1824 if ((center_width != 0 && right_width != 0) &&
1825 (center_xpos + center_width + space_width > right_xpos)) {
1826 /* replace the former separator '\0' of center and
1827 right string with a space */
1828 *(--format_align->right) = ' ';
1829 /* move the center string to the right after merge */
1830 format_align->right = format_align->center;
1831 /* calculate the new width and position of the merged string */
1832 right_width = center_width + space_width + right_width;
1833 right_xpos = (display->getwidth() - right_width);
1834 /* there is no centered string anymore */
1835 center_width = 0;
1837 /* there is no right string, move center to right */
1838 if ((center_width != 0 && right_width == 0) &&
1839 (center_xpos + center_width > right_xpos)) {
1840 /* move the center string to the right string */
1841 format_align->right = format_align->center;
1842 /* calculate the new width and position of the string */
1843 right_width = center_width;
1844 right_xpos = (display->getwidth() - right_width);
1845 /* there is no centered string anymore */
1846 center_width = 0;
1849 /* CASE 3: left and right overlap
1850 There is no center string anymore, either there never
1851 was one or it has been merged in case 1 or 2 */
1852 /* there is a left string, need to merge left and right */
1853 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1854 (left_xpos + left_width + space_width > right_xpos)) {
1855 /* replace the former separator '\0' of left and
1856 right string with a space */
1857 *(--format_align->right) = ' ';
1858 /* calculate the new width and position of the string */
1859 left_width = left_width + space_width + right_width;
1860 /* there is no right string anymore */
1861 right_width = 0;
1863 /* there is no left string, move right to left */
1864 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1865 (left_width > right_xpos)) {
1866 /* move the right string to the left string */
1867 format_align->left = format_align->right;
1868 /* calculate the new width and position of the string */
1869 left_width = right_width;
1870 /* there is no right string anymore */
1871 right_width = 0;
1874 ypos = (line * string_height);
1877 if (scroll && ((left_width > scroll_width) ||
1878 (center_width > scroll_width) ||
1879 (right_width > scroll_width)))
1881 display->puts_scroll(0, line,
1882 (unsigned char *)format_align->left);
1884 else
1886 #ifdef HAVE_LCD_BITMAP
1887 /* clear the line first */
1888 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1889 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1890 display->set_drawmode(DRMODE_SOLID);
1891 #endif
1893 /* Nasty hack: we output an empty scrolling string,
1894 which will reset the scroller for that line */
1895 display->puts_scroll(0, line, (unsigned char *)"");
1897 /* print aligned strings */
1898 if (left_width != 0)
1900 display->putsxy(left_xpos, ypos,
1901 (unsigned char *)format_align->left);
1903 if (center_width != 0)
1905 display->putsxy(center_xpos, ypos,
1906 (unsigned char *)format_align->center);
1908 if (right_width != 0)
1910 display->putsxy(right_xpos, ypos,
1911 (unsigned char *)format_align->right);
1916 /* Refresh the WPS according to refresh_mode. */
1917 bool gui_wps_refresh(struct gui_wps *gwps,
1918 int ffwd_offset,
1919 unsigned char refresh_mode)
1921 struct wps_data *data = gwps->data;
1922 struct screen *display = gwps->display;
1923 struct wps_state *state = gwps->state;
1925 if(!gwps || !data || !state || !display)
1926 return false;
1928 int v, line, i, subline_idx;
1929 unsigned char flags;
1930 char linebuf[MAX_PATH];
1931 unsigned char vp_refresh_mode;
1933 struct align_pos align;
1934 align.left = NULL;
1935 align.center = NULL;
1936 align.right = NULL;
1938 bool update_line, new_subline_refresh;
1940 #ifdef HAVE_LCD_BITMAP
1941 gui_wps_statusbar_draw(gwps, true);
1943 /* to find out wether the peak meter is enabled we
1944 assume it wasn't until we find a line that contains
1945 the peak meter. We can't use peak_meter_enabled itself
1946 because that would mean to turn off the meter thread
1947 temporarily. (That shouldn't matter unless yield
1948 or sleep is called but who knows...)
1950 bool enable_pm = false;
1952 #endif
1954 /* reset to first subline if refresh all flag is set */
1955 if (refresh_mode == WPS_REFRESH_ALL)
1957 display->set_viewport(&data->viewports[0].vp);
1958 display->clear_viewport();
1960 for (i = 0; i <= data->num_lines; i++)
1962 data->lines[i].curr_subline = SUBLINE_RESET;
1966 #ifdef HAVE_LCD_CHARCELLS
1967 for (i = 0; i < 8; i++)
1969 if (data->wps_progress_pat[i] == 0)
1970 data->wps_progress_pat[i] = display->get_locked_pattern();
1972 #endif
1974 if (!state->id3)
1976 display->stop_scroll();
1977 return false;
1980 state->ff_rewind_count = ffwd_offset;
1982 /* disable any viewports which are conditionally displayed */
1983 for (v = 0; v < data->num_viewports; v++)
1985 if (data->viewports[v].hidden_flags&VP_DRAW_HIDEABLE)
1987 if (data->viewports[v].hidden_flags&VP_DRAW_HIDDEN)
1988 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
1989 else
1990 data->viewports[v].hidden_flags |= VP_DRAW_HIDDEN;
1993 for (v = 0; v < data->num_viewports; v++)
1995 display->set_viewport(&data->viewports[v].vp);
1996 vp_refresh_mode = refresh_mode;
1998 #ifdef HAVE_LCD_BITMAP
1999 /* Set images to not to be displayed */
2000 for (i = 0; i < MAX_IMAGES; i++)
2002 data->img[i].display = -1;
2004 #endif
2005 /* dont redraw the viewport if its disabled */
2006 if ((data->viewports[v].hidden_flags&VP_DRAW_HIDDEN))
2008 if (!(data->viewports[v].hidden_flags&VP_DRAW_WASHIDDEN))
2009 display->scroll_stop(&data->viewports[v].vp);
2010 data->viewports[v].hidden_flags |= VP_DRAW_WASHIDDEN;
2011 continue;
2013 else if (((data->viewports[v].hidden_flags&
2014 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
2015 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
2017 vp_refresh_mode = WPS_REFRESH_ALL;
2018 data->viewports[v].hidden_flags = VP_DRAW_HIDEABLE;
2020 if (vp_refresh_mode == WPS_REFRESH_ALL)
2022 display->clear_viewport();
2025 for (line = data->viewports[v].first_line;
2026 line <= data->viewports[v].last_line; line++)
2028 memset(linebuf, 0, sizeof(linebuf));
2029 update_line = false;
2031 /* get current subline for the line */
2032 new_subline_refresh = update_curr_subline(gwps, line);
2034 subline_idx = wps_subline_index(data, line,
2035 data->lines[line].curr_subline);
2036 flags = data->sublines[subline_idx].line_type;
2038 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
2039 || new_subline_refresh)
2041 /* get_line tells us if we need to update the line */
2042 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2043 &align, linebuf, sizeof(linebuf));
2045 #ifdef HAVE_LCD_BITMAP
2046 /* peakmeter */
2047 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
2049 /* the peakmeter should be alone on its line */
2050 update_line = false;
2052 int h = font_get(data->viewports[v].vp.font)->height;
2053 int peak_meter_y = (line - data->viewports[v].first_line)* h;
2055 /* The user might decide to have the peak meter in the last
2056 line so that it is only displayed if no status bar is
2057 visible. If so we neither want do draw nor enable the
2058 peak meter. */
2059 if (peak_meter_y + h <= display->getheight()) {
2060 /* found a line with a peak meter -> remember that we must
2061 enable it later */
2062 enable_pm = true;
2063 peak_meter_screen(gwps->display, 0, peak_meter_y,
2064 MIN(h, display->getheight() - peak_meter_y));
2068 #else /* HAVE_LCD_CHARCELL */
2070 /* progressbar */
2071 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2073 if (data->full_line_progressbar)
2074 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2075 else
2076 draw_player_progress(gwps);
2078 #endif
2080 if (update_line &&
2081 /* conditionals clear the line which means if the %Vd is put into the default
2082 viewport there will be a blank line.
2083 To get around this we dont allow any actual drawing to happen in the
2084 deault vp if other vp's are defined */
2085 ((data->num_viewports>1 && v!=0) || data->num_viewports == 1))
2087 if (flags & WPS_REFRESH_SCROLL)
2089 /* if the line is a scrolling one we don't want to update
2090 too often, so that it has the time to scroll */
2091 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2092 write_line(display, &align, line - data->viewports[v].first_line, true);
2094 else
2095 write_line(display, &align, line - data->viewports[v].first_line, false);
2099 #ifdef HAVE_LCD_BITMAP
2100 /* progressbar */
2101 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2103 if (data->viewports[v].pb)
2104 draw_progressbar(gwps, data->viewports[v].pb);
2106 /* Now display any images in this viewport */
2107 wps_display_images(gwps, &data->viewports[v].vp);
2108 #endif
2111 #ifdef HAVE_LCD_BITMAP
2112 data->peak_meter_enabled = enable_pm;
2113 #endif
2115 /* Restore the default viewport */
2116 display->set_viewport(NULL);
2118 display->update();
2120 #ifdef HAVE_BACKLIGHT
2121 if (global_settings.caption_backlight && state->id3)
2123 /* turn on backlight n seconds before track ends, and turn it off n
2124 seconds into the new track. n == backlight_timeout, or 5s */
2125 int n = global_settings.backlight_timeout * 1000;
2127 if ( n < 1000 )
2128 n = 5000; /* use 5s if backlight is always on or off */
2130 if (((state->id3->elapsed < 1000) ||
2131 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2132 (state->paused == false))
2133 backlight_on();
2135 #endif
2136 #ifdef HAVE_REMOTE_LCD
2137 if (global_settings.remote_caption_backlight && state->id3)
2139 /* turn on remote backlight n seconds before track ends, and turn it
2140 off n seconds into the new track. n == remote_backlight_timeout,
2141 or 5s */
2142 int n = global_settings.remote_backlight_timeout * 1000;
2144 if ( n < 1000 )
2145 n = 5000; /* use 5s if backlight is always on or off */
2147 if (((state->id3->elapsed < 1000) ||
2148 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2149 (state->paused == false))
2150 remote_backlight_on();
2152 #endif
2154 return true;