quiet the masses...
[Rockbox.git] / apps / gui / gwps-common.c
blobcfaabafe90e2b38ae9cbbff2ba182f6f985c933d
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 unsigned int step = 0; /* current ff/rewind step */
149 unsigned int max_step = 0; /* maximum ff/rewind step */
150 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
151 int direction = -1; /* forward=1 or backward=-1 */
152 long accel_tick = 0; /* next time at which to bump the step size */
153 bool exit = false;
154 bool usb = false;
155 int i = 0;
157 if (button == ACTION_NONE)
159 status_set_ffmode(0);
160 return usb;
162 while (!exit)
164 switch ( button )
166 case ACTION_WPS_SEEKFWD:
167 direction = 1;
168 case ACTION_WPS_SEEKBACK:
169 if (wps_state.ff_rewind)
171 if (direction == 1)
173 /* fast forwarding, calc max step relative to end */
174 max_step = (wps_state.id3->length -
175 (wps_state.id3->elapsed +
176 ff_rewind_count)) *
177 FF_REWIND_MAX_PERCENT / 100;
179 else
181 /* rewinding, calc max step relative to start */
182 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
183 FF_REWIND_MAX_PERCENT / 100;
186 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
188 if (step > max_step)
189 step = max_step;
191 ff_rewind_count += step * direction;
193 if (global_settings.ff_rewind_accel != 0 &&
194 current_tick >= accel_tick)
196 step *= 2;
197 accel_tick = current_tick +
198 global_settings.ff_rewind_accel*HZ;
201 else
203 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
204 wps_state.id3 && wps_state.id3->length )
206 if (!wps_state.paused)
207 #if (CONFIG_CODEC == SWCODEC)
208 audio_pre_ff_rewind();
209 #else
210 audio_pause();
211 #endif
212 #if CONFIG_KEYPAD == PLAYER_PAD
213 FOR_NB_SCREENS(i)
214 gui_wps[i].display->stop_scroll();
215 #endif
216 if (direction > 0)
217 status_set_ffmode(STATUS_FASTFORWARD);
218 else
219 status_set_ffmode(STATUS_FASTBACKWARD);
221 wps_state.ff_rewind = true;
223 step = 1000 * global_settings.ff_rewind_min_step;
225 accel_tick = current_tick +
226 global_settings.ff_rewind_accel*HZ;
228 else
229 break;
232 if (direction > 0) {
233 if ((wps_state.id3->elapsed + ff_rewind_count) >
234 wps_state.id3->length)
235 ff_rewind_count = wps_state.id3->length -
236 wps_state.id3->elapsed;
238 else {
239 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
240 ff_rewind_count = -wps_state.id3->elapsed;
243 FOR_NB_SCREENS(i)
244 gui_wps_refresh(&gui_wps[i],
245 (wps_state.wps_time_countup == false)?
246 ff_rewind_count:-ff_rewind_count,
247 WPS_REFRESH_PLAYER_PROGRESS |
248 WPS_REFRESH_DYNAMIC);
250 break;
252 case ACTION_WPS_STOPSEEK:
253 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
254 audio_ff_rewind(wps_state.id3->elapsed);
255 ff_rewind_count = 0;
256 wps_state.ff_rewind = false;
257 status_set_ffmode(0);
258 #if (CONFIG_CODEC != SWCODEC)
259 if (!wps_state.paused)
260 audio_resume();
261 #endif
262 #ifdef HAVE_LCD_CHARCELLS
263 gui_wps_display();
264 #endif
265 exit = true;
266 break;
268 default:
269 if(default_event_handler(button) == SYS_USB_CONNECTED) {
270 status_set_ffmode(0);
271 usb = true;
272 exit = true;
274 break;
276 if (!exit)
277 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
279 return usb;
282 bool gui_wps_display(void)
284 int i;
285 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
287 global_status.resume_index = -1;
288 #ifdef HAVE_LCD_BITMAP
289 gui_syncstatusbar_draw(&statusbars, true);
290 #endif
291 gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST));
292 return true;
294 else
296 FOR_NB_SCREENS(i)
298 gui_wps[i].display->clear_display();
299 if (!gui_wps[i].data->wps_loaded) {
300 if ( !gui_wps[i].data->num_tokens ) {
301 /* set the default wps for the main-screen */
302 if(i == 0)
304 #ifdef HAVE_LCD_BITMAP
305 #if LCD_DEPTH > 1
306 unload_wps_backdrop();
307 #endif
308 wps_data_load(gui_wps[i].data,
309 "%s%?it<%?in<%in. |>%it|%fn>\n"
310 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
311 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
312 "\n"
313 "%al%pc/%pt%ar[%pp:%pe]\n"
314 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
315 "%pb\n"
316 "%pm\n", false);
317 #else
318 wps_data_load(gui_wps[i].data,
319 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
320 "%pc%?ps<*|/>%pt\n", false);
321 #endif
323 #if NB_SCREENS == 2
324 /* set the default wps for the remote-screen */
325 else if(i == 1)
327 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
328 unload_remote_wps_backdrop();
329 #endif
330 wps_data_load(gui_wps[i].data,
331 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
332 "%s%?it<%?in<%in. |>%it|%fn>\n"
333 "%al%pc/%pt%ar[%pp:%pe]\n"
334 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
335 "%pb\n", false);
337 #endif
342 yield();
343 FOR_NB_SCREENS(i)
345 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
347 return false;
350 bool update(struct gui_wps *gwps)
352 bool track_changed = audio_has_changed_track();
353 bool retcode = false;
355 gwps->state->nid3 = audio_next_track();
356 if (track_changed)
358 gwps->display->stop_scroll();
359 gwps->state->id3 = audio_current_track();
361 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
362 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
364 /* the current cuesheet isn't the right one any more */
366 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
367 /* We have the new cuesheet in memory (temp_cue),
368 let's make it the current one ! */
369 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
371 else {
372 /* We need to parse the new cuesheet */
374 char cuepath[MAX_PATH];
376 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
377 parse_cuesheet(cuepath, curr_cue))
379 gwps->state->id3->cuesheet_type = 1;
380 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
384 cue_spoof_id3(curr_cue, gwps->state->id3);
387 if (gui_wps_display())
388 retcode = true;
389 else{
390 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
393 if (gwps->state->id3)
395 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
396 sizeof(gwps->state->current_track_path));
397 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
401 if (gwps->state->id3)
403 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
404 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
405 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
406 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
408 /* We've changed tracks within the cuesheet :
409 we need to update the ID3 info and refresh the WPS */
411 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
412 cue_spoof_id3(curr_cue, gwps->state->id3);
414 gwps->display->stop_scroll();
415 if (gui_wps_display())
416 retcode = true;
417 else
418 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
420 else
421 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
424 gui_wps_statusbar_draw(gwps, false);
426 return retcode;
430 void display_keylock_text(bool locked)
432 char* s;
433 int i;
434 FOR_NB_SCREENS(i)
435 gui_wps[i].display->stop_scroll();
437 if(locked)
438 s = str(LANG_KEYLOCK_ON);
439 else
440 s = str(LANG_KEYLOCK_OFF);
441 gui_syncsplash(HZ, s);
444 #ifdef HAVE_LCD_BITMAP
446 static void draw_progressbar(struct gui_wps *gwps, int line)
448 struct wps_data *data = gwps->data;
449 struct screen *display = gwps->display;
450 struct wps_state *state = gwps->state;
451 int h = font_get(FONT_UI)->height;
453 int sb_y;
454 if (data->progress_top < 0)
455 sb_y = line*h + display->getymargin() +
456 ((h > data->progress_height + 1)
457 ? (h - data->progress_height) / 2 : 1);
458 else
459 sb_y = data->progress_top;
461 if (!data->progress_end)
462 data->progress_end=display->width;
464 if (gwps->data->progressbar.have_bitmap_pb)
465 gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
466 data->progress_start, sb_y,
467 data->progress_end-data->progress_start,
468 data->progressbar.bm.height,
469 state->id3->length ? state->id3->length : 1, 0,
470 state->id3->length ? state->id3->elapsed
471 + state->ff_rewind_count : 0,
472 HORIZONTAL);
473 else
474 gui_scrollbar_draw(display, data->progress_start, sb_y,
475 data->progress_end-data->progress_start,
476 data->progress_height,
477 state->id3->length ? state->id3->length : 1, 0,
478 state->id3->length ? state->id3->elapsed
479 + state->ff_rewind_count : 0,
480 HORIZONTAL);
482 #ifdef AB_REPEAT_ENABLE
483 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
484 ab_draw_markers(display, state->id3->length,
485 data->progress_start, data->progress_end, sb_y,
486 data->progress_height);
487 #endif
489 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
490 cue_draw_markers(display, state->id3->length,
491 data->progress_start, data->progress_end,
492 sb_y+1, data->progress_height-2);
495 /* clears the area where the image was shown */
496 static void clear_image_pos(struct gui_wps *gwps, int n)
498 if(!gwps)
499 return;
500 struct wps_data *data = gwps->data;
501 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
502 gwps->display->fillrect(data->img[n].x, data->img[n].y,
503 data->img[n].bm.width, data->img[n].bm.height);
504 gwps->display->set_drawmode(DRMODE_SOLID);
507 static void wps_draw_image(struct gui_wps *gwps, int n)
509 struct screen *display = gwps->display;
510 struct wps_data *data = gwps->data;
511 if(data->img[n].always_display)
512 display->set_drawmode(DRMODE_FG);
513 else
514 display->set_drawmode(DRMODE_SOLID);
516 #if LCD_DEPTH > 1
517 if(data->img[n].bm.format == FORMAT_MONO) {
518 #endif
519 display->mono_bitmap(data->img[n].bm.data, data->img[n].x,
520 data->img[n].y, data->img[n].bm.width,
521 data->img[n].bm.height);
522 #if LCD_DEPTH > 1
523 } else {
524 display->transparent_bitmap((fb_data *)data->img[n].bm.data,
525 data->img[n].x,
526 data->img[n].y, data->img[n].bm.width,
527 data->img[n].bm.height);
529 #endif
532 static void wps_display_images(struct gui_wps *gwps)
534 if(!gwps || !gwps->data || !gwps->display)
535 return;
537 int n;
538 struct wps_data *data = gwps->data;
539 struct screen *display = gwps->display;
541 for (n = 0; n < MAX_IMAGES; n++)
543 if (data->img[n].loaded &&
544 (data->img[n].display || data->img[n].always_display))
546 wps_draw_image(gwps, n);
549 display->set_drawmode(DRMODE_SOLID);
552 #else /* HAVE_LCD_CHARCELL */
554 static bool draw_player_progress(struct gui_wps *gwps)
556 struct wps_state *state = gwps->state;
557 struct screen *display = gwps->display;
558 unsigned char progress_pattern[7];
559 int pos = 0;
560 int i;
562 if (!state->id3)
563 return false;
565 if (state->id3->length)
566 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
567 / state->id3->length;
569 for (i = 0; i < 7; i++, pos -= 5)
571 if (pos <= 0)
572 progress_pattern[i] = 0x1f;
573 else if (pos >= 5)
574 progress_pattern[i] = 0x00;
575 else
576 progress_pattern[i] = 0x1f >> pos;
579 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
580 return true;
583 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
585 static const unsigned char numbers[10][4] = {
586 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
587 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
588 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
589 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
590 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
591 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
592 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
593 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
594 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
595 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
598 struct wps_state *state = gwps->state;
599 struct screen *display = gwps->display;
600 struct wps_data *data = gwps->data;
601 unsigned char progress_pattern[7];
602 char timestr[10];
603 int time;
604 int time_idx = 0;
605 int pos = 0;
606 int pat_idx = 1;
607 int digit, i, j;
608 bool softchar;
610 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
611 return;
613 time = state->id3->elapsed + state->ff_rewind_count;
614 if (state->id3->length)
615 pos = 55 * time / state->id3->length;
617 memset(timestr, 0, sizeof(timestr));
618 format_time(timestr, sizeof(timestr)-2, time);
619 timestr[strlen(timestr)] = ':'; /* always safe */
621 for (i = 0; i < 11; i++, pos -= 5)
623 softchar = false;
624 memset(progress_pattern, 0, sizeof(progress_pattern));
626 if ((digit = timestr[time_idx]))
628 softchar = true;
629 digit -= '0';
631 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
633 memcpy(progress_pattern, numbers[digit], 4);
634 time_idx += 2;
636 else /* tens, shifted right */
638 for (j = 0; j < 4; j++)
639 progress_pattern[j] = numbers[digit][j] >> 1;
641 if (time_idx > 0) /* not the first group, add colon in front */
643 progress_pattern[1] |= 0x10;
644 progress_pattern[3] |= 0x10;
646 time_idx++;
649 if (pos >= 5)
650 progress_pattern[5] = progress_pattern[6] = 0x1f;
653 if (pos > 0 && pos < 5)
655 softchar = true;
656 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
659 if (softchar && pat_idx < 8)
661 display->define_pattern(data->wps_progress_pat[pat_idx],
662 progress_pattern);
663 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
664 pat_idx++;
666 else if (pos <= 0)
667 buf = utf8encode(' ', buf);
668 else
669 buf = utf8encode(0xe115, buf); /* 2/7 _ */
671 *buf = '\0';
674 #endif /* HAVE_LCD_CHARCELL */
676 /* Extract a part from a path.
678 * buf - buffer extract part to.
679 * buf_size - size of buffer.
680 * path - path to extract from.
681 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
682 * parent of parent, etc.
684 * Returns buf if the desired level was found, NULL otherwise.
686 static char* get_dir(char* buf, int buf_size, const char* path, int level)
688 const char* sep;
689 const char* last_sep;
690 int len;
692 sep = path + strlen(path);
693 last_sep = sep;
695 while (sep > path)
697 if ('/' == *(--sep))
699 if (!level)
700 break;
702 level--;
703 last_sep = sep - 1;
707 if (level || (last_sep <= sep))
708 return NULL;
710 len = MIN(last_sep - sep, buf_size - 1);
711 strncpy(buf, sep + 1, len);
712 buf[len] = 0;
713 return buf;
716 /* Return the tag found at index i and write its value in buf.
717 The return value is buf if the tag had a value, or NULL if not.
719 intval is used with conditionals/enums: when this function is called,
720 intval should contain the number of options in the conditional/enum.
721 When this function returns, intval is -1 if the tag is non numeric or,
722 if the tag is numeric, intval is the enum case we want to go to.
723 When not treating a conditional/enum, intval should be NULL.
725 static char *get_token_value(struct gui_wps *gwps,
726 struct wps_token *token,
727 char *buf, int buf_size,
728 int *intval)
730 if (!gwps)
731 return NULL;
733 struct wps_data *data = gwps->data;
734 struct wps_state *state = gwps->state;
736 if (!data || !state)
737 return NULL;
739 struct mp3entry *id3;
741 if (token->next)
742 id3 = state->nid3;
743 else
744 id3 = state->id3;
746 if (!id3)
747 return NULL;
749 #if CONFIG_RTC
750 struct tm* tm = NULL;
752 /* if the token is an RTC one, update the time
753 and do the necessary checks */
754 if (token->type >= WPS_TOKENS_RTC_BEGIN
755 && token->type <= WPS_TOKENS_RTC_END)
757 tm = get_time();
759 if (!valid_time(tm))
760 return NULL;
762 #endif
764 int limit = 1;
765 if (intval)
767 limit = *intval;
768 *intval = -1;
771 switch (token->type)
773 case WPS_TOKEN_CHARACTER:
774 return &(token->value.c);
776 case WPS_TOKEN_STRING:
777 return data->strings[token->value.i];
779 case WPS_TOKEN_TRACK_TIME_ELAPSED:
780 format_time(buf, buf_size,
781 id3->elapsed + state->ff_rewind_count);
782 return buf;
784 case WPS_TOKEN_TRACK_TIME_REMAINING:
785 format_time(buf, buf_size,
786 id3->length - id3->elapsed -
787 state->ff_rewind_count);
788 return buf;
790 case WPS_TOKEN_TRACK_LENGTH:
791 format_time(buf, buf_size, id3->length);
792 return buf;
794 case WPS_TOKEN_PLAYLIST_ENTRIES:
795 snprintf(buf, buf_size, "%d", playlist_amount());
796 return buf;
798 case WPS_TOKEN_PLAYLIST_NAME:
799 return playlist_name(NULL, buf, buf_size);
801 case WPS_TOKEN_PLAYLIST_POSITION:
802 snprintf(buf, buf_size, "%d",
803 playlist_get_display_index());
804 return buf;
806 case WPS_TOKEN_PLAYLIST_SHUFFLE:
807 if ( global_settings.playlist_shuffle )
808 return "s";
809 else
810 return NULL;
811 break;
813 case WPS_TOKEN_VOLUME:
814 snprintf(buf, buf_size, "%d", global_settings.volume);
815 if (intval)
817 if (global_settings.volume == sound_min(SOUND_VOLUME))
819 *intval = 1;
821 else if (global_settings.volume == 0)
823 *intval = limit - 1;
825 else if (global_settings.volume > 0)
827 *intval = limit;
829 else
831 *intval = (limit - 3) * (global_settings.volume
832 - sound_min(SOUND_VOLUME) - 1)
833 / (-1 - sound_min(SOUND_VOLUME)) + 2;
836 return buf;
838 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
839 if (id3->length <= 0)
840 return NULL;
842 if (intval)
844 *intval = limit * (id3->elapsed + state->ff_rewind_count)
845 / id3->length + 1;
847 snprintf(buf, buf_size, "%d",
848 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
849 return buf;
851 case WPS_TOKEN_METADATA_ARTIST:
852 return id3->artist;
854 case WPS_TOKEN_METADATA_COMPOSER:
855 return id3->composer;
857 case WPS_TOKEN_METADATA_ALBUM:
858 return id3->album;
860 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
861 return id3->albumartist;
863 case WPS_TOKEN_METADATA_GROUPING:
864 return id3->grouping;
866 case WPS_TOKEN_METADATA_GENRE:
867 return id3->genre_string;
869 case WPS_TOKEN_METADATA_DISC_NUMBER:
870 if (id3->disc_string)
871 return id3->disc_string;
872 if (id3->discnum) {
873 snprintf(buf, buf_size, "%d", id3->discnum);
874 return buf;
876 return NULL;
878 case WPS_TOKEN_METADATA_TRACK_NUMBER:
879 if (id3->track_string)
880 return id3->track_string;
882 if (id3->tracknum) {
883 snprintf(buf, buf_size, "%d", id3->tracknum);
884 return buf;
886 return NULL;
888 case WPS_TOKEN_METADATA_TRACK_TITLE:
889 return id3->title;
891 case WPS_TOKEN_METADATA_VERSION:
892 switch (id3->id3version)
894 case ID3_VER_1_0:
895 return "1";
897 case ID3_VER_1_1:
898 return "1.1";
900 case ID3_VER_2_2:
901 return "2.2";
903 case ID3_VER_2_3:
904 return "2.3";
906 case ID3_VER_2_4:
907 return "2.4";
909 default:
910 return NULL;
913 case WPS_TOKEN_METADATA_YEAR:
914 if( id3->year_string )
915 return id3->year_string;
917 if (id3->year) {
918 snprintf(buf, buf_size, "%d", id3->year);
919 return buf;
921 return NULL;
923 case WPS_TOKEN_METADATA_COMMENT:
924 return id3->comment;
926 #ifdef HAVE_ALBUMART
927 case WPS_TOKEN_ALBUMART_DISPLAY:
928 draw_album_art(gwps, audio_current_aa_hid(), false);
929 return NULL;
931 case WPS_TOKEN_ALBUMART_FOUND:
932 if (audio_current_aa_hid() >= 0) {
933 snprintf(buf, buf_size, "C");
934 return buf;
936 return NULL;
937 #endif
939 case WPS_TOKEN_FILE_BITRATE:
940 if(id3->bitrate)
941 snprintf(buf, buf_size, "%d", id3->bitrate);
942 else
943 snprintf(buf, buf_size, "?");
944 return buf;
946 case WPS_TOKEN_FILE_CODEC:
947 if (intval)
949 if(id3->codectype == AFMT_UNKNOWN)
950 *intval = AFMT_NUM_CODECS;
951 else
952 *intval = id3->codectype;
954 return id3_get_codec(id3);
956 case WPS_TOKEN_FILE_FREQUENCY:
957 snprintf(buf, buf_size, "%ld", id3->frequency);
958 return buf;
960 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
961 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
962 if ((id3->frequency % 1000) < 100)
963 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
964 else
965 snprintf(buf, buf_size, "%ld.%d",
966 id3->frequency / 1000,
967 (id3->frequency % 1000) / 100);
968 return buf;
970 case WPS_TOKEN_FILE_NAME:
971 if (get_dir(buf, buf_size, id3->path, 0)) {
972 /* Remove extension */
973 char* sep = strrchr(buf, '.');
974 if (NULL != sep) {
975 *sep = 0;
977 return buf;
979 else {
980 return NULL;
983 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
984 return get_dir(buf, buf_size, id3->path, 0);
986 case WPS_TOKEN_FILE_PATH:
987 return id3->path;
989 case WPS_TOKEN_FILE_SIZE:
990 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
991 return buf;
993 case WPS_TOKEN_FILE_VBR:
994 return id3->vbr ? "(avg)" : NULL;
996 case WPS_TOKEN_FILE_DIRECTORY:
997 return get_dir(buf, buf_size, id3->path, token->value.i);
999 case WPS_TOKEN_BATTERY_PERCENT:
1001 int l = battery_level();
1003 if (intval)
1005 limit = MAX(limit, 2);
1006 if (l > -1) {
1007 /* First enum is used for "unknown level". */
1008 *intval = (limit - 1) * l / 100 + 2;
1009 } else {
1010 *intval = 1;
1014 if (l > -1) {
1015 snprintf(buf, buf_size, "%d", l);
1016 return buf;
1017 } else {
1018 return "?";
1022 case WPS_TOKEN_BATTERY_VOLTS:
1024 unsigned int v = battery_voltage();
1025 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1026 return buf;
1029 case WPS_TOKEN_BATTERY_TIME:
1031 int t = battery_time();
1032 if (t >= 0)
1033 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1034 else
1035 strncpy(buf, "?h ?m", buf_size);
1036 return buf;
1039 #if CONFIG_CHARGING
1040 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1042 if(charger_input_state==CHARGER)
1043 return "p";
1044 else
1045 return NULL;
1047 #endif
1048 #if CONFIG_CHARGING >= CHARGING_MONITOR
1049 case WPS_TOKEN_BATTERY_CHARGING:
1051 if (charge_state == CHARGING || charge_state == TOPOFF) {
1052 return "c";
1053 } else {
1054 return NULL;
1057 #endif
1058 case WPS_TOKEN_BATTERY_SLEEPTIME:
1060 if (get_sleep_timer() == 0)
1061 return NULL;
1062 else
1064 format_time(buf, buf_size, get_sleep_timer() * 1000);
1065 return buf;
1069 case WPS_TOKEN_PLAYBACK_STATUS:
1071 int status = audio_status();
1072 int mode = 1;
1073 if (status == AUDIO_STATUS_PLAY && \
1074 !(status & AUDIO_STATUS_PAUSE))
1075 mode = 2;
1076 if (audio_status() & AUDIO_STATUS_PAUSE && \
1077 (! status_get_ffmode()))
1078 mode = 3;
1079 if (status_get_ffmode() == STATUS_FASTFORWARD)
1080 mode = 4;
1081 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1082 mode = 5;
1084 if (intval) {
1085 *intval = mode;
1088 snprintf(buf, buf_size, "%d", mode);
1089 return buf;
1092 case WPS_TOKEN_REPEAT_MODE:
1093 if (intval)
1094 *intval = global_settings.repeat_mode + 1;
1095 snprintf(buf, buf_size, "%d", *intval);
1096 return buf;
1097 case WPS_TOKEN_RTC_12HOUR_CFG:
1098 if (intval)
1099 *intval = global_settings.timeformat + 1;
1100 snprintf(buf, buf_size, "%d", *intval);
1101 return buf;
1102 #if CONFIG_RTC
1103 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1104 /* d: day of month (01..31) */
1105 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1106 return buf;
1108 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1109 /* e: day of month, blank padded ( 1..31) */
1110 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1111 return buf;
1113 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1114 /* H: hour (00..23) */
1115 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1116 return buf;
1118 case WPS_TOKEN_RTC_HOUR_24:
1119 /* k: hour ( 0..23) */
1120 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1121 return buf;
1123 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1124 /* I: hour (01..12) */
1125 snprintf(buf, buf_size, "%02d",
1126 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1127 return buf;
1129 case WPS_TOKEN_RTC_HOUR_12:
1130 /* l: hour ( 1..12) */
1131 snprintf(buf, buf_size, "%2d",
1132 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1133 return buf;
1135 case WPS_TOKEN_RTC_MONTH:
1136 /* m: month (01..12) */
1137 if (intval)
1138 *intval = tm->tm_mon + 1;
1139 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1140 return buf;
1142 case WPS_TOKEN_RTC_MINUTE:
1143 /* M: minute (00..59) */
1144 snprintf(buf, buf_size, "%02d", tm->tm_min);
1145 return buf;
1147 case WPS_TOKEN_RTC_SECOND:
1148 /* S: second (00..59) */
1149 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1150 return buf;
1152 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1153 /* y: last two digits of year (00..99) */
1154 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1155 return buf;
1157 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1158 /* Y: year (1970...) */
1159 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1160 return buf;
1162 case WPS_TOKEN_RTC_AM_PM_UPPER:
1163 /* p: upper case AM or PM indicator */
1164 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1165 return buf;
1167 case WPS_TOKEN_RTC_AM_PM_LOWER:
1168 /* P: lower case am or pm indicator */
1169 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1170 return buf;
1172 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1173 /* a: abbreviated weekday name (Sun..Sat) */
1174 snprintf(buf, buf_size, "%s",str(LANG_WEEKDAY_SUNDAY + tm->tm_wday));
1175 return buf;
1177 case WPS_TOKEN_RTC_MONTH_NAME:
1178 /* b: abbreviated month name (Jan..Dec) */
1179 snprintf(buf, buf_size, "%s",str(LANG_MONTH_JANUARY + tm->tm_mon));
1180 return buf;
1182 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1183 /* u: day of week (1..7); 1 is Monday */
1184 if (intval)
1185 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1186 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1187 return buf;
1189 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1190 /* w: day of week (0..6); 0 is Sunday */
1191 if (intval)
1192 *intval = tm->tm_wday + 1;
1193 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1194 return buf;
1195 #else
1196 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1197 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1198 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1199 case WPS_TOKEN_RTC_HOUR_24:
1200 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1201 case WPS_TOKEN_RTC_HOUR_12:
1202 case WPS_TOKEN_RTC_MONTH:
1203 case WPS_TOKEN_RTC_MINUTE:
1204 case WPS_TOKEN_RTC_SECOND:
1205 case WPS_TOKEN_RTC_AM_PM_UPPER:
1206 case WPS_TOKEN_RTC_AM_PM_LOWER:
1207 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1208 strncpy(buf, "--", buf_size);
1209 return buf;
1210 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1211 strncpy(buf, "----", buf_size);
1212 return buf;
1213 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1214 case WPS_TOKEN_RTC_MONTH_NAME:
1215 strncpy(buf, "---", buf_size);
1216 return buf;
1217 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1218 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1219 strncpy(buf, "-", buf_size);
1220 return buf;
1221 #endif
1223 #ifdef HAVE_LCD_CHARCELLS
1224 case WPS_TOKEN_PROGRESSBAR:
1226 char *end = utf8encode(data->wps_progress_pat[0], buf);
1227 *end = '\0';
1228 return buf;
1231 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1232 if(is_new_player())
1234 /* we need 11 characters (full line) for
1235 progress-bar */
1236 snprintf(buf, buf_size, " ");
1238 else
1240 /* Tell the user if we have an OldPlayer */
1241 snprintf(buf, buf_size, " <Old LCD> ");
1243 return buf;
1244 #endif
1246 #ifdef HAVE_TAGCACHE
1247 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1248 if (intval) {
1249 *intval = id3->playcount + 1;
1251 snprintf(buf, buf_size, "%ld", id3->playcount);
1252 return buf;
1254 case WPS_TOKEN_DATABASE_RATING:
1255 if (intval) {
1256 *intval = id3->rating + 1;
1258 snprintf(buf, buf_size, "%d", id3->rating);
1259 return buf;
1261 case WPS_TOKEN_DATABASE_AUTOSCORE:
1262 if (intval)
1263 *intval = id3->score + 1;
1265 snprintf(buf, buf_size, "%d", id3->score);
1266 return buf;
1267 #endif
1269 #if (CONFIG_CODEC == SWCODEC)
1270 case WPS_TOKEN_CROSSFADE:
1271 if (intval)
1272 *intval = global_settings.crossfade + 1;
1273 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1274 return buf;
1276 case WPS_TOKEN_REPLAYGAIN:
1278 int val;
1280 if (global_settings.replaygain == 0)
1281 val = 1; /* off */
1282 else
1284 int type =
1285 get_replaygain_mode(id3->track_gain_string != NULL,
1286 id3->album_gain_string != NULL);
1287 if (type < 0)
1288 val = 6; /* no tag */
1289 else
1290 val = type + 2;
1292 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1293 val += 2;
1296 if (intval)
1297 *intval = val;
1299 switch (val)
1301 case 1:
1302 case 6:
1303 return "+0.00 dB";
1304 break;
1305 case 2:
1306 case 4:
1307 strncpy(buf, id3->track_gain_string, buf_size);
1308 break;
1309 case 3:
1310 case 5:
1311 strncpy(buf, id3->album_gain_string, buf_size);
1312 break;
1314 return buf;
1316 #endif /* (CONFIG_CODEC == SWCODEC) */
1318 #if (CONFIG_CODEC != MAS3507D)
1319 case WPS_TOKEN_SOUND_PITCH:
1321 int val = sound_get_pitch();
1322 snprintf(buf, buf_size, "%d.%d",
1323 val / 10, val % 10);
1324 return buf;
1326 #endif
1328 case WPS_TOKEN_MAIN_HOLD:
1329 #ifdef HAS_BUTTON_HOLD
1330 if (button_hold())
1331 #else
1332 if (is_keys_locked())
1333 #endif /*hold switch or softlock*/
1334 return "h";
1335 else
1336 return NULL;
1338 #ifdef HAS_REMOTE_BUTTON_HOLD
1339 case WPS_TOKEN_REMOTE_HOLD:
1340 if (remote_button_hold())
1341 return "r";
1342 else
1343 return NULL;
1344 #endif
1346 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1347 case WPS_TOKEN_VLED_HDD:
1348 if(led_read(HZ/2))
1349 return "h";
1350 else
1351 return NULL;
1352 #endif
1354 #ifdef HAVE_LCD_BITMAP
1355 case WPS_TOKEN_LEFTMARGIN:
1356 gwps->display->setmargins(token->value.i,
1357 gwps->display->getymargin());
1358 return NULL;
1359 #endif
1361 default:
1362 return NULL;
1366 /* Return the index to the end token for the conditional token at index.
1367 The conditional token can be either a start token or a separator
1368 (i.e. option) token.
1370 static int find_conditional_end(struct wps_data *data, int index)
1372 int ret = index;
1373 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1374 ret = data->tokens[ret].value.i;
1376 /* ret now is the index to the end token for the conditional. */
1377 return ret;
1380 /* Return the index of the appropriate case for the conditional
1381 that starts at cond_index.
1383 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1385 if (!gwps)
1386 return false;
1388 struct wps_data *data = gwps->data;
1390 int i, cond_end;
1391 int cond_index = *token_index;
1392 char result[128], *value;
1393 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1394 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1396 /* treat ?xx<true> constructs as if they had 2 options. */
1397 if (num_options < 2)
1398 num_options = 2;
1400 int intval = num_options;
1401 /* get_token_value needs to know the number of options in the enum */
1402 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1403 result, sizeof(result), &intval);
1405 /* intval is now the number of the enum option we want to read,
1406 starting from 1. If intval is -1, we check if value is empty. */
1407 if (intval == -1)
1408 intval = (value && *value) ? 1 : num_options;
1409 else if (intval > num_options || intval < 1)
1410 intval = num_options;
1412 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1414 /* skip to the right enum case */
1415 int next = cond_index + 2;
1416 for (i = 1; i < intval; i++)
1418 next = data->tokens[next].value.i;
1420 *token_index = next;
1422 if (prev_val == intval)
1424 /* Same conditional case as previously. Return without clearing the
1425 pictures */
1426 return false;
1429 cond_end = find_conditional_end(data, cond_index + 2);
1430 for (i = cond_index + 3; i < cond_end; i++)
1432 #ifdef HAVE_LCD_BITMAP
1433 /* clear all pictures in the conditional and nested ones */
1434 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1435 clear_image_pos(gwps, data->tokens[i].value.i);
1436 #endif
1437 #ifdef HAVE_ALBUMART
1438 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1439 draw_album_art(gwps, audio_current_aa_hid(), true);
1440 #endif
1443 return true;
1446 /* Read a (sub)line to the given alignment format buffer.
1447 linebuf is the buffer where the data is actually stored.
1448 align is the alignment format that'll be used to display the text.
1449 The return value indicates whether the line needs to be updated.
1451 static bool get_line(struct gui_wps *gwps,
1452 int line, int subline,
1453 struct align_pos *align,
1454 char *linebuf,
1455 int linebuf_size)
1457 struct wps_data *data = gwps->data;
1459 char temp_buf[128];
1460 char *buf = linebuf; /* will always point to the writing position */
1461 char *linebuf_end = linebuf + linebuf_size - 1;
1462 int i, last_token_idx;
1463 bool update = false;
1465 /* alignment-related variables */
1466 int cur_align;
1467 char* cur_align_start;
1468 cur_align_start = buf;
1469 cur_align = WPS_ALIGN_LEFT;
1470 align->left = NULL;
1471 align->center = NULL;
1472 align->right = NULL;
1474 #ifdef HAVE_LCD_BITMAP
1475 /* Reset margins - only bitmap targets modify them */
1476 gwps->display->setmargins(0, gwps->display->getymargin());
1477 #endif
1479 /* Process all tokens of the desired subline */
1480 last_token_idx = wps_last_token_index(data, line, subline);
1481 for (i = wps_first_token_index(data, line, subline);
1482 i <= last_token_idx; i++)
1484 switch(data->tokens[i].type)
1486 case WPS_TOKEN_CONDITIONAL:
1487 /* place ourselves in the right conditional case */
1488 update |= evaluate_conditional(gwps, &i);
1489 break;
1491 case WPS_TOKEN_CONDITIONAL_OPTION:
1492 /* we've finished in the curent conditional case,
1493 skip to the end of the conditional structure */
1494 i = find_conditional_end(data, i);
1495 break;
1497 #ifdef HAVE_LCD_BITMAP
1498 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1500 struct gui_img *img = data->img;
1501 int n = data->tokens[i].value.i;
1502 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1503 img[n].display = true;
1504 break;
1506 #endif
1508 case WPS_TOKEN_ALIGN_LEFT:
1509 case WPS_TOKEN_ALIGN_CENTER:
1510 case WPS_TOKEN_ALIGN_RIGHT:
1511 /* remember where the current aligned text started */
1512 switch (cur_align)
1514 case WPS_ALIGN_LEFT:
1515 align->left = cur_align_start;
1516 break;
1518 case WPS_ALIGN_CENTER:
1519 align->center = cur_align_start;
1520 break;
1522 case WPS_ALIGN_RIGHT:
1523 align->right = cur_align_start;
1524 break;
1526 /* start a new alignment */
1527 switch (data->tokens[i].type)
1529 case WPS_TOKEN_ALIGN_LEFT:
1530 cur_align = WPS_ALIGN_LEFT;
1531 break;
1532 case WPS_TOKEN_ALIGN_CENTER:
1533 cur_align = WPS_ALIGN_CENTER;
1534 break;
1535 case WPS_TOKEN_ALIGN_RIGHT:
1536 cur_align = WPS_ALIGN_RIGHT;
1537 break;
1538 default:
1539 break;
1541 *buf++ = 0;
1542 cur_align_start = buf;
1543 break;
1545 default:
1547 /* get the value of the tag and copy it to the buffer */
1548 char *value = get_token_value(gwps, &data->tokens[i],
1549 temp_buf, sizeof(temp_buf), NULL);
1550 if (value)
1552 update = true;
1553 while (*value && (buf < linebuf_end))
1554 *buf++ = *value++;
1556 break;
1561 /* close the current alignment */
1562 switch (cur_align)
1564 case WPS_ALIGN_LEFT:
1565 align->left = cur_align_start;
1566 break;
1568 case WPS_ALIGN_CENTER:
1569 align->center = cur_align_start;
1570 break;
1572 case WPS_ALIGN_RIGHT:
1573 align->right = cur_align_start;
1574 break;
1577 return update;
1580 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1582 struct wps_data *data = gwps->data;
1583 int i;
1584 int subline_idx = wps_subline_index(data, line, subline);
1585 int last_token_idx = wps_last_token_index(data, line, subline);
1587 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1589 for (i = wps_first_token_index(data, line, subline);
1590 i <= last_token_idx; i++)
1592 switch(data->tokens[i].type)
1594 case WPS_TOKEN_CONDITIONAL:
1595 /* place ourselves in the right conditional case */
1596 evaluate_conditional(gwps, &i);
1597 break;
1599 case WPS_TOKEN_CONDITIONAL_OPTION:
1600 /* we've finished in the curent conditional case,
1601 skip to the end of the conditional structure */
1602 i = find_conditional_end(data, i);
1603 break;
1605 case WPS_TOKEN_SUBLINE_TIMEOUT:
1606 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1607 break;
1609 default:
1610 break;
1615 /* Calculates which subline should be displayed for the specified line
1616 Returns true iff the subline must be refreshed */
1617 static bool update_curr_subline(struct gui_wps *gwps, int line)
1619 struct wps_data *data = gwps->data;
1621 int search, search_start, num_sublines;
1622 bool reset_subline;
1623 bool new_subline_refresh;
1624 bool only_one_subline;
1626 num_sublines = data->lines[line].num_sublines;
1627 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1628 new_subline_refresh = false;
1629 only_one_subline = false;
1631 /* if time to advance to next sub-line */
1632 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1633 reset_subline)
1635 /* search all sublines until the next subline with time > 0
1636 is found or we get back to the subline we started with */
1637 if (reset_subline)
1638 search_start = 0;
1639 else
1640 search_start = data->lines[line].curr_subline;
1642 for (search = 0; search < num_sublines; search++)
1644 data->lines[line].curr_subline++;
1646 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1647 if (data->lines[line].curr_subline == num_sublines)
1649 if (data->lines[line].curr_subline == 1)
1650 only_one_subline = true;
1651 data->lines[line].curr_subline = 0;
1654 /* if back where we started after search or
1655 only one subline is defined on the line */
1656 if (((search > 0) &&
1657 (data->lines[line].curr_subline == search_start)) ||
1658 only_one_subline)
1660 /* no other subline with a time > 0 exists */
1661 data->lines[line].subline_expire_time = (reset_subline ?
1662 current_tick :
1663 data->lines[line].subline_expire_time) + 100 * HZ;
1664 break;
1666 else
1668 /* get initial time multiplier for this subline */
1669 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1671 int subline_idx = wps_subline_index(data, line,
1672 data->lines[line].curr_subline);
1674 /* only use this subline if subline time > 0 */
1675 if (data->sublines[subline_idx].time_mult > 0)
1677 new_subline_refresh = true;
1678 data->lines[line].subline_expire_time = (reset_subline ?
1679 current_tick : data->lines[line].subline_expire_time) +
1680 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1681 break;
1687 return new_subline_refresh;
1690 /* Display a line appropriately according to its alignment format.
1691 format_align contains the text, separated between left, center and right.
1692 line is the index of the line on the screen.
1693 scroll indicates whether the line is a scrolling one or not.
1695 static void write_line(struct screen *display,
1696 struct align_pos *format_align,
1697 int line,
1698 bool scroll)
1701 int left_width = 0, left_xpos;
1702 int center_width = 0, center_xpos;
1703 int right_width = 0, right_xpos;
1704 int ypos;
1705 int space_width;
1706 int string_height;
1707 int scroll_width;
1709 /* calculate different string sizes and positions */
1710 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1711 if (format_align->left != 0) {
1712 display->getstringsize((unsigned char *)format_align->left,
1713 &left_width, &string_height);
1716 if (format_align->right != 0) {
1717 display->getstringsize((unsigned char *)format_align->right,
1718 &right_width, &string_height);
1721 if (format_align->center != 0) {
1722 display->getstringsize((unsigned char *)format_align->center,
1723 &center_width, &string_height);
1726 left_xpos = display->getxmargin();
1727 right_xpos = (display->width - right_width);
1728 center_xpos = (display->width + left_xpos - center_width) / 2;
1730 scroll_width = display->width - left_xpos;
1732 /* Checks for overlapping strings.
1733 If needed the overlapping strings will be merged, separated by a
1734 space */
1736 /* CASE 1: left and centered string overlap */
1737 /* there is a left string, need to merge left and center */
1738 if ((left_width != 0 && center_width != 0) &&
1739 (left_xpos + left_width + space_width > center_xpos)) {
1740 /* replace the former separator '\0' of left and
1741 center string with a space */
1742 *(--format_align->center) = ' ';
1743 /* calculate the new width and position of the merged string */
1744 left_width = left_width + space_width + center_width;
1745 /* there is no centered string anymore */
1746 center_width = 0;
1748 /* there is no left string, move center to left */
1749 if ((left_width == 0 && center_width != 0) &&
1750 (left_xpos + left_width > center_xpos)) {
1751 /* move the center string to the left string */
1752 format_align->left = format_align->center;
1753 /* calculate the new width and position of the string */
1754 left_width = center_width;
1755 /* there is no centered string anymore */
1756 center_width = 0;
1759 /* CASE 2: centered and right string overlap */
1760 /* there is a right string, need to merge center and right */
1761 if ((center_width != 0 && right_width != 0) &&
1762 (center_xpos + center_width + space_width > right_xpos)) {
1763 /* replace the former separator '\0' of center and
1764 right string with a space */
1765 *(--format_align->right) = ' ';
1766 /* move the center string to the right after merge */
1767 format_align->right = format_align->center;
1768 /* calculate the new width and position of the merged string */
1769 right_width = center_width + space_width + right_width;
1770 right_xpos = (display->width - right_width);
1771 /* there is no centered string anymore */
1772 center_width = 0;
1774 /* there is no right string, move center to right */
1775 if ((center_width != 0 && right_width == 0) &&
1776 (center_xpos + center_width > right_xpos)) {
1777 /* move the center string to the right string */
1778 format_align->right = format_align->center;
1779 /* calculate the new width and position of the string */
1780 right_width = center_width;
1781 right_xpos = (display->width - right_width);
1782 /* there is no centered string anymore */
1783 center_width = 0;
1786 /* CASE 3: left and right overlap
1787 There is no center string anymore, either there never
1788 was one or it has been merged in case 1 or 2 */
1789 /* there is a left string, need to merge left and right */
1790 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1791 (left_xpos + left_width + space_width > right_xpos)) {
1792 /* replace the former separator '\0' of left and
1793 right string with a space */
1794 *(--format_align->right) = ' ';
1795 /* calculate the new width and position of the string */
1796 left_width = left_width + space_width + right_width;
1797 /* there is no right string anymore */
1798 right_width = 0;
1800 /* there is no left string, move right to left */
1801 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1802 (left_width > right_xpos)) {
1803 /* move the right string to the left string */
1804 format_align->left = format_align->right;
1805 /* calculate the new width and position of the string */
1806 left_width = right_width;
1807 /* there is no right string anymore */
1808 right_width = 0;
1811 ypos = (line * string_height) + display->getymargin();
1814 if (scroll && ((left_width > scroll_width) ||
1815 (center_width > scroll_width) ||
1816 (right_width > scroll_width)))
1818 display->puts_scroll(0, line,
1819 (unsigned char *)format_align->left);
1821 else
1823 #ifdef HAVE_LCD_BITMAP
1824 /* clear the line first */
1825 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1826 display->fillrect(left_xpos, ypos, display->width, string_height);
1827 display->set_drawmode(DRMODE_SOLID);
1828 #endif
1830 /* Nasty hack: we output an empty scrolling string,
1831 which will reset the scroller for that line */
1832 display->puts_scroll(0, line, (unsigned char *)"");
1834 /* print aligned strings */
1835 if (left_width != 0)
1837 display->putsxy(left_xpos, ypos,
1838 (unsigned char *)format_align->left);
1840 if (center_width != 0)
1842 display->putsxy(center_xpos, ypos,
1843 (unsigned char *)format_align->center);
1845 if (right_width != 0)
1847 display->putsxy(right_xpos, ypos,
1848 (unsigned char *)format_align->right);
1853 /* Refresh the WPS according to refresh_mode. */
1854 bool gui_wps_refresh(struct gui_wps *gwps,
1855 int ffwd_offset,
1856 unsigned char refresh_mode)
1858 struct wps_data *data = gwps->data;
1859 struct screen *display = gwps->display;
1860 struct wps_state *state = gwps->state;
1862 if(!gwps || !data || !state || !display)
1863 return false;
1865 int line, i, subline_idx;
1866 unsigned char flags;
1867 char linebuf[MAX_PATH];
1869 struct align_pos align;
1870 align.left = NULL;
1871 align.center = NULL;
1872 align.right = NULL;
1874 bool update_line, new_subline_refresh;
1876 #ifdef HAVE_LCD_BITMAP
1877 gui_wps_statusbar_draw(gwps, true);
1879 /* to find out wether the peak meter is enabled we
1880 assume it wasn't until we find a line that contains
1881 the peak meter. We can't use peak_meter_enabled itself
1882 because that would mean to turn off the meter thread
1883 temporarily. (That shouldn't matter unless yield
1884 or sleep is called but who knows...)
1886 bool enable_pm = false;
1888 /* Set images to not to be displayed */
1889 for (i = 0; i < MAX_IMAGES; i++)
1891 data->img[i].display = false;
1893 #endif
1895 /* reset to first subline if refresh all flag is set */
1896 if (refresh_mode == WPS_REFRESH_ALL)
1898 for (i = 0; i < data->num_lines; i++)
1900 data->lines[i].curr_subline = SUBLINE_RESET;
1904 #ifdef HAVE_LCD_CHARCELLS
1905 for (i = 0; i < 8; i++)
1907 if (data->wps_progress_pat[i] == 0)
1908 data->wps_progress_pat[i] = display->get_locked_pattern();
1910 #endif
1912 if (!state->id3)
1914 display->stop_scroll();
1915 return false;
1918 state->ff_rewind_count = ffwd_offset;
1920 for (line = 0; line < data->num_lines; line++)
1922 memset(linebuf, 0, sizeof(linebuf));
1923 update_line = false;
1925 /* get current subline for the line */
1926 new_subline_refresh = update_curr_subline(gwps, line);
1928 subline_idx = wps_subline_index(data, line,
1929 data->lines[line].curr_subline);
1930 flags = data->sublines[subline_idx].line_type;
1932 if (refresh_mode == WPS_REFRESH_ALL || (flags & refresh_mode)
1933 || new_subline_refresh)
1935 /* get_line tells us if we need to update the line */
1936 update_line = get_line(gwps, line, data->lines[line].curr_subline,
1937 &align, linebuf, sizeof(linebuf));
1940 #ifdef HAVE_LCD_BITMAP
1941 /* progressbar */
1942 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1944 /* the progressbar should be alone on its line */
1945 update_line = false;
1946 draw_progressbar(gwps, line);
1949 /* peakmeter */
1950 if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
1952 /* the peakmeter should be alone on its line */
1953 update_line = false;
1955 int h = font_get(FONT_UI)->height;
1956 int peak_meter_y = display->getymargin() + line * h;
1958 /* The user might decide to have the peak meter in the last
1959 line so that it is only displayed if no status bar is
1960 visible. If so we neither want do draw nor enable the
1961 peak meter. */
1962 if (peak_meter_y + h <= display->height) {
1963 /* found a line with a peak meter -> remember that we must
1964 enable it later */
1965 enable_pm = true;
1966 peak_meter_screen(gwps->display, 0, peak_meter_y,
1967 MIN(h, display->height - peak_meter_y));
1971 #else /* HAVE_LCD_CHARCELL */
1973 /* progressbar */
1974 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1976 if (data->full_line_progressbar)
1977 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1978 else
1979 draw_player_progress(gwps);
1981 #endif
1983 if (update_line)
1985 if (flags & WPS_REFRESH_SCROLL)
1987 /* if the line is a scrolling one we don't want to update
1988 too often, so that it has the time to scroll */
1989 if ((refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1990 write_line(display, &align, line, true);
1992 else
1993 write_line(display, &align, line, false);
1997 #ifdef HAVE_LCD_BITMAP
1998 data->peak_meter_enabled = enable_pm;
1999 wps_display_images(gwps);
2000 #endif
2002 display->update();
2004 #ifdef HAVE_BACKLIGHT
2005 if (global_settings.caption_backlight && state->id3)
2007 /* turn on backlight n seconds before track ends, and turn it off n
2008 seconds into the new track. n == backlight_timeout, or 5s */
2009 int n = global_settings.backlight_timeout * 1000;
2011 if ( n < 1000 )
2012 n = 5000; /* use 5s if backlight is always on or off */
2014 if (((state->id3->elapsed < 1000) ||
2015 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2016 (state->paused == false))
2017 backlight_on();
2019 #endif
2020 #ifdef HAVE_REMOTE_LCD
2021 if (global_settings.remote_caption_backlight && state->id3)
2023 /* turn on remote backlight n seconds before track ends, and turn it
2024 off n seconds into the new track. n == remote_backlight_timeout,
2025 or 5s */
2026 int n = global_settings.remote_backlight_timeout * 1000;
2028 if ( n < 1000 )
2029 n = 5000; /* use 5s if backlight is always on or off */
2031 if (((state->id3->elapsed < 1000) ||
2032 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2033 (state->paused == false))
2034 remote_backlight_on();
2036 #endif
2038 return true;