Hopefully fix the compile errors.
[Rockbox.git] / apps / gui / gwps-common.c
blob7f1c52c7bf92e1074590e3237ef1f5b4e45a4f47
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Nicolas Pennequin
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
19 #include "gwps-common.h"
20 #include "font.h"
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include "system.h"
25 #include "settings.h"
26 #include "rbunicode.h"
27 #include "rtc.h"
28 #include "audio.h"
29 #include "status.h"
30 #include "power.h"
31 #include "powermgmt.h"
32 #include "sound.h"
33 #include "debug.h"
34 #ifdef HAVE_LCD_CHARCELLS
35 #include "hwcompat.h"
36 #endif
37 #include "abrepeat.h"
38 #include "mp3_playback.h"
39 #include "backlight.h"
40 #include "lang.h"
41 #include "misc.h"
42 #include "splash.h"
43 #include "scrollbar.h"
44 #include "led.h"
45 #include "lcd.h"
46 #ifdef HAVE_LCD_BITMAP
47 #include "peakmeter.h"
48 /* Image stuff */
49 #include "bmp.h"
50 #include "atoi.h"
51 #include "albumart.h"
52 #endif
53 #include "dsp.h"
54 #include "action.h"
55 #include "cuesheet.h"
57 #if (LCD_DEPTH > 1) || (defined(HAVE_LCD_REMOTE) && (LCD_REMOTE_DEPTH > 1))
58 #include "backdrop.h"
59 #endif
61 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
62 /* 3% of 30min file == 54s step size */
63 #define MIN_FF_REWIND_STEP 500
65 /* draws the statusbar on the given wps-screen */
66 #ifdef HAVE_LCD_BITMAP
67 void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
69 bool draw = global_settings.statusbar;
71 if (wps->data->wps_sb_tag)
72 draw = wps->data->show_sb_on_wps;
74 if (draw)
75 gui_statusbar_draw(wps->statusbar, force);
77 #else
78 #define gui_wps_statusbar_draw(wps, force) \
79 gui_statusbar_draw((wps)->statusbar, (force))
80 #endif
82 /* fades the volume */
83 void fade(bool fade_in)
85 int fp_global_vol = global_settings.volume << 8;
86 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
87 int fp_step = (fp_global_vol - fp_min_vol) / 30;
89 if (fade_in) {
90 /* fade in */
91 int fp_volume = fp_min_vol;
93 /* zero out the sound */
94 sound_set_volume(fp_min_vol >> 8);
96 sleep(HZ/10); /* let audio thread run */
97 audio_resume();
99 while (fp_volume < fp_global_vol - fp_step) {
100 fp_volume += fp_step;
101 sound_set_volume(fp_volume >> 8);
102 sleep(1);
104 sound_set_volume(global_settings.volume);
106 else {
107 /* fade out */
108 int fp_volume = fp_global_vol;
110 while (fp_volume > fp_min_vol + fp_step) {
111 fp_volume -= fp_step;
112 sound_set_volume(fp_volume >> 8);
113 sleep(1);
115 audio_pause();
117 #if CONFIG_CODEC != SWCODEC
118 #ifndef SIMULATOR
119 /* let audio thread run and wait for the mas to run out of data */
120 while (!mp3_pause_done())
121 #endif
122 sleep(HZ/10);
123 #endif
125 /* reset volume to what it was before the fade */
126 sound_set_volume(global_settings.volume);
130 /* return true if screen restore is needed
131 return false otherwise
133 bool update_onvol_change(struct gui_wps * gwps)
135 gui_wps_statusbar_draw(gwps, false);
136 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
138 #ifdef HAVE_LCD_CHARCELLS
139 gui_splash(gwps->display, 0, "Vol: %3d dB",
140 sound_val2phys(SOUND_VOLUME, global_settings.volume));
141 return true;
142 #endif
143 return false;
146 bool ffwd_rew(int button)
148 static const uint16_t ff_rew_steps[] = {
149 1000, 2000, 3000, 4000,
150 5000, 6000, 8000, 10000,
151 15000, 20000, 25000, 30000,
152 45000, 60000
155 unsigned int step = 0; /* current ff/rewind step */
156 unsigned int max_step = 0; /* maximum ff/rewind step */
157 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
158 int direction = -1; /* forward=1 or backward=-1 */
159 long accel_tick = 0; /* next time at which to bump the step size */
160 bool exit = false;
161 bool usb = false;
162 int i = 0;
164 if (button == ACTION_NONE)
166 status_set_ffmode(0);
167 return usb;
169 while (!exit)
171 switch ( button )
173 case ACTION_WPS_SEEKFWD:
174 direction = 1;
175 case ACTION_WPS_SEEKBACK:
176 if (wps_state.ff_rewind)
178 if (direction == 1)
180 /* fast forwarding, calc max step relative to end */
181 max_step = (wps_state.id3->length -
182 (wps_state.id3->elapsed +
183 ff_rewind_count)) *
184 FF_REWIND_MAX_PERCENT / 100;
186 else
188 /* rewinding, calc max step relative to start */
189 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
190 FF_REWIND_MAX_PERCENT / 100;
193 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
195 if (step > max_step)
196 step = max_step;
198 ff_rewind_count += step * direction;
200 if (global_settings.ff_rewind_accel != 0 &&
201 current_tick >= accel_tick)
203 step *= 2;
204 accel_tick = current_tick +
205 global_settings.ff_rewind_accel*HZ;
208 else
210 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
211 wps_state.id3 && wps_state.id3->length )
213 if (!wps_state.paused)
214 #if (CONFIG_CODEC == SWCODEC)
215 audio_pre_ff_rewind();
216 #else
217 audio_pause();
218 #endif
219 #if CONFIG_KEYPAD == PLAYER_PAD
220 FOR_NB_SCREENS(i)
221 gui_wps[i].display->stop_scroll();
222 #endif
223 if (direction > 0)
224 status_set_ffmode(STATUS_FASTFORWARD);
225 else
226 status_set_ffmode(STATUS_FASTBACKWARD);
228 wps_state.ff_rewind = true;
230 step = ff_rew_steps[global_settings.ff_rewind_min_step];
232 accel_tick = current_tick +
233 global_settings.ff_rewind_accel*HZ;
235 else
236 break;
239 if (direction > 0) {
240 if ((wps_state.id3->elapsed + ff_rewind_count) >
241 wps_state.id3->length)
242 ff_rewind_count = wps_state.id3->length -
243 wps_state.id3->elapsed;
245 else {
246 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
247 ff_rewind_count = -wps_state.id3->elapsed;
250 FOR_NB_SCREENS(i)
251 gui_wps_refresh(&gui_wps[i],
252 (wps_state.wps_time_countup == false)?
253 ff_rewind_count:-ff_rewind_count,
254 WPS_REFRESH_PLAYER_PROGRESS |
255 WPS_REFRESH_DYNAMIC);
257 break;
259 case ACTION_WPS_STOPSEEK:
260 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
261 audio_ff_rewind(wps_state.id3->elapsed);
262 ff_rewind_count = 0;
263 wps_state.ff_rewind = false;
264 status_set_ffmode(0);
265 #if (CONFIG_CODEC != SWCODEC)
266 if (!wps_state.paused)
267 audio_resume();
268 #endif
269 #ifdef HAVE_LCD_CHARCELLS
270 gui_wps_display();
271 #endif
272 exit = true;
273 break;
275 default:
276 if(default_event_handler(button) == SYS_USB_CONNECTED) {
277 status_set_ffmode(0);
278 usb = true;
279 exit = true;
281 break;
283 if (!exit)
284 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
286 return usb;
289 bool gui_wps_display(void)
291 int i;
292 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
294 global_status.resume_index = -1;
295 #ifdef HAVE_LCD_BITMAP
296 gui_syncstatusbar_draw(&statusbars, true);
297 #endif
298 gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST));
299 return true;
301 else
303 FOR_NB_SCREENS(i)
305 gui_wps[i].display->clear_display();
306 if (!gui_wps[i].data->wps_loaded) {
307 if ( !gui_wps[i].data->num_tokens ) {
308 /* set the default wps for the main-screen */
309 if(i == 0)
311 #ifdef HAVE_LCD_BITMAP
312 #if LCD_DEPTH > 1
313 unload_wps_backdrop();
314 #endif
315 wps_data_load(gui_wps[i].data,
316 "%s%?it<%?in<%in. |>%it|%fn>\n"
317 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
318 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
319 "\n"
320 "%al%pc/%pt%ar[%pp:%pe]\n"
321 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
322 "%pb\n"
323 "%pm\n", false);
324 #else
325 wps_data_load(gui_wps[i].data,
326 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
327 "%pc%?ps<*|/>%pt\n", false);
328 #endif
330 #if NB_SCREENS == 2
331 /* set the default wps for the remote-screen */
332 else if(i == 1)
334 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
335 unload_remote_wps_backdrop();
336 #endif
337 wps_data_load(gui_wps[i].data,
338 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
339 "%s%?it<%?in<%in. |>%it|%fn>\n"
340 "%al%pc/%pt%ar[%pp:%pe]\n"
341 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
342 "%pb\n", false);
344 #endif
349 yield();
350 FOR_NB_SCREENS(i)
352 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
354 return false;
357 bool update(struct gui_wps *gwps)
359 bool track_changed = audio_has_changed_track();
360 bool retcode = false;
362 gwps->state->nid3 = audio_next_track();
363 if (track_changed)
365 gwps->display->stop_scroll();
366 gwps->state->id3 = audio_current_track();
368 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
369 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
371 /* the current cuesheet isn't the right one any more */
373 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
374 /* We have the new cuesheet in memory (temp_cue),
375 let's make it the current one ! */
376 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
378 else {
379 /* We need to parse the new cuesheet */
381 char cuepath[MAX_PATH];
383 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
384 parse_cuesheet(cuepath, curr_cue))
386 gwps->state->id3->cuesheet_type = 1;
387 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
391 cue_spoof_id3(curr_cue, gwps->state->id3);
394 if (gui_wps_display())
395 retcode = true;
396 else{
397 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
400 if (gwps->state->id3)
402 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
403 sizeof(gwps->state->current_track_path));
404 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
408 if (gwps->state->id3)
410 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
411 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
412 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
413 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
415 /* We've changed tracks within the cuesheet :
416 we need to update the ID3 info and refresh the WPS */
418 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
419 cue_spoof_id3(curr_cue, gwps->state->id3);
421 gwps->display->stop_scroll();
422 if (gui_wps_display())
423 retcode = true;
424 else
425 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
427 else
428 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
431 gui_wps_statusbar_draw(gwps, false);
433 return retcode;
437 void display_keylock_text(bool locked)
439 char* s;
440 int i;
441 FOR_NB_SCREENS(i)
442 gui_wps[i].display->stop_scroll();
444 if(locked)
445 s = str(LANG_KEYLOCK_ON);
446 else
447 s = str(LANG_KEYLOCK_OFF);
448 gui_syncsplash(HZ, s);
451 #ifdef HAVE_LCD_BITMAP
453 static void draw_progressbar(struct gui_wps *gwps, int line)
455 struct wps_data *data = gwps->data;
456 struct screen *display = gwps->display;
457 struct wps_state *state = gwps->state;
458 int h = font_get(FONT_UI)->height;
460 int sb_y;
461 if (data->progress_top < 0)
462 sb_y = line*h + display->getymargin() +
463 ((h > data->progress_height + 1)
464 ? (h - data->progress_height) / 2 : 1);
465 else
466 sb_y = data->progress_top;
468 if (!data->progress_end)
469 data->progress_end=display->width;
471 if (gwps->data->progressbar.have_bitmap_pb)
472 gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
473 data->progress_start, sb_y,
474 data->progress_end-data->progress_start,
475 data->progressbar.bm.height,
476 state->id3->length ? state->id3->length : 1, 0,
477 state->id3->length ? state->id3->elapsed
478 + state->ff_rewind_count : 0,
479 HORIZONTAL);
480 else
481 gui_scrollbar_draw(display, data->progress_start, sb_y,
482 data->progress_end-data->progress_start,
483 data->progress_height,
484 state->id3->length ? state->id3->length : 1, 0,
485 state->id3->length ? state->id3->elapsed
486 + state->ff_rewind_count : 0,
487 HORIZONTAL);
489 #ifdef AB_REPEAT_ENABLE
490 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
491 ab_draw_markers(display, state->id3->length,
492 data->progress_start, data->progress_end, sb_y,
493 data->progress_height);
494 #endif
496 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
497 cue_draw_markers(display, state->id3->length,
498 data->progress_start, data->progress_end,
499 sb_y+1, data->progress_height-2);
502 /* clears the area where the image was shown */
503 static void clear_image_pos(struct gui_wps *gwps, int n)
505 if(!gwps)
506 return;
507 struct wps_data *data = gwps->data;
508 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
509 gwps->display->fillrect(data->img[n].x, data->img[n].y,
510 data->img[n].bm.width, data->img[n].bm.height);
511 gwps->display->set_drawmode(DRMODE_SOLID);
514 static void wps_draw_image(struct gui_wps *gwps, int n)
516 struct screen *display = gwps->display;
517 struct wps_data *data = gwps->data;
518 if(data->img[n].always_display)
519 display->set_drawmode(DRMODE_FG);
520 else
521 display->set_drawmode(DRMODE_SOLID);
523 #if LCD_DEPTH > 1
524 if(data->img[n].bm.format == FORMAT_MONO) {
525 #endif
526 display->mono_bitmap(data->img[n].bm.data, data->img[n].x,
527 data->img[n].y, data->img[n].bm.width,
528 data->img[n].bm.height);
529 #if LCD_DEPTH > 1
530 } else {
531 display->transparent_bitmap((fb_data *)data->img[n].bm.data,
532 data->img[n].x,
533 data->img[n].y, data->img[n].bm.width,
534 data->img[n].bm.height);
536 #endif
539 static void wps_display_images(struct gui_wps *gwps)
541 if(!gwps || !gwps->data || !gwps->display)
542 return;
544 int n;
545 struct wps_data *data = gwps->data;
546 struct screen *display = gwps->display;
548 for (n = 0; n < MAX_IMAGES; n++)
550 if (data->img[n].loaded &&
551 (data->img[n].display || data->img[n].always_display))
553 wps_draw_image(gwps, n);
556 display->set_drawmode(DRMODE_SOLID);
559 #else /* HAVE_LCD_CHARCELL */
561 static bool draw_player_progress(struct gui_wps *gwps)
563 struct wps_state *state = gwps->state;
564 struct screen *display = gwps->display;
565 unsigned char progress_pattern[7];
566 int pos = 0;
567 int i;
569 if (!state->id3)
570 return false;
572 if (state->id3->length)
573 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
574 / state->id3->length;
576 for (i = 0; i < 7; i++, pos -= 5)
578 if (pos <= 0)
579 progress_pattern[i] = 0x1f;
580 else if (pos >= 5)
581 progress_pattern[i] = 0x00;
582 else
583 progress_pattern[i] = 0x1f >> pos;
586 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
587 return true;
590 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
592 static const unsigned char numbers[10][4] = {
593 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
594 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
595 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
596 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
597 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
598 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
599 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
600 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
601 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
602 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
605 struct wps_state *state = gwps->state;
606 struct screen *display = gwps->display;
607 struct wps_data *data = gwps->data;
608 unsigned char progress_pattern[7];
609 char timestr[10];
610 int time;
611 int time_idx = 0;
612 int pos = 0;
613 int pat_idx = 1;
614 int digit, i, j;
615 bool softchar;
617 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
618 return;
620 time = state->id3->elapsed + state->ff_rewind_count;
621 if (state->id3->length)
622 pos = 55 * time / state->id3->length;
624 memset(timestr, 0, sizeof(timestr));
625 format_time(timestr, sizeof(timestr)-2, time);
626 timestr[strlen(timestr)] = ':'; /* always safe */
628 for (i = 0; i < 11; i++, pos -= 5)
630 softchar = false;
631 memset(progress_pattern, 0, sizeof(progress_pattern));
633 if ((digit = timestr[time_idx]))
635 softchar = true;
636 digit -= '0';
638 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
640 memcpy(progress_pattern, numbers[digit], 4);
641 time_idx += 2;
643 else /* tens, shifted right */
645 for (j = 0; j < 4; j++)
646 progress_pattern[j] = numbers[digit][j] >> 1;
648 if (time_idx > 0) /* not the first group, add colon in front */
650 progress_pattern[1] |= 0x10;
651 progress_pattern[3] |= 0x10;
653 time_idx++;
656 if (pos >= 5)
657 progress_pattern[5] = progress_pattern[6] = 0x1f;
660 if (pos > 0 && pos < 5)
662 softchar = true;
663 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
666 if (softchar && pat_idx < 8)
668 display->define_pattern(data->wps_progress_pat[pat_idx],
669 progress_pattern);
670 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
671 pat_idx++;
673 else if (pos <= 0)
674 buf = utf8encode(' ', buf);
675 else
676 buf = utf8encode(0xe115, buf); /* 2/7 _ */
678 *buf = '\0';
681 #endif /* HAVE_LCD_CHARCELL */
683 /* Extract a part from a path.
685 * buf - buffer extract part to.
686 * buf_size - size of buffer.
687 * path - path to extract from.
688 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
689 * parent of parent, etc.
691 * Returns buf if the desired level was found, NULL otherwise.
693 static char* get_dir(char* buf, int buf_size, const char* path, int level)
695 const char* sep;
696 const char* last_sep;
697 int len;
699 sep = path + strlen(path);
700 last_sep = sep;
702 while (sep > path)
704 if ('/' == *(--sep))
706 if (!level)
707 break;
709 level--;
710 last_sep = sep - 1;
714 if (level || (last_sep <= sep))
715 return NULL;
717 len = MIN(last_sep - sep, buf_size - 1);
718 strncpy(buf, sep + 1, len);
719 buf[len] = 0;
720 return buf;
723 /* Return the tag found at index i and write its value in buf.
724 The return value is buf if the tag had a value, or NULL if not.
726 intval is used with conditionals/enums: when this function is called,
727 intval should contain the number of options in the conditional/enum.
728 When this function returns, intval is -1 if the tag is non numeric or,
729 if the tag is numeric, intval is the enum case we want to go to.
730 When not treating a conditional/enum, intval should be NULL.
732 static char *get_token_value(struct gui_wps *gwps,
733 struct wps_token *token,
734 char *buf, int buf_size,
735 int *intval)
737 if (!gwps)
738 return NULL;
740 struct wps_data *data = gwps->data;
741 struct wps_state *state = gwps->state;
743 if (!data || !state)
744 return NULL;
746 struct mp3entry *id3;
748 if (token->next)
749 id3 = state->nid3;
750 else
751 id3 = state->id3;
753 if (!id3)
754 return NULL;
756 #if CONFIG_RTC
757 struct tm* tm = NULL;
759 /* if the token is an RTC one, update the time
760 and do the necessary checks */
761 if (token->type >= WPS_TOKENS_RTC_BEGIN
762 && token->type <= WPS_TOKENS_RTC_END)
764 tm = get_time();
766 if (!valid_time(tm))
767 return NULL;
769 #endif
771 int limit = 1;
772 if (intval)
774 limit = *intval;
775 *intval = -1;
778 switch (token->type)
780 case WPS_TOKEN_CHARACTER:
781 return &(token->value.c);
783 case WPS_TOKEN_STRING:
784 return data->strings[token->value.i];
786 case WPS_TOKEN_TRACK_TIME_ELAPSED:
787 format_time(buf, buf_size,
788 id3->elapsed + state->ff_rewind_count);
789 return buf;
791 case WPS_TOKEN_TRACK_TIME_REMAINING:
792 format_time(buf, buf_size,
793 id3->length - id3->elapsed -
794 state->ff_rewind_count);
795 return buf;
797 case WPS_TOKEN_TRACK_LENGTH:
798 format_time(buf, buf_size, id3->length);
799 return buf;
801 case WPS_TOKEN_PLAYLIST_ENTRIES:
802 snprintf(buf, buf_size, "%d", playlist_amount());
803 return buf;
805 case WPS_TOKEN_PLAYLIST_NAME:
806 return playlist_name(NULL, buf, buf_size);
808 case WPS_TOKEN_PLAYLIST_POSITION:
809 snprintf(buf, buf_size, "%d",
810 playlist_get_display_index());
811 return buf;
813 case WPS_TOKEN_PLAYLIST_SHUFFLE:
814 if ( global_settings.playlist_shuffle )
815 return "s";
816 else
817 return NULL;
818 break;
820 case WPS_TOKEN_VOLUME:
821 snprintf(buf, buf_size, "%d", global_settings.volume);
822 if (intval)
824 if (global_settings.volume == sound_min(SOUND_VOLUME))
826 *intval = 1;
828 else if (global_settings.volume == 0)
830 *intval = limit - 1;
832 else if (global_settings.volume > 0)
834 *intval = limit;
836 else
838 *intval = (limit - 3) * (global_settings.volume
839 - sound_min(SOUND_VOLUME) - 1)
840 / (-1 - sound_min(SOUND_VOLUME)) + 2;
843 return buf;
845 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
846 if (id3->length <= 0)
847 return NULL;
849 if (intval)
851 *intval = limit * (id3->elapsed + state->ff_rewind_count)
852 / id3->length + 1;
854 snprintf(buf, buf_size, "%d",
855 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
856 return buf;
858 case WPS_TOKEN_METADATA_ARTIST:
859 return id3->artist;
861 case WPS_TOKEN_METADATA_COMPOSER:
862 return id3->composer;
864 case WPS_TOKEN_METADATA_ALBUM:
865 return id3->album;
867 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
868 return id3->albumartist;
870 case WPS_TOKEN_METADATA_GROUPING:
871 return id3->grouping;
873 case WPS_TOKEN_METADATA_GENRE:
874 return id3->genre_string;
876 case WPS_TOKEN_METADATA_DISC_NUMBER:
877 if (id3->disc_string)
878 return id3->disc_string;
879 if (id3->discnum) {
880 snprintf(buf, buf_size, "%d", id3->discnum);
881 return buf;
883 return NULL;
885 case WPS_TOKEN_METADATA_TRACK_NUMBER:
886 if (id3->track_string)
887 return id3->track_string;
889 if (id3->tracknum) {
890 snprintf(buf, buf_size, "%d", id3->tracknum);
891 return buf;
893 return NULL;
895 case WPS_TOKEN_METADATA_TRACK_TITLE:
896 return id3->title;
898 case WPS_TOKEN_METADATA_VERSION:
899 switch (id3->id3version)
901 case ID3_VER_1_0:
902 return "1";
904 case ID3_VER_1_1:
905 return "1.1";
907 case ID3_VER_2_2:
908 return "2.2";
910 case ID3_VER_2_3:
911 return "2.3";
913 case ID3_VER_2_4:
914 return "2.4";
916 default:
917 return NULL;
920 case WPS_TOKEN_METADATA_YEAR:
921 if( id3->year_string )
922 return id3->year_string;
924 if (id3->year) {
925 snprintf(buf, buf_size, "%d", id3->year);
926 return buf;
928 return NULL;
930 case WPS_TOKEN_METADATA_COMMENT:
931 return id3->comment;
933 #ifdef HAVE_ALBUMART
934 case WPS_TOKEN_ALBUMART_DISPLAY:
935 draw_album_art(gwps, audio_current_aa_hid(), false);
936 return NULL;
938 case WPS_TOKEN_ALBUMART_FOUND:
939 if (audio_current_aa_hid() >= 0) {
940 snprintf(buf, buf_size, "C");
941 return buf;
943 return NULL;
944 #endif
946 case WPS_TOKEN_FILE_BITRATE:
947 if(id3->bitrate)
948 snprintf(buf, buf_size, "%d", id3->bitrate);
949 else
950 snprintf(buf, buf_size, "?");
951 return buf;
953 case WPS_TOKEN_FILE_CODEC:
954 if (intval)
956 if(id3->codectype == AFMT_UNKNOWN)
957 *intval = AFMT_NUM_CODECS;
958 else
959 *intval = id3->codectype;
961 return id3_get_codec(id3);
963 case WPS_TOKEN_FILE_FREQUENCY:
964 snprintf(buf, buf_size, "%ld", id3->frequency);
965 return buf;
967 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
968 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
969 if ((id3->frequency % 1000) < 100)
970 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
971 else
972 snprintf(buf, buf_size, "%ld.%d",
973 id3->frequency / 1000,
974 (id3->frequency % 1000) / 100);
975 return buf;
977 case WPS_TOKEN_FILE_NAME:
978 if (get_dir(buf, buf_size, id3->path, 0)) {
979 /* Remove extension */
980 char* sep = strrchr(buf, '.');
981 if (NULL != sep) {
982 *sep = 0;
984 return buf;
986 else {
987 return NULL;
990 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
991 return get_dir(buf, buf_size, id3->path, 0);
993 case WPS_TOKEN_FILE_PATH:
994 return id3->path;
996 case WPS_TOKEN_FILE_SIZE:
997 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
998 return buf;
1000 case WPS_TOKEN_FILE_VBR:
1001 return id3->vbr ? "(avg)" : NULL;
1003 case WPS_TOKEN_FILE_DIRECTORY:
1004 return get_dir(buf, buf_size, id3->path, token->value.i);
1006 case WPS_TOKEN_BATTERY_PERCENT:
1008 int l = battery_level();
1010 if (intval)
1012 limit = MAX(limit, 2);
1013 if (l > -1) {
1014 /* First enum is used for "unknown level". */
1015 *intval = (limit - 1) * l / 100 + 2;
1016 } else {
1017 *intval = 1;
1021 if (l > -1) {
1022 snprintf(buf, buf_size, "%d", l);
1023 return buf;
1024 } else {
1025 return "?";
1029 case WPS_TOKEN_BATTERY_VOLTS:
1031 unsigned int v = battery_voltage();
1032 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1033 return buf;
1036 case WPS_TOKEN_BATTERY_TIME:
1038 int t = battery_time();
1039 if (t >= 0)
1040 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1041 else
1042 strncpy(buf, "?h ?m", buf_size);
1043 return buf;
1046 #if CONFIG_CHARGING
1047 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1049 if(charger_input_state==CHARGER)
1050 return "p";
1051 else
1052 return NULL;
1054 #endif
1055 #if CONFIG_CHARGING >= CHARGING_MONITOR
1056 case WPS_TOKEN_BATTERY_CHARGING:
1058 if (charge_state == CHARGING || charge_state == TOPOFF) {
1059 return "c";
1060 } else {
1061 return NULL;
1064 #endif
1065 case WPS_TOKEN_BATTERY_SLEEPTIME:
1067 if (get_sleep_timer() == 0)
1068 return NULL;
1069 else
1071 format_time(buf, buf_size, get_sleep_timer() * 1000);
1072 return buf;
1076 case WPS_TOKEN_PLAYBACK_STATUS:
1078 int status = audio_status();
1079 int mode = 1;
1080 if (status == AUDIO_STATUS_PLAY && \
1081 !(status & AUDIO_STATUS_PAUSE))
1082 mode = 2;
1083 if (audio_status() & AUDIO_STATUS_PAUSE && \
1084 (! status_get_ffmode()))
1085 mode = 3;
1086 if (status_get_ffmode() == STATUS_FASTFORWARD)
1087 mode = 4;
1088 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1089 mode = 5;
1091 if (intval) {
1092 *intval = mode;
1095 snprintf(buf, buf_size, "%d", mode);
1096 return buf;
1099 case WPS_TOKEN_REPEAT_MODE:
1100 if (intval)
1101 *intval = global_settings.repeat_mode + 1;
1102 snprintf(buf, buf_size, "%d", *intval);
1103 return buf;
1105 #if CONFIG_RTC
1106 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1107 /* d: day of month (01..31) */
1108 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1109 return buf;
1111 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1112 /* e: day of month, blank padded ( 1..31) */
1113 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1114 return buf;
1116 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1117 /* H: hour (00..23) */
1118 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1119 return buf;
1121 case WPS_TOKEN_RTC_HOUR_24:
1122 /* k: hour ( 0..23) */
1123 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1124 return buf;
1126 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1127 /* I: hour (01..12) */
1128 snprintf(buf, buf_size, "%02d",
1129 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1130 return buf;
1132 case WPS_TOKEN_RTC_HOUR_12:
1133 /* l: hour ( 1..12) */
1134 snprintf(buf, buf_size, "%2d",
1135 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1136 return buf;
1138 case WPS_TOKEN_RTC_MONTH:
1139 /* m: month (01..12) */
1140 if (intval)
1141 *intval = tm->tm_mon + 1;
1142 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1143 return buf;
1145 case WPS_TOKEN_RTC_MINUTE:
1146 /* M: minute (00..59) */
1147 snprintf(buf, buf_size, "%02d", tm->tm_min);
1148 return buf;
1150 case WPS_TOKEN_RTC_SECOND:
1151 /* S: second (00..59) */
1152 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1153 return buf;
1155 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1156 /* y: last two digits of year (00..99) */
1157 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1158 return buf;
1160 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1161 /* Y: year (1970...) */
1162 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1163 return buf;
1165 case WPS_TOKEN_RTC_AM_PM_UPPER:
1166 /* p: upper case AM or PM indicator */
1167 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1168 return buf;
1170 case WPS_TOKEN_RTC_AM_PM_LOWER:
1171 /* P: lower case am or pm indicator */
1172 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1173 return buf;
1175 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1176 /* a: abbreviated weekday name (Sun..Sat) */
1177 snprintf(buf, buf_size, "%s",str(LANG_WEEKDAY_SUNDAY + tm->tm_wday));
1178 return buf;
1180 case WPS_TOKEN_RTC_MONTH_NAME:
1181 /* b: abbreviated month name (Jan..Dec) */
1182 snprintf(buf, buf_size, "%s",str(LANG_MONTH_JANUARY + tm->tm_mon));
1183 return buf;
1185 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1186 /* u: day of week (1..7); 1 is Monday */
1187 if (intval)
1188 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1189 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1190 return buf;
1192 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1193 /* w: day of week (0..6); 0 is Sunday */
1194 if (intval)
1195 *intval = tm->tm_wday + 1;
1196 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1197 return buf;
1198 #else
1199 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1200 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1201 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1202 case WPS_TOKEN_RTC_HOUR_24:
1203 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1204 case WPS_TOKEN_RTC_HOUR_12:
1205 case WPS_TOKEN_RTC_MONTH:
1206 case WPS_TOKEN_RTC_MINUTE:
1207 case WPS_TOKEN_RTC_SECOND:
1208 case WPS_TOKEN_RTC_AM_PM_UPPER:
1209 case WPS_TOKEN_RTC_AM_PM_LOWER:
1210 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1211 strncpy(buf, "--", buf_size);
1212 return buf;
1213 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1214 strncpy(buf, "----", buf_size);
1215 return buf;
1216 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1217 case WPS_TOKEN_RTC_MONTH_NAME:
1218 strncpy(buf, "---", buf_size);
1219 return buf;
1220 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1221 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1222 strncpy(buf, "-", buf_size);
1223 return buf;
1224 #endif
1226 #ifdef HAVE_LCD_CHARCELLS
1227 case WPS_TOKEN_PROGRESSBAR:
1229 char *end = utf8encode(data->wps_progress_pat[0], buf);
1230 *end = '\0';
1231 return buf;
1234 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1235 if(is_new_player())
1237 /* we need 11 characters (full line) for
1238 progress-bar */
1239 snprintf(buf, buf_size, " ");
1241 else
1243 /* Tell the user if we have an OldPlayer */
1244 snprintf(buf, buf_size, " <Old LCD> ");
1246 return buf;
1247 #endif
1249 #ifdef HAVE_TAGCACHE
1250 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1251 if (intval) {
1252 *intval = id3->playcount + 1;
1254 snprintf(buf, buf_size, "%ld", id3->playcount);
1255 return buf;
1257 case WPS_TOKEN_DATABASE_RATING:
1258 if (intval) {
1259 *intval = id3->rating + 1;
1261 snprintf(buf, buf_size, "%d", id3->rating);
1262 return buf;
1264 case WPS_TOKEN_DATABASE_AUTOSCORE:
1265 if (intval)
1266 *intval = id3->score + 1;
1268 snprintf(buf, buf_size, "%d", id3->score);
1269 return buf;
1270 #endif
1272 #if (CONFIG_CODEC == SWCODEC)
1273 case WPS_TOKEN_CROSSFADE:
1274 if (intval)
1275 *intval = global_settings.crossfade + 1;
1276 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1277 return buf;
1279 case WPS_TOKEN_REPLAYGAIN:
1281 int val;
1283 if (global_settings.replaygain == 0)
1284 val = 1; /* off */
1285 else
1287 int type =
1288 get_replaygain_mode(id3->track_gain_string != NULL,
1289 id3->album_gain_string != NULL);
1290 if (type < 0)
1291 val = 6; /* no tag */
1292 else
1293 val = type + 2;
1295 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1296 val += 2;
1299 if (intval)
1300 *intval = val;
1302 switch (val)
1304 case 1:
1305 case 6:
1306 return "+0.00 dB";
1307 break;
1308 case 2:
1309 case 4:
1310 strncpy(buf, id3->track_gain_string, buf_size);
1311 break;
1312 case 3:
1313 case 5:
1314 strncpy(buf, id3->album_gain_string, buf_size);
1315 break;
1317 return buf;
1319 #endif /* (CONFIG_CODEC == SWCODEC) */
1321 #if (CONFIG_CODEC != MAS3507D)
1322 case WPS_TOKEN_SOUND_PITCH:
1324 int val = sound_get_pitch();
1325 snprintf(buf, buf_size, "%d.%d",
1326 val / 10, val % 10);
1327 return buf;
1329 #endif
1331 case WPS_TOKEN_MAIN_HOLD:
1332 #ifdef HAS_BUTTON_HOLD
1333 if (button_hold())
1334 #else
1335 if (is_keys_locked())
1336 #endif /*hold switch or softlock*/
1337 return "h";
1338 else
1339 return NULL;
1341 #ifdef HAS_REMOTE_BUTTON_HOLD
1342 case WPS_TOKEN_REMOTE_HOLD:
1343 if (remote_button_hold())
1344 return "r";
1345 else
1346 return NULL;
1347 #endif
1349 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1350 case WPS_TOKEN_VLED_HDD:
1351 if(led_read(HZ/2))
1352 return "h";
1353 else
1354 return NULL;
1355 #endif
1357 #ifdef HAVE_LCD_BITMAP
1358 case WPS_TOKEN_LEFTMARGIN:
1359 gwps->display->setmargins(token->value.i,
1360 gwps->display->getymargin());
1361 return NULL;
1362 #endif
1364 default:
1365 return NULL;
1369 /* Return the index to the end token for the conditional token at index.
1370 The conditional token can be either a start token or a separator
1371 (i.e. option) token.
1373 static int find_conditional_end(struct wps_data *data, int index)
1375 int ret = index;
1376 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1377 ret = data->tokens[ret].value.i;
1379 /* ret now is the index to the end token for the conditional. */
1380 return ret;
1383 /* Return the index of the appropriate case for the conditional
1384 that starts at cond_index.
1386 static int evaluate_conditional(struct gui_wps *gwps, int cond_index)
1388 if (!gwps)
1389 return 0;
1391 struct wps_data *data = gwps->data;
1393 int ret, i;
1394 char result[128], *value;
1395 int num_options = data->tokens[cond_index].value.i;
1397 /* treat ?xx<true> constructs as if they had 2 options. */
1398 if (num_options < 2)
1399 num_options = 2;
1401 int intval = num_options;
1402 /* get_token_value needs to know the number of options in the enum */
1403 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1404 result, sizeof(result), &intval);
1406 /* intval is now the number of the enum option we want to read,
1407 starting from 1. If intval is -1, we check if value is empty. */
1408 if (intval == -1)
1409 intval = (value && *value) ? 1 : num_options;
1410 else if (intval > num_options || intval < 1)
1411 intval = num_options;
1413 /* skip to the right enum case */
1414 int next = cond_index + 2;
1415 for (i = 1; i < intval; i++)
1417 next = data->tokens[next].value.i;
1419 ret = next;
1421 #ifdef HAVE_LCD_BITMAP
1422 /* clear all pictures in the conditional */
1423 for (i = 0; i < MAX_IMAGES; i++)
1425 if (data->img[i].cond_index == cond_index)
1426 clear_image_pos(gwps, i);
1428 #endif
1430 #ifdef HAVE_ALBUMART
1431 if (data->wps_uses_albumart != WPS_ALBUMART_NONE &&
1432 data->albumart_cond_index == cond_index)
1434 draw_album_art(gwps, audio_current_aa_hid(), true);
1436 #endif
1438 return ret;
1441 /* Read a (sub)line to the given alignment format buffer.
1442 linebuf is the buffer where the data is actually stored.
1443 align is the alignment format that'll be used to display the text.
1444 The return value indicates whether the line needs to be updated.
1446 static bool get_line(struct gui_wps *gwps,
1447 int line, int subline,
1448 struct align_pos *align,
1449 char *linebuf,
1450 int linebuf_size)
1452 struct wps_data *data = gwps->data;
1454 char temp_buf[128];
1455 char *buf = linebuf; /* will always point to the writing position */
1456 char *linebuf_end = linebuf + linebuf_size - 1;
1457 int i, last_token_idx;
1458 bool update = false;
1460 /* alignment-related variables */
1461 int cur_align;
1462 char* cur_align_start;
1463 cur_align_start = buf;
1464 cur_align = WPS_ALIGN_LEFT;
1465 align->left = NULL;
1466 align->center = NULL;
1467 align->right = NULL;
1469 #ifdef HAVE_LCD_BITMAP
1470 /* Reset margins - only bitmap targets modify them */
1471 gwps->display->setmargins(0, gwps->display->getymargin());
1472 #endif
1474 /* Process all tokens of the desired subline */
1475 last_token_idx = wps_last_token_index(data, line, subline);
1476 for (i = wps_first_token_index(data, line, subline);
1477 i <= last_token_idx; i++)
1479 switch(data->tokens[i].type)
1481 case WPS_TOKEN_CONDITIONAL:
1482 /* place ourselves in the right conditional case */
1483 i = evaluate_conditional(gwps, i);
1484 update = true;
1485 break;
1487 case WPS_TOKEN_CONDITIONAL_OPTION:
1488 /* we've finished in the curent conditional case,
1489 skip to the end of the conditional structure */
1490 i = find_conditional_end(data, i);
1491 break;
1493 #ifdef HAVE_LCD_BITMAP
1494 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1496 struct gui_img *img = data->img;
1497 int n = data->tokens[i].value.i;
1498 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1499 img[n].display = true;
1500 break;
1502 #endif
1504 case WPS_TOKEN_ALIGN_LEFT:
1505 case WPS_TOKEN_ALIGN_CENTER:
1506 case WPS_TOKEN_ALIGN_RIGHT:
1507 /* remember where the current aligned text started */
1508 switch (cur_align)
1510 case WPS_ALIGN_LEFT:
1511 align->left = cur_align_start;
1512 break;
1514 case WPS_ALIGN_CENTER:
1515 align->center = cur_align_start;
1516 break;
1518 case WPS_ALIGN_RIGHT:
1519 align->right = cur_align_start;
1520 break;
1522 /* start a new alignment */
1523 switch (data->tokens[i].type)
1525 case WPS_TOKEN_ALIGN_LEFT:
1526 cur_align = WPS_ALIGN_LEFT;
1527 break;
1528 case WPS_TOKEN_ALIGN_CENTER:
1529 cur_align = WPS_ALIGN_CENTER;
1530 break;
1531 case WPS_TOKEN_ALIGN_RIGHT:
1532 cur_align = WPS_ALIGN_RIGHT;
1533 break;
1534 default:
1535 break;
1537 *buf++ = 0;
1538 cur_align_start = buf;
1539 break;
1541 default:
1543 /* get the value of the tag and copy it to the buffer */
1544 char *value = get_token_value(gwps, &data->tokens[i],
1545 temp_buf, sizeof(temp_buf), NULL);
1546 if (value)
1548 update = true;
1549 while (*value && (buf < linebuf_end))
1550 *buf++ = *value++;
1552 break;
1557 /* close the current alignment */
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;
1573 return update;
1576 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1578 struct wps_data *data = gwps->data;
1579 int i;
1580 int subline_idx = wps_subline_index(data, line, subline);
1581 int last_token_idx = wps_last_token_index(data, line, subline);
1583 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1585 for (i = wps_first_token_index(data, line, subline);
1586 i <= last_token_idx; i++)
1588 switch(data->tokens[i].type)
1590 case WPS_TOKEN_CONDITIONAL:
1591 /* place ourselves in the right conditional case */
1592 i = evaluate_conditional(gwps, i);
1593 break;
1595 case WPS_TOKEN_CONDITIONAL_OPTION:
1596 /* we've finished in the curent conditional case,
1597 skip to the end of the conditional structure */
1598 i = find_conditional_end(data, i);
1599 break;
1601 case WPS_TOKEN_SUBLINE_TIMEOUT:
1602 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1603 break;
1605 default:
1606 break;
1611 /* Calculates which subline should be displayed for the specified line
1612 Returns true iff the subline must be refreshed */
1613 static bool update_curr_subline(struct gui_wps *gwps, int line)
1615 struct wps_data *data = gwps->data;
1617 int search, search_start, num_sublines;
1618 bool reset_subline;
1619 bool new_subline_refresh;
1620 bool only_one_subline;
1622 num_sublines = data->lines[line].num_sublines;
1623 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1624 new_subline_refresh = false;
1625 only_one_subline = false;
1627 /* if time to advance to next sub-line */
1628 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1629 reset_subline)
1631 /* search all sublines until the next subline with time > 0
1632 is found or we get back to the subline we started with */
1633 if (reset_subline)
1634 search_start = 0;
1635 else
1636 search_start = data->lines[line].curr_subline;
1638 for (search = 0; search < num_sublines; search++)
1640 data->lines[line].curr_subline++;
1642 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1643 if (data->lines[line].curr_subline == num_sublines)
1645 if (data->lines[line].curr_subline == 1)
1646 only_one_subline = true;
1647 data->lines[line].curr_subline = 0;
1650 /* if back where we started after search or
1651 only one subline is defined on the line */
1652 if (((search > 0) &&
1653 (data->lines[line].curr_subline == search_start)) ||
1654 only_one_subline)
1656 /* no other subline with a time > 0 exists */
1657 data->lines[line].subline_expire_time = (reset_subline ?
1658 current_tick :
1659 data->lines[line].subline_expire_time) + 100 * HZ;
1660 break;
1662 else
1664 /* get initial time multiplier for this subline */
1665 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1667 int subline_idx = wps_subline_index(data, line,
1668 data->lines[line].curr_subline);
1670 /* only use this subline if subline time > 0 */
1671 if (data->sublines[subline_idx].time_mult > 0)
1673 new_subline_refresh = true;
1674 data->lines[line].subline_expire_time = (reset_subline ?
1675 current_tick : data->lines[line].subline_expire_time) +
1676 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1677 break;
1683 return new_subline_refresh;
1686 /* Display a line appropriately according to its alignment format.
1687 format_align contains the text, separated between left, center and right.
1688 line is the index of the line on the screen.
1689 scroll indicates whether the line is a scrolling one or not.
1691 static void write_line(struct screen *display,
1692 struct align_pos *format_align,
1693 int line,
1694 bool scroll)
1697 int left_width = 0, left_xpos;
1698 int center_width = 0, center_xpos;
1699 int right_width = 0, right_xpos;
1700 int ypos;
1701 int space_width;
1702 int string_height;
1703 int scroll_width;
1705 /* calculate different string sizes and positions */
1706 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1707 if (format_align->left != 0) {
1708 display->getstringsize((unsigned char *)format_align->left,
1709 &left_width, &string_height);
1712 if (format_align->right != 0) {
1713 display->getstringsize((unsigned char *)format_align->right,
1714 &right_width, &string_height);
1717 if (format_align->center != 0) {
1718 display->getstringsize((unsigned char *)format_align->center,
1719 &center_width, &string_height);
1722 left_xpos = display->getxmargin();
1723 right_xpos = (display->width - right_width);
1724 center_xpos = (display->width + left_xpos - center_width) / 2;
1726 scroll_width = display->width - left_xpos;
1728 /* Checks for overlapping strings.
1729 If needed the overlapping strings will be merged, separated by a
1730 space */
1732 /* CASE 1: left and centered string overlap */
1733 /* there is a left string, need to merge left and center */
1734 if ((left_width != 0 && center_width != 0) &&
1735 (left_xpos + left_width + space_width > center_xpos)) {
1736 /* replace the former separator '\0' of left and
1737 center string with a space */
1738 *(--format_align->center) = ' ';
1739 /* calculate the new width and position of the merged string */
1740 left_width = left_width + space_width + center_width;
1741 /* there is no centered string anymore */
1742 center_width = 0;
1744 /* there is no left string, move center to left */
1745 if ((left_width == 0 && center_width != 0) &&
1746 (left_xpos + left_width > center_xpos)) {
1747 /* move the center string to the left string */
1748 format_align->left = format_align->center;
1749 /* calculate the new width and position of the string */
1750 left_width = center_width;
1751 /* there is no centered string anymore */
1752 center_width = 0;
1755 /* CASE 2: centered and right string overlap */
1756 /* there is a right string, need to merge center and right */
1757 if ((center_width != 0 && right_width != 0) &&
1758 (center_xpos + center_width + space_width > right_xpos)) {
1759 /* replace the former separator '\0' of center and
1760 right string with a space */
1761 *(--format_align->right) = ' ';
1762 /* move the center string to the right after merge */
1763 format_align->right = format_align->center;
1764 /* calculate the new width and position of the merged string */
1765 right_width = center_width + space_width + right_width;
1766 right_xpos = (display->width - right_width);
1767 /* there is no centered string anymore */
1768 center_width = 0;
1770 /* there is no right string, move center to right */
1771 if ((center_width != 0 && right_width == 0) &&
1772 (center_xpos + center_width > right_xpos)) {
1773 /* move the center string to the right string */
1774 format_align->right = format_align->center;
1775 /* calculate the new width and position of the string */
1776 right_width = center_width;
1777 right_xpos = (display->width - right_width);
1778 /* there is no centered string anymore */
1779 center_width = 0;
1782 /* CASE 3: left and right overlap
1783 There is no center string anymore, either there never
1784 was one or it has been merged in case 1 or 2 */
1785 /* there is a left string, need to merge left and right */
1786 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1787 (left_xpos + left_width + space_width > right_xpos)) {
1788 /* replace the former separator '\0' of left and
1789 right string with a space */
1790 *(--format_align->right) = ' ';
1791 /* calculate the new width and position of the string */
1792 left_width = left_width + space_width + right_width;
1793 /* there is no right string anymore */
1794 right_width = 0;
1796 /* there is no left string, move right to left */
1797 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1798 (left_width > right_xpos)) {
1799 /* move the right string to the left string */
1800 format_align->left = format_align->right;
1801 /* calculate the new width and position of the string */
1802 left_width = right_width;
1803 /* there is no right string anymore */
1804 right_width = 0;
1807 ypos = (line * string_height) + display->getymargin();
1810 if (scroll && ((left_width > scroll_width) ||
1811 (center_width > scroll_width) ||
1812 (right_width > scroll_width)))
1814 display->puts_scroll(0, line,
1815 (unsigned char *)format_align->left);
1817 else
1819 #ifdef HAVE_LCD_BITMAP
1820 /* clear the line first */
1821 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1822 display->fillrect(left_xpos, ypos, display->width, string_height);
1823 display->set_drawmode(DRMODE_SOLID);
1824 #endif
1826 /* Nasty hack: we output an empty scrolling string,
1827 which will reset the scroller for that line */
1828 display->puts_scroll(0, line, (unsigned char *)"");
1830 /* print aligned strings */
1831 if (left_width != 0)
1833 display->putsxy(left_xpos, ypos,
1834 (unsigned char *)format_align->left);
1836 if (center_width != 0)
1838 display->putsxy(center_xpos, ypos,
1839 (unsigned char *)format_align->center);
1841 if (right_width != 0)
1843 display->putsxy(right_xpos, ypos,
1844 (unsigned char *)format_align->right);
1849 /* Refresh the WPS according to refresh_mode. */
1850 bool gui_wps_refresh(struct gui_wps *gwps,
1851 int ffwd_offset,
1852 unsigned char refresh_mode)
1854 struct wps_data *data = gwps->data;
1855 struct screen *display = gwps->display;
1856 struct wps_state *state = gwps->state;
1858 if(!gwps || !data || !state || !display)
1859 return false;
1861 int line, i, subline_idx;
1862 unsigned char flags;
1863 char linebuf[MAX_PATH];
1865 struct align_pos align;
1866 align.left = NULL;
1867 align.center = NULL;
1868 align.right = NULL;
1870 bool update_line, new_subline_refresh;
1872 #ifdef HAVE_LCD_BITMAP
1873 gui_wps_statusbar_draw(gwps, true);
1875 /* to find out wether the peak meter is enabled we
1876 assume it wasn't until we find a line that contains
1877 the peak meter. We can't use peak_meter_enabled itself
1878 because that would mean to turn off the meter thread
1879 temporarily. (That shouldn't matter unless yield
1880 or sleep is called but who knows...)
1882 bool enable_pm = false;
1884 /* Set images to not to be displayed */
1885 for (i = 0; i < MAX_IMAGES; i++)
1887 data->img[i].display = false;
1889 #endif
1891 /* reset to first subline if refresh all flag is set */
1892 if (refresh_mode == WPS_REFRESH_ALL)
1894 for (i = 0; i < data->num_lines; i++)
1896 data->lines[i].curr_subline = SUBLINE_RESET;
1900 #ifdef HAVE_LCD_CHARCELLS
1901 for (i = 0; i < 8; i++)
1903 if (data->wps_progress_pat[i] == 0)
1904 data->wps_progress_pat[i] = display->get_locked_pattern();
1906 #endif
1908 if (!state->id3)
1910 display->stop_scroll();
1911 return false;
1914 state->ff_rewind_count = ffwd_offset;
1916 for (line = 0; line < data->num_lines; line++)
1918 memset(linebuf, 0, sizeof(linebuf));
1919 update_line = false;
1921 /* get current subline for the line */
1922 new_subline_refresh = update_curr_subline(gwps, line);
1924 subline_idx = wps_subline_index(data, line,
1925 data->lines[line].curr_subline);
1926 flags = data->sublines[subline_idx].line_type;
1928 if (refresh_mode == WPS_REFRESH_ALL || (flags & refresh_mode)
1929 || new_subline_refresh)
1931 /* get_line tells us if we need to update the line */
1932 update_line = get_line(gwps, line, data->lines[line].curr_subline,
1933 &align, linebuf, sizeof(linebuf));
1936 #ifdef HAVE_LCD_BITMAP
1937 /* progressbar */
1938 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1940 /* the progressbar should be alone on its line */
1941 update_line = false;
1942 draw_progressbar(gwps, line);
1945 /* peakmeter */
1946 if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
1948 /* the peakmeter should be alone on its line */
1949 update_line = false;
1951 int h = font_get(FONT_UI)->height;
1952 int peak_meter_y = display->getymargin() + line * h;
1954 /* The user might decide to have the peak meter in the last
1955 line so that it is only displayed if no status bar is
1956 visible. If so we neither want do draw nor enable the
1957 peak meter. */
1958 if (peak_meter_y + h <= display->height) {
1959 /* found a line with a peak meter -> remember that we must
1960 enable it later */
1961 enable_pm = true;
1962 peak_meter_screen(gwps->display, 0, peak_meter_y,
1963 MIN(h, display->height - peak_meter_y));
1967 #else /* HAVE_LCD_CHARCELL */
1969 /* progressbar */
1970 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1972 if (data->full_line_progressbar)
1973 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1974 else
1975 draw_player_progress(gwps);
1977 #endif
1979 if (update_line)
1981 if (flags & WPS_REFRESH_SCROLL)
1983 /* if the line is a scrolling one we don't want to update
1984 too often, so that it has the time to scroll */
1985 if ((refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1986 write_line(display, &align, line, true);
1988 else
1989 write_line(display, &align, line, false);
1993 #ifdef HAVE_LCD_BITMAP
1994 data->peak_meter_enabled = enable_pm;
1995 wps_display_images(gwps);
1996 #endif
1998 display->update();
2000 #ifdef HAVE_BACKLIGHT
2001 if (global_settings.caption_backlight && state->id3)
2003 /* turn on backlight n seconds before track ends, and turn it off n
2004 seconds into the new track. n == backlight_timeout, or 5s */
2005 int n = backlight_timeout_value[global_settings.backlight_timeout]
2006 * 1000;
2008 if ( n < 1000 )
2009 n = 5000; /* use 5s if backlight is always on or off */
2011 if (((state->id3->elapsed < 1000) ||
2012 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2013 (state->paused == false))
2014 backlight_on();
2016 #endif
2017 #ifdef HAVE_REMOTE_LCD
2018 if (global_settings.remote_caption_backlight && state->id3)
2020 /* turn on remote backlight n seconds before track ends, and turn it
2021 off n seconds into the new track. n == remote_backlight_timeout,
2022 or 5s */
2023 int n = backlight_timeout_value[global_settings.remote_backlight_timeout]
2024 * 1000;
2026 if ( n < 1000 )
2027 n = 5000; /* use 5s if backlight is always on or off */
2029 if (((state->id3->elapsed < 1000) ||
2030 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2031 (state->paused == false))
2032 remote_backlight_on();
2034 #endif
2036 return true;