Musepack speed optimization. Speep up 64 bit precision synthesizer by another 1.5MHz...
[kugel-rb.git] / apps / gui / gwps-common.c
blobb90c216f58ca9939be1a709ee4b2044bbe75fce3
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002-2007 Björn Stenberg
11 * Copyright (C) 2007-2008 Nicolas Pennequin
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
19 ****************************************************************************/
20 #include "gwps-common.h"
21 #include "font.h"
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include "system.h"
26 #include "settings.h"
27 #include "rbunicode.h"
28 #include "rtc.h"
29 #include "audio.h"
30 #include "status.h"
31 #include "power.h"
32 #include "powermgmt.h"
33 #include "sound.h"
34 #include "debug.h"
35 #ifdef HAVE_LCD_CHARCELLS
36 #include "hwcompat.h"
37 #endif
38 #include "abrepeat.h"
39 #include "mp3_playback.h"
40 #include "backlight.h"
41 #include "lang.h"
42 #include "misc.h"
43 #include "splash.h"
44 #include "scrollbar.h"
45 #include "led.h"
46 #include "lcd.h"
47 #ifdef HAVE_LCD_BITMAP
48 #include "peakmeter.h"
49 /* Image stuff */
50 #include "bmp.h"
51 #include "albumart.h"
52 #endif
53 #include "dsp.h"
54 #include "action.h"
55 #include "cuesheet.h"
56 #include "playlist.h"
57 #if CONFIG_CODEC == SWCODEC
58 #include "playback.h"
59 #endif
60 #include "backdrop.h"
62 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
63 /* 3% of 30min file == 54s step size */
64 #define MIN_FF_REWIND_STEP 500
66 /* draws the statusbar on the given wps-screen */
67 #ifdef HAVE_LCD_BITMAP
68 static void gui_wps_statusbar_draw(struct gui_wps *wps, bool force)
70 bool draw = global_settings.statusbar;
72 if (wps->data->wps_sb_tag)
73 draw = wps->data->show_sb_on_wps;
75 if (draw)
76 gui_statusbar_draw(wps->statusbar, force);
78 #else
79 #define gui_wps_statusbar_draw(wps, force) \
80 gui_statusbar_draw((wps)->statusbar, (force))
81 #endif
82 #include "pcmbuf.h"
84 /* fades the volume */
85 bool wps_fading_out = false;
86 void fade(bool fade_in)
88 int fp_global_vol = global_settings.volume << 8;
89 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
90 int fp_step = (fp_global_vol - fp_min_vol) / 30;
91 int i;
92 wps_fading_out = !fade_in;
93 if (fade_in) {
94 /* fade in */
95 int fp_volume = fp_min_vol;
97 /* zero out the sound */
98 sound_set_volume(fp_min_vol >> 8);
100 sleep(HZ/10); /* let audio thread run */
101 audio_resume();
103 while (fp_volume < fp_global_vol - fp_step) {
104 fp_volume += fp_step;
105 sound_set_volume(fp_volume >> 8);
106 FOR_NB_SCREENS(i)
107 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
108 sleep(1);
110 sound_set_volume(global_settings.volume);
112 else {
113 /* fade out */
114 int fp_volume = fp_global_vol;
116 while (fp_volume > fp_min_vol + fp_step) {
117 fp_volume -= fp_step;
118 sound_set_volume(fp_volume >> 8);
119 FOR_NB_SCREENS(i)
120 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_NON_STATIC);
121 sleep(1);
123 audio_pause();
124 wps_fading_out = false;
125 #if CONFIG_CODEC != SWCODEC
126 #ifndef SIMULATOR
127 /* let audio thread run and wait for the mas to run out of data */
128 while (!mp3_pause_done())
129 #endif
130 sleep(HZ/10);
131 #endif
133 /* reset volume to what it was before the fade */
134 sound_set_volume(global_settings.volume);
138 /* return true if screen restore is needed
139 return false otherwise
141 bool update_onvol_change(struct gui_wps * gwps)
143 gui_wps_statusbar_draw(gwps, false);
144 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
146 #ifdef HAVE_LCD_CHARCELLS
147 gui_splash(gwps->display, 0, "Vol: %3d dB",
148 sound_val2phys(SOUND_VOLUME, global_settings.volume));
149 return true;
150 #endif
151 return false;
154 void play_hop(int direction)
156 if(!wps_state.id3 || !wps_state.id3->length
157 || global_settings.study_hop_step == 0)
158 return;
159 #define STEP ((unsigned)global_settings.study_hop_step *1000)
160 if(direction == 1
161 && wps_state.id3->length - wps_state.id3->elapsed < STEP+1000) {
162 #if CONFIG_CODEC == SWCODEC
163 if(global_settings.beep)
164 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
165 #endif
166 return;
168 if((direction == -1 && wps_state.id3->elapsed < STEP))
169 wps_state.id3->elapsed = 0;
170 else
171 wps_state.id3->elapsed += STEP *direction;
172 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused) {
173 #if (CONFIG_CODEC == SWCODEC)
174 audio_pre_ff_rewind();
175 #else
176 audio_pause();
177 #endif
179 audio_ff_rewind(wps_state.id3->elapsed);
180 #if (CONFIG_CODEC != SWCODEC)
181 if (!wps_state.paused)
182 audio_resume();
183 #endif
184 #undef STEP
187 bool ffwd_rew(int button)
189 unsigned int step = 0; /* current ff/rewind step */
190 unsigned int max_step = 0; /* maximum ff/rewind step */
191 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
192 int direction = -1; /* forward=1 or backward=-1 */
193 long accel_tick = 0; /* next time at which to bump the step size */
194 bool exit = false;
195 bool usb = false;
196 int i = 0;
198 if (button == ACTION_NONE)
200 status_set_ffmode(0);
201 return usb;
203 while (!exit)
205 switch ( button )
207 case ACTION_WPS_SEEKFWD:
208 direction = 1;
209 case ACTION_WPS_SEEKBACK:
210 if (wps_state.ff_rewind)
212 if (direction == 1)
214 /* fast forwarding, calc max step relative to end */
215 max_step = (wps_state.id3->length -
216 (wps_state.id3->elapsed +
217 ff_rewind_count)) *
218 FF_REWIND_MAX_PERCENT / 100;
220 else
222 /* rewinding, calc max step relative to start */
223 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
224 FF_REWIND_MAX_PERCENT / 100;
227 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
229 if (step > max_step)
230 step = max_step;
232 ff_rewind_count += step * direction;
234 if (global_settings.ff_rewind_accel != 0 &&
235 current_tick >= accel_tick)
237 step *= 2;
238 accel_tick = current_tick +
239 global_settings.ff_rewind_accel*HZ;
242 else
244 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
245 wps_state.id3 && wps_state.id3->length )
247 if (!wps_state.paused)
248 #if (CONFIG_CODEC == SWCODEC)
249 audio_pre_ff_rewind();
250 #else
251 audio_pause();
252 #endif
253 #if CONFIG_KEYPAD == PLAYER_PAD
254 FOR_NB_SCREENS(i)
255 gui_wps[i].display->stop_scroll();
256 #endif
257 if (direction > 0)
258 status_set_ffmode(STATUS_FASTFORWARD);
259 else
260 status_set_ffmode(STATUS_FASTBACKWARD);
262 wps_state.ff_rewind = true;
264 step = 1000 * global_settings.ff_rewind_min_step;
266 accel_tick = current_tick +
267 global_settings.ff_rewind_accel*HZ;
269 else
270 break;
273 if (direction > 0) {
274 if ((wps_state.id3->elapsed + ff_rewind_count) >
275 wps_state.id3->length)
276 ff_rewind_count = wps_state.id3->length -
277 wps_state.id3->elapsed;
279 else {
280 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
281 ff_rewind_count = -wps_state.id3->elapsed;
284 FOR_NB_SCREENS(i)
285 gui_wps_refresh(&gui_wps[i],
286 (wps_state.wps_time_countup == false)?
287 ff_rewind_count:-ff_rewind_count,
288 WPS_REFRESH_PLAYER_PROGRESS |
289 WPS_REFRESH_DYNAMIC);
291 break;
293 case ACTION_WPS_STOPSEEK:
294 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
295 audio_ff_rewind(wps_state.id3->elapsed);
296 ff_rewind_count = 0;
297 wps_state.ff_rewind = false;
298 status_set_ffmode(0);
299 #if (CONFIG_CODEC != SWCODEC)
300 if (!wps_state.paused)
301 audio_resume();
302 #endif
303 #ifdef HAVE_LCD_CHARCELLS
304 gui_wps_display();
305 #endif
306 exit = true;
307 break;
309 default:
310 if(default_event_handler(button) == SYS_USB_CONNECTED) {
311 status_set_ffmode(0);
312 usb = true;
313 exit = true;
315 break;
317 if (!exit)
318 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
320 return usb;
323 bool gui_wps_display(void)
325 int i;
326 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
328 global_status.resume_index = -1;
329 #ifdef HAVE_LCD_BITMAP
330 gui_syncstatusbar_draw(&statusbars, true);
331 #endif
332 gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST));
333 return true;
335 else
337 FOR_NB_SCREENS(i)
339 /* Update the values in the first (default) viewport - in case the user
340 has modified the statusbar or colour settings */
341 #ifdef HAVE_LCD_BITMAP
342 gui_wps[i].data->viewports[0].vp.ymargin = gui_wps[i].display->getymargin();
343 #if LCD_DEPTH > 1
344 if (gui_wps[i].display->depth > 1)
346 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
347 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
349 #endif
350 #endif
352 gui_wps[i].display->clear_display();
353 if (!gui_wps[i].data->wps_loaded) {
354 if ( !gui_wps[i].data->num_tokens ) {
355 /* set the default wps for the main-screen */
356 if(i == 0)
358 #ifdef HAVE_LCD_BITMAP
359 #if LCD_DEPTH > 1
360 unload_wps_backdrop();
361 #endif
362 wps_data_load(gui_wps[i].data,
363 gui_wps[i].display,
364 "%s%?it<%?in<%in. |>%it|%fn>\n"
365 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
366 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
367 "\n"
368 "%al%pc/%pt%ar[%pp:%pe]\n"
369 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
370 "%pb\n"
371 "%pm\n", false);
372 #else
373 wps_data_load(gui_wps[i].data,
374 gui_wps[i].display,
375 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
376 "%pc%?ps<*|/>%pt\n", false);
377 #endif
379 #if NB_SCREENS == 2
380 /* set the default wps for the remote-screen */
381 else if(i == 1)
383 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
384 unload_remote_wps_backdrop();
385 #endif
386 wps_data_load(gui_wps[i].data,
387 gui_wps[i].display,
388 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
389 "%s%?it<%?in<%in. |>%it|%fn>\n"
390 "%al%pc/%pt%ar[%pp:%pe]\n"
391 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
392 "%pb\n", false);
394 #endif
399 yield();
400 FOR_NB_SCREENS(i)
402 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
404 return false;
407 bool update(struct gui_wps *gwps)
409 bool track_changed = audio_has_changed_track();
410 bool retcode = false;
412 gwps->state->nid3 = audio_next_track();
413 if (track_changed)
415 gwps->display->stop_scroll();
416 gwps->state->id3 = audio_current_track();
418 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
419 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
421 /* the current cuesheet isn't the right one any more */
423 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
424 /* We have the new cuesheet in memory (temp_cue),
425 let's make it the current one ! */
426 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
428 else {
429 /* We need to parse the new cuesheet */
431 char cuepath[MAX_PATH];
433 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
434 parse_cuesheet(cuepath, curr_cue))
436 gwps->state->id3->cuesheet_type = 1;
437 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
441 cue_spoof_id3(curr_cue, gwps->state->id3);
444 if (gui_wps_display())
445 retcode = true;
446 else{
447 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
450 if (gwps->state->id3)
452 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
453 sizeof(gwps->state->current_track_path));
454 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
458 if (gwps->state->id3)
460 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
461 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
462 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
463 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
465 /* We've changed tracks within the cuesheet :
466 we need to update the ID3 info and refresh the WPS */
468 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
469 cue_spoof_id3(curr_cue, gwps->state->id3);
471 gwps->display->stop_scroll();
472 if (gui_wps_display())
473 retcode = true;
474 else
475 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
477 else
478 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
481 gui_wps_statusbar_draw(gwps, false);
483 return retcode;
487 void display_keylock_text(bool locked)
489 char* s;
490 int i;
491 FOR_NB_SCREENS(i)
492 gui_wps[i].display->stop_scroll();
494 if(locked)
495 s = str(LANG_KEYLOCK_ON);
496 else
497 s = str(LANG_KEYLOCK_OFF);
498 gui_syncsplash(HZ, s);
501 #ifdef HAVE_LCD_BITMAP
503 static void draw_progressbar(struct gui_wps *gwps, int line)
505 struct wps_data *data = gwps->data;
506 struct screen *display = gwps->display;
507 struct wps_state *state = gwps->state;
508 int h = font_get(display->getfont())->height;
510 int sb_y;
511 if (data->progress_top < 0)
512 sb_y = line*h + display->getymargin() +
513 ((h > data->progress_height + 1)
514 ? (h - data->progress_height) / 2 : 1);
515 else
516 sb_y = data->progress_top;
518 if (!data->progress_end)
519 data->progress_end=display->getwidth();
521 if (gwps->data->progressbar.have_bitmap_pb)
522 gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
523 data->progress_start, sb_y,
524 data->progress_end-data->progress_start,
525 data->progressbar.bm.height,
526 state->id3->length ? state->id3->length : 1, 0,
527 state->id3->length ? state->id3->elapsed
528 + state->ff_rewind_count : 0,
529 HORIZONTAL);
530 else
531 gui_scrollbar_draw(display, data->progress_start, sb_y,
532 data->progress_end-data->progress_start,
533 data->progress_height,
534 state->id3->length ? state->id3->length : 1, 0,
535 state->id3->length ? state->id3->elapsed
536 + state->ff_rewind_count : 0,
537 HORIZONTAL);
539 #ifdef AB_REPEAT_ENABLE
540 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
541 ab_draw_markers(display, state->id3->length,
542 data->progress_start, data->progress_end, sb_y,
543 data->progress_height);
544 #endif
546 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
547 cue_draw_markers(display, state->id3->length,
548 data->progress_start, data->progress_end,
549 sb_y+1, data->progress_height-2);
552 /* clears the area where the image was shown */
553 static void clear_image_pos(struct gui_wps *gwps, int n)
555 if(!gwps)
556 return;
557 struct wps_data *data = gwps->data;
558 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
559 gwps->display->fillrect(data->img[n].x, data->img[n].y,
560 data->img[n].bm.width, data->img[n].subimage_height);
561 gwps->display->set_drawmode(DRMODE_SOLID);
564 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
566 struct screen *display = gwps->display;
567 struct wps_data *data = gwps->data;
568 if(data->img[n].always_display)
569 display->set_drawmode(DRMODE_FG);
570 else
571 display->set_drawmode(DRMODE_SOLID);
573 #if LCD_DEPTH > 1
574 if(data->img[n].bm.format == FORMAT_MONO) {
575 #endif
576 display->mono_bitmap_part(data->img[n].bm.data,
577 0, data->img[n].subimage_height * subimage,
578 data->img[n].bm.width, data->img[n].x,
579 data->img[n].y, data->img[n].bm.width,
580 data->img[n].subimage_height);
581 #if LCD_DEPTH > 1
582 } else {
583 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
584 0, data->img[n].subimage_height * subimage,
585 data->img[n].bm.width, data->img[n].x,
586 data->img[n].y, data->img[n].bm.width,
587 data->img[n].subimage_height);
589 #endif
592 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
594 if(!gwps || !gwps->data || !gwps->display)
595 return;
597 int n;
598 struct wps_data *data = gwps->data;
599 struct screen *display = gwps->display;
601 for (n = 0; n < MAX_IMAGES; n++)
603 if (data->img[n].loaded)
605 if (data->img[n].display >= 0)
607 wps_draw_image(gwps, n, data->img[n].display);
608 } else if (data->img[n].always_display && data->img[n].vp == vp)
610 wps_draw_image(gwps, n, 0);
614 display->set_drawmode(DRMODE_SOLID);
617 #else /* HAVE_LCD_CHARCELL */
619 static bool draw_player_progress(struct gui_wps *gwps)
621 struct wps_state *state = gwps->state;
622 struct screen *display = gwps->display;
623 unsigned char progress_pattern[7];
624 int pos = 0;
625 int i;
627 if (!state->id3)
628 return false;
630 if (state->id3->length)
631 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
632 / state->id3->length;
634 for (i = 0; i < 7; i++, pos -= 5)
636 if (pos <= 0)
637 progress_pattern[i] = 0x1f;
638 else if (pos >= 5)
639 progress_pattern[i] = 0x00;
640 else
641 progress_pattern[i] = 0x1f >> pos;
644 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
645 return true;
648 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
650 static const unsigned char numbers[10][4] = {
651 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
652 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
653 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
654 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
655 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
656 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
657 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
658 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
659 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
660 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
663 struct wps_state *state = gwps->state;
664 struct screen *display = gwps->display;
665 struct wps_data *data = gwps->data;
666 unsigned char progress_pattern[7];
667 char timestr[10];
668 int time;
669 int time_idx = 0;
670 int pos = 0;
671 int pat_idx = 1;
672 int digit, i, j;
673 bool softchar;
675 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
676 return;
678 time = state->id3->elapsed + state->ff_rewind_count;
679 if (state->id3->length)
680 pos = 55 * time / state->id3->length;
682 memset(timestr, 0, sizeof(timestr));
683 format_time(timestr, sizeof(timestr)-2, time);
684 timestr[strlen(timestr)] = ':'; /* always safe */
686 for (i = 0; i < 11; i++, pos -= 5)
688 softchar = false;
689 memset(progress_pattern, 0, sizeof(progress_pattern));
691 if ((digit = timestr[time_idx]))
693 softchar = true;
694 digit -= '0';
696 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
698 memcpy(progress_pattern, numbers[digit], 4);
699 time_idx += 2;
701 else /* tens, shifted right */
703 for (j = 0; j < 4; j++)
704 progress_pattern[j] = numbers[digit][j] >> 1;
706 if (time_idx > 0) /* not the first group, add colon in front */
708 progress_pattern[1] |= 0x10;
709 progress_pattern[3] |= 0x10;
711 time_idx++;
714 if (pos >= 5)
715 progress_pattern[5] = progress_pattern[6] = 0x1f;
718 if (pos > 0 && pos < 5)
720 softchar = true;
721 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
724 if (softchar && pat_idx < 8)
726 display->define_pattern(data->wps_progress_pat[pat_idx],
727 progress_pattern);
728 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
729 pat_idx++;
731 else if (pos <= 0)
732 buf = utf8encode(' ', buf);
733 else
734 buf = utf8encode(0xe115, buf); /* 2/7 _ */
736 *buf = '\0';
739 #endif /* HAVE_LCD_CHARCELL */
741 static char* get_codectype(const struct mp3entry* id3)
743 if (id3->codectype < AFMT_NUM_CODECS) {
744 return (char*)audio_formats[id3->codectype].label;
745 } else {
746 return NULL;
750 /* Extract a part from a path.
752 * buf - buffer extract part to.
753 * buf_size - size of buffer.
754 * path - path to extract from.
755 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
756 * parent of parent, etc.
758 * Returns buf if the desired level was found, NULL otherwise.
760 static char* get_dir(char* buf, int buf_size, const char* path, int level)
762 const char* sep;
763 const char* last_sep;
764 int len;
766 sep = path + strlen(path);
767 last_sep = sep;
769 while (sep > path)
771 if ('/' == *(--sep))
773 if (!level)
774 break;
776 level--;
777 last_sep = sep - 1;
781 if (level || (last_sep <= sep))
782 return NULL;
784 len = MIN(last_sep - sep, buf_size - 1);
785 strncpy(buf, sep + 1, len);
786 buf[len] = 0;
787 return buf;
790 /* Return the tag found at index i and write its value in buf.
791 The return value is buf if the tag had a value, or NULL if not.
793 intval is used with conditionals/enums: when this function is called,
794 intval should contain the number of options in the conditional/enum.
795 When this function returns, intval is -1 if the tag is non numeric or,
796 if the tag is numeric, intval is the enum case we want to go to.
797 When not treating a conditional/enum, intval should be NULL.
799 static char *get_token_value(struct gui_wps *gwps,
800 struct wps_token *token,
801 char *buf, int buf_size,
802 int *intval)
804 if (!gwps)
805 return NULL;
807 struct wps_data *data = gwps->data;
808 struct wps_state *state = gwps->state;
810 if (!data || !state)
811 return NULL;
813 struct mp3entry *id3;
815 if (token->next)
816 id3 = state->nid3;
817 else
818 id3 = state->id3;
820 if (!id3)
821 return NULL;
823 #if CONFIG_RTC
824 struct tm* tm = NULL;
826 /* if the token is an RTC one, update the time
827 and do the necessary checks */
828 if (token->type >= WPS_TOKENS_RTC_BEGIN
829 && token->type <= WPS_TOKENS_RTC_END)
831 tm = get_time();
833 if (!valid_time(tm))
834 return NULL;
836 #endif
838 int limit = 1;
839 if (intval)
841 limit = *intval;
842 *intval = -1;
845 switch (token->type)
847 case WPS_TOKEN_CHARACTER:
848 return &(token->value.c);
850 case WPS_TOKEN_STRING:
851 return data->strings[token->value.i];
853 case WPS_TOKEN_TRACK_TIME_ELAPSED:
854 format_time(buf, buf_size,
855 id3->elapsed + state->ff_rewind_count);
856 return buf;
858 case WPS_TOKEN_TRACK_TIME_REMAINING:
859 format_time(buf, buf_size,
860 id3->length - id3->elapsed -
861 state->ff_rewind_count);
862 return buf;
864 case WPS_TOKEN_TRACK_LENGTH:
865 format_time(buf, buf_size, id3->length);
866 return buf;
868 case WPS_TOKEN_PLAYLIST_ENTRIES:
869 snprintf(buf, buf_size, "%d", playlist_amount());
870 return buf;
872 case WPS_TOKEN_PLAYLIST_NAME:
873 return playlist_name(NULL, buf, buf_size);
875 case WPS_TOKEN_PLAYLIST_POSITION:
876 snprintf(buf, buf_size, "%d", playlist_get_display_index());
877 return buf;
879 case WPS_TOKEN_PLAYLIST_SHUFFLE:
880 if ( global_settings.playlist_shuffle )
881 return "s";
882 else
883 return NULL;
884 break;
886 case WPS_TOKEN_VOLUME:
887 snprintf(buf, buf_size, "%d", global_settings.volume);
888 if (intval)
890 if (global_settings.volume == sound_min(SOUND_VOLUME))
892 *intval = 1;
894 else if (global_settings.volume == 0)
896 *intval = limit - 1;
898 else if (global_settings.volume > 0)
900 *intval = limit;
902 else
904 *intval = (limit - 3) * (global_settings.volume
905 - sound_min(SOUND_VOLUME) - 1)
906 / (-1 - sound_min(SOUND_VOLUME)) + 2;
909 return buf;
911 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
912 if (id3->length <= 0)
913 return NULL;
915 if (intval)
917 *intval = limit * (id3->elapsed + state->ff_rewind_count)
918 / id3->length + 1;
920 snprintf(buf, buf_size, "%d",
921 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
922 return buf;
924 case WPS_TOKEN_METADATA_ARTIST:
925 return id3->artist;
927 case WPS_TOKEN_METADATA_COMPOSER:
928 return id3->composer;
930 case WPS_TOKEN_METADATA_ALBUM:
931 return id3->album;
933 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
934 return id3->albumartist;
936 case WPS_TOKEN_METADATA_GROUPING:
937 return id3->grouping;
939 case WPS_TOKEN_METADATA_GENRE:
940 return id3->genre_string;
942 case WPS_TOKEN_METADATA_DISC_NUMBER:
943 if (id3->disc_string)
944 return id3->disc_string;
945 if (id3->discnum) {
946 snprintf(buf, buf_size, "%d", id3->discnum);
947 return buf;
949 return NULL;
951 case WPS_TOKEN_METADATA_TRACK_NUMBER:
952 if (id3->track_string)
953 return id3->track_string;
955 if (id3->tracknum) {
956 snprintf(buf, buf_size, "%d", id3->tracknum);
957 return buf;
959 return NULL;
961 case WPS_TOKEN_METADATA_TRACK_TITLE:
962 return id3->title;
964 case WPS_TOKEN_METADATA_VERSION:
965 switch (id3->id3version)
967 case ID3_VER_1_0:
968 return "1";
970 case ID3_VER_1_1:
971 return "1.1";
973 case ID3_VER_2_2:
974 return "2.2";
976 case ID3_VER_2_3:
977 return "2.3";
979 case ID3_VER_2_4:
980 return "2.4";
982 default:
983 return NULL;
986 case WPS_TOKEN_METADATA_YEAR:
987 if( id3->year_string )
988 return id3->year_string;
990 if (id3->year) {
991 snprintf(buf, buf_size, "%d", id3->year);
992 return buf;
994 return NULL;
996 case WPS_TOKEN_METADATA_COMMENT:
997 return id3->comment;
999 #ifdef HAVE_ALBUMART
1000 case WPS_TOKEN_ALBUMART_DISPLAY:
1001 draw_album_art(gwps, audio_current_aa_hid(), false);
1002 return NULL;
1004 case WPS_TOKEN_ALBUMART_FOUND:
1005 if (audio_current_aa_hid() >= 0) {
1006 snprintf(buf, buf_size, "C");
1007 return buf;
1009 return NULL;
1010 #endif
1012 case WPS_TOKEN_FILE_BITRATE:
1013 if(id3->bitrate)
1014 snprintf(buf, buf_size, "%d", id3->bitrate);
1015 else
1016 snprintf(buf, buf_size, "?");
1017 return buf;
1019 case WPS_TOKEN_FILE_CODEC:
1020 if (intval)
1022 if(id3->codectype == AFMT_UNKNOWN)
1023 *intval = AFMT_NUM_CODECS;
1024 else
1025 *intval = id3->codectype;
1027 return get_codectype(id3);
1029 case WPS_TOKEN_FILE_FREQUENCY:
1030 snprintf(buf, buf_size, "%ld", id3->frequency);
1031 return buf;
1033 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
1034 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1035 if ((id3->frequency % 1000) < 100)
1036 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
1037 else
1038 snprintf(buf, buf_size, "%ld.%d",
1039 id3->frequency / 1000,
1040 (id3->frequency % 1000) / 100);
1041 return buf;
1043 case WPS_TOKEN_FILE_NAME:
1044 if (get_dir(buf, buf_size, id3->path, 0)) {
1045 /* Remove extension */
1046 char* sep = strrchr(buf, '.');
1047 if (NULL != sep) {
1048 *sep = 0;
1050 return buf;
1052 else {
1053 return NULL;
1056 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1057 return get_dir(buf, buf_size, id3->path, 0);
1059 case WPS_TOKEN_FILE_PATH:
1060 return id3->path;
1062 case WPS_TOKEN_FILE_SIZE:
1063 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1064 return buf;
1066 case WPS_TOKEN_FILE_VBR:
1067 return id3->vbr ? "(avg)" : NULL;
1069 case WPS_TOKEN_FILE_DIRECTORY:
1070 return get_dir(buf, buf_size, id3->path, token->value.i);
1072 case WPS_TOKEN_BATTERY_PERCENT:
1074 int l = battery_level();
1076 if (intval)
1078 limit = MAX(limit, 2);
1079 if (l > -1) {
1080 /* First enum is used for "unknown level". */
1081 *intval = (limit - 1) * l / 100 + 2;
1082 } else {
1083 *intval = 1;
1087 if (l > -1) {
1088 snprintf(buf, buf_size, "%d", l);
1089 return buf;
1090 } else {
1091 return "?";
1095 case WPS_TOKEN_BATTERY_VOLTS:
1097 unsigned int v = battery_voltage();
1098 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1099 return buf;
1102 case WPS_TOKEN_BATTERY_TIME:
1104 int t = battery_time();
1105 if (t >= 0)
1106 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1107 else
1108 strncpy(buf, "?h ?m", buf_size);
1109 return buf;
1112 #if CONFIG_CHARGING
1113 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1115 if(charger_input_state==CHARGER)
1116 return "p";
1117 else
1118 return NULL;
1120 #endif
1121 #if CONFIG_CHARGING >= CHARGING_MONITOR
1122 case WPS_TOKEN_BATTERY_CHARGING:
1124 if (charge_state == CHARGING || charge_state == TOPOFF) {
1125 return "c";
1126 } else {
1127 return NULL;
1130 #endif
1131 case WPS_TOKEN_BATTERY_SLEEPTIME:
1133 if (get_sleep_timer() == 0)
1134 return NULL;
1135 else
1137 format_time(buf, buf_size, get_sleep_timer() * 1000);
1138 return buf;
1142 case WPS_TOKEN_PLAYBACK_STATUS:
1144 int status = audio_status();
1145 int mode = 1;
1146 if (status == AUDIO_STATUS_PLAY)
1147 mode = 2;
1148 if (wps_fading_out ||
1149 (status & AUDIO_STATUS_PAUSE && !status_get_ffmode()))
1150 mode = 3;
1151 if (status_get_ffmode() == STATUS_FASTFORWARD)
1152 mode = 4;
1153 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1154 mode = 5;
1156 if (intval) {
1157 *intval = mode;
1160 snprintf(buf, buf_size, "%d", mode-1);
1161 return buf;
1164 case WPS_TOKEN_REPEAT_MODE:
1165 if (intval)
1166 *intval = global_settings.repeat_mode + 1;
1167 snprintf(buf, buf_size, "%d", *intval);
1168 return buf;
1169 case WPS_TOKEN_RTC_12HOUR_CFG:
1170 if (intval)
1171 *intval = global_settings.timeformat + 1;
1172 snprintf(buf, buf_size, "%d", *intval);
1173 return buf;
1174 #if CONFIG_RTC
1175 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1176 /* d: day of month (01..31) */
1177 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1178 return buf;
1180 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1181 /* e: day of month, blank padded ( 1..31) */
1182 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1183 return buf;
1185 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1186 /* H: hour (00..23) */
1187 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1188 return buf;
1190 case WPS_TOKEN_RTC_HOUR_24:
1191 /* k: hour ( 0..23) */
1192 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1193 return buf;
1195 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1196 /* I: hour (01..12) */
1197 snprintf(buf, buf_size, "%02d",
1198 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1199 return buf;
1201 case WPS_TOKEN_RTC_HOUR_12:
1202 /* l: hour ( 1..12) */
1203 snprintf(buf, buf_size, "%2d",
1204 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1205 return buf;
1207 case WPS_TOKEN_RTC_MONTH:
1208 /* m: month (01..12) */
1209 if (intval)
1210 *intval = tm->tm_mon + 1;
1211 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1212 return buf;
1214 case WPS_TOKEN_RTC_MINUTE:
1215 /* M: minute (00..59) */
1216 snprintf(buf, buf_size, "%02d", tm->tm_min);
1217 return buf;
1219 case WPS_TOKEN_RTC_SECOND:
1220 /* S: second (00..59) */
1221 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1222 return buf;
1224 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1225 /* y: last two digits of year (00..99) */
1226 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1227 return buf;
1229 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1230 /* Y: year (1970...) */
1231 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1232 return buf;
1234 case WPS_TOKEN_RTC_AM_PM_UPPER:
1235 /* p: upper case AM or PM indicator */
1236 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1237 return buf;
1239 case WPS_TOKEN_RTC_AM_PM_LOWER:
1240 /* P: lower case am or pm indicator */
1241 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1242 return buf;
1244 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1245 /* a: abbreviated weekday name (Sun..Sat) */
1246 snprintf(buf, buf_size, "%s",str(LANG_WEEKDAY_SUNDAY + tm->tm_wday));
1247 return buf;
1249 case WPS_TOKEN_RTC_MONTH_NAME:
1250 /* b: abbreviated month name (Jan..Dec) */
1251 snprintf(buf, buf_size, "%s",str(LANG_MONTH_JANUARY + tm->tm_mon));
1252 return buf;
1254 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1255 /* u: day of week (1..7); 1 is Monday */
1256 if (intval)
1257 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1258 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1259 return buf;
1261 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1262 /* w: day of week (0..6); 0 is Sunday */
1263 if (intval)
1264 *intval = tm->tm_wday + 1;
1265 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1266 return buf;
1267 #else
1268 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1269 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1270 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1271 case WPS_TOKEN_RTC_HOUR_24:
1272 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1273 case WPS_TOKEN_RTC_HOUR_12:
1274 case WPS_TOKEN_RTC_MONTH:
1275 case WPS_TOKEN_RTC_MINUTE:
1276 case WPS_TOKEN_RTC_SECOND:
1277 case WPS_TOKEN_RTC_AM_PM_UPPER:
1278 case WPS_TOKEN_RTC_AM_PM_LOWER:
1279 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1280 strncpy(buf, "--", buf_size);
1281 return buf;
1282 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1283 strncpy(buf, "----", buf_size);
1284 return buf;
1285 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1286 case WPS_TOKEN_RTC_MONTH_NAME:
1287 strncpy(buf, "---", buf_size);
1288 return buf;
1289 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1290 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1291 strncpy(buf, "-", buf_size);
1292 return buf;
1293 #endif
1295 #ifdef HAVE_LCD_CHARCELLS
1296 case WPS_TOKEN_PROGRESSBAR:
1298 char *end = utf8encode(data->wps_progress_pat[0], buf);
1299 *end = '\0';
1300 return buf;
1303 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1304 if(is_new_player())
1306 /* we need 11 characters (full line) for
1307 progress-bar */
1308 snprintf(buf, buf_size, " ");
1310 else
1312 /* Tell the user if we have an OldPlayer */
1313 snprintf(buf, buf_size, " <Old LCD> ");
1315 return buf;
1316 #endif
1318 #ifdef HAVE_TAGCACHE
1319 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1320 if (intval) {
1321 *intval = id3->playcount + 1;
1323 snprintf(buf, buf_size, "%ld", id3->playcount);
1324 return buf;
1326 case WPS_TOKEN_DATABASE_RATING:
1327 if (intval) {
1328 *intval = id3->rating + 1;
1330 snprintf(buf, buf_size, "%d", id3->rating);
1331 return buf;
1333 case WPS_TOKEN_DATABASE_AUTOSCORE:
1334 if (intval)
1335 *intval = id3->score + 1;
1337 snprintf(buf, buf_size, "%d", id3->score);
1338 return buf;
1339 #endif
1341 #if (CONFIG_CODEC == SWCODEC)
1342 case WPS_TOKEN_CROSSFADE:
1343 if (intval)
1344 *intval = global_settings.crossfade + 1;
1345 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1346 return buf;
1348 case WPS_TOKEN_REPLAYGAIN:
1350 int val;
1352 if (global_settings.replaygain == 0)
1353 val = 1; /* off */
1354 else
1356 int type =
1357 get_replaygain_mode(id3->track_gain_string != NULL,
1358 id3->album_gain_string != NULL);
1359 if (type < 0)
1360 val = 6; /* no tag */
1361 else
1362 val = type + 2;
1364 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1365 val += 2;
1368 if (intval)
1369 *intval = val;
1371 switch (val)
1373 case 1:
1374 case 6:
1375 return "+0.00 dB";
1376 break;
1377 case 2:
1378 case 4:
1379 strncpy(buf, id3->track_gain_string, buf_size);
1380 break;
1381 case 3:
1382 case 5:
1383 strncpy(buf, id3->album_gain_string, buf_size);
1384 break;
1386 return buf;
1388 #endif /* (CONFIG_CODEC == SWCODEC) */
1390 #if (CONFIG_CODEC != MAS3507D)
1391 case WPS_TOKEN_SOUND_PITCH:
1393 int val = sound_get_pitch();
1394 snprintf(buf, buf_size, "%d.%d",
1395 val / 10, val % 10);
1396 return buf;
1398 #endif
1400 case WPS_TOKEN_MAIN_HOLD:
1401 #ifdef HAS_BUTTON_HOLD
1402 if (button_hold())
1403 #else
1404 if (is_keys_locked())
1405 #endif /*hold switch or softlock*/
1406 return "h";
1407 else
1408 return NULL;
1410 #ifdef HAS_REMOTE_BUTTON_HOLD
1411 case WPS_TOKEN_REMOTE_HOLD:
1412 if (remote_button_hold())
1413 return "r";
1414 else
1415 return NULL;
1416 #endif
1418 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1419 case WPS_TOKEN_VLED_HDD:
1420 if(led_read(HZ/2))
1421 return "h";
1422 else
1423 return NULL;
1424 #endif
1426 #ifdef HAVE_LCD_BITMAP
1427 case WPS_TOKEN_LEFTMARGIN:
1428 gwps->display->setmargins(token->value.i,
1429 gwps->display->getymargin());
1430 return NULL;
1431 #endif
1433 default:
1434 return NULL;
1438 /* Return the index to the end token for the conditional token at index.
1439 The conditional token can be either a start token or a separator
1440 (i.e. option) token.
1442 static int find_conditional_end(struct wps_data *data, int index)
1444 int ret = index;
1445 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1446 ret = data->tokens[ret].value.i;
1448 /* ret now is the index to the end token for the conditional. */
1449 return ret;
1452 /* Return the index of the appropriate case for the conditional
1453 that starts at cond_index.
1455 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1457 if (!gwps)
1458 return false;
1460 struct wps_data *data = gwps->data;
1462 int i, cond_end;
1463 int cond_index = *token_index;
1464 char result[128], *value;
1465 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1466 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1468 /* treat ?xx<true> constructs as if they had 2 options. */
1469 if (num_options < 2)
1470 num_options = 2;
1472 int intval = num_options;
1473 /* get_token_value needs to know the number of options in the enum */
1474 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1475 result, sizeof(result), &intval);
1477 /* intval is now the number of the enum option we want to read,
1478 starting from 1. If intval is -1, we check if value is empty. */
1479 if (intval == -1)
1480 intval = (value && *value) ? 1 : num_options;
1481 else if (intval > num_options || intval < 1)
1482 intval = num_options;
1484 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1486 /* skip to the right enum case */
1487 int next = cond_index + 2;
1488 for (i = 1; i < intval; i++)
1490 next = data->tokens[next].value.i;
1492 *token_index = next;
1494 if (prev_val == intval)
1496 /* Same conditional case as previously. Return without clearing the
1497 pictures */
1498 return false;
1501 cond_end = find_conditional_end(data, cond_index + 2);
1502 for (i = cond_index + 3; i < cond_end; i++)
1504 #ifdef HAVE_LCD_BITMAP
1505 /* clear all pictures in the conditional and nested ones */
1506 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1507 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1508 #endif
1509 #ifdef HAVE_ALBUMART
1510 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1511 draw_album_art(gwps, audio_current_aa_hid(), true);
1512 #endif
1515 return true;
1518 /* Read a (sub)line to the given alignment format buffer.
1519 linebuf is the buffer where the data is actually stored.
1520 align is the alignment format that'll be used to display the text.
1521 The return value indicates whether the line needs to be updated.
1523 static bool get_line(struct gui_wps *gwps,
1524 int line, int subline,
1525 struct align_pos *align,
1526 char *linebuf,
1527 int linebuf_size)
1529 struct wps_data *data = gwps->data;
1531 char temp_buf[128];
1532 char *buf = linebuf; /* will always point to the writing position */
1533 char *linebuf_end = linebuf + linebuf_size - 1;
1534 int i, last_token_idx;
1535 bool update = false;
1537 /* alignment-related variables */
1538 int cur_align;
1539 char* cur_align_start;
1540 cur_align_start = buf;
1541 cur_align = WPS_ALIGN_LEFT;
1542 align->left = NULL;
1543 align->center = NULL;
1544 align->right = NULL;
1546 #ifdef HAVE_LCD_BITMAP
1547 /* Reset margins - only bitmap targets modify them */
1548 gwps->display->setmargins(0, gwps->display->getymargin());
1549 #endif
1551 /* Process all tokens of the desired subline */
1552 last_token_idx = wps_last_token_index(data, line, subline);
1553 for (i = wps_first_token_index(data, line, subline);
1554 i <= last_token_idx; i++)
1556 switch(data->tokens[i].type)
1558 case WPS_TOKEN_CONDITIONAL:
1559 /* place ourselves in the right conditional case */
1560 update |= evaluate_conditional(gwps, &i);
1561 break;
1563 case WPS_TOKEN_CONDITIONAL_OPTION:
1564 /* we've finished in the curent conditional case,
1565 skip to the end of the conditional structure */
1566 i = find_conditional_end(data, i);
1567 break;
1569 #ifdef HAVE_LCD_BITMAP
1570 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1572 struct gui_img *img = data->img;
1573 int n = data->tokens[i].value.i & 0xFF;
1574 int subimage = data->tokens[i].value.i >> 8;
1576 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1577 img[n].display = subimage;
1578 break;
1580 #endif
1582 case WPS_TOKEN_ALIGN_LEFT:
1583 case WPS_TOKEN_ALIGN_CENTER:
1584 case WPS_TOKEN_ALIGN_RIGHT:
1585 /* remember where the current aligned text started */
1586 switch (cur_align)
1588 case WPS_ALIGN_LEFT:
1589 align->left = cur_align_start;
1590 break;
1592 case WPS_ALIGN_CENTER:
1593 align->center = cur_align_start;
1594 break;
1596 case WPS_ALIGN_RIGHT:
1597 align->right = cur_align_start;
1598 break;
1600 /* start a new alignment */
1601 switch (data->tokens[i].type)
1603 case WPS_TOKEN_ALIGN_LEFT:
1604 cur_align = WPS_ALIGN_LEFT;
1605 break;
1606 case WPS_TOKEN_ALIGN_CENTER:
1607 cur_align = WPS_ALIGN_CENTER;
1608 break;
1609 case WPS_TOKEN_ALIGN_RIGHT:
1610 cur_align = WPS_ALIGN_RIGHT;
1611 break;
1612 default:
1613 break;
1615 *buf++ = 0;
1616 cur_align_start = buf;
1617 break;
1619 default:
1621 /* get the value of the tag and copy it to the buffer */
1622 char *value = get_token_value(gwps, &data->tokens[i],
1623 temp_buf, sizeof(temp_buf), NULL);
1624 if (value)
1626 update = true;
1627 while (*value && (buf < linebuf_end))
1628 *buf++ = *value++;
1630 break;
1635 /* close the current alignment */
1636 switch (cur_align)
1638 case WPS_ALIGN_LEFT:
1639 align->left = cur_align_start;
1640 break;
1642 case WPS_ALIGN_CENTER:
1643 align->center = cur_align_start;
1644 break;
1646 case WPS_ALIGN_RIGHT:
1647 align->right = cur_align_start;
1648 break;
1651 return update;
1654 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1656 struct wps_data *data = gwps->data;
1657 int i;
1658 int subline_idx = wps_subline_index(data, line, subline);
1659 int last_token_idx = wps_last_token_index(data, line, subline);
1661 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1663 for (i = wps_first_token_index(data, line, subline);
1664 i <= last_token_idx; i++)
1666 switch(data->tokens[i].type)
1668 case WPS_TOKEN_CONDITIONAL:
1669 /* place ourselves in the right conditional case */
1670 evaluate_conditional(gwps, &i);
1671 break;
1673 case WPS_TOKEN_CONDITIONAL_OPTION:
1674 /* we've finished in the curent conditional case,
1675 skip to the end of the conditional structure */
1676 i = find_conditional_end(data, i);
1677 break;
1679 case WPS_TOKEN_SUBLINE_TIMEOUT:
1680 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1681 break;
1683 default:
1684 break;
1689 /* Calculates which subline should be displayed for the specified line
1690 Returns true iff the subline must be refreshed */
1691 static bool update_curr_subline(struct gui_wps *gwps, int line)
1693 struct wps_data *data = gwps->data;
1695 int search, search_start, num_sublines;
1696 bool reset_subline;
1697 bool new_subline_refresh;
1698 bool only_one_subline;
1700 num_sublines = data->lines[line].num_sublines;
1701 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1702 new_subline_refresh = false;
1703 only_one_subline = false;
1705 /* if time to advance to next sub-line */
1706 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1707 reset_subline)
1709 /* search all sublines until the next subline with time > 0
1710 is found or we get back to the subline we started with */
1711 if (reset_subline)
1712 search_start = 0;
1713 else
1714 search_start = data->lines[line].curr_subline;
1716 for (search = 0; search < num_sublines; search++)
1718 data->lines[line].curr_subline++;
1720 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1721 if (data->lines[line].curr_subline == num_sublines)
1723 if (data->lines[line].curr_subline == 1)
1724 only_one_subline = true;
1725 data->lines[line].curr_subline = 0;
1728 /* if back where we started after search or
1729 only one subline is defined on the line */
1730 if (((search > 0) &&
1731 (data->lines[line].curr_subline == search_start)) ||
1732 only_one_subline)
1734 /* no other subline with a time > 0 exists */
1735 data->lines[line].subline_expire_time = (reset_subline ?
1736 current_tick :
1737 data->lines[line].subline_expire_time) + 100 * HZ;
1738 break;
1740 else
1742 /* get initial time multiplier for this subline */
1743 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1745 int subline_idx = wps_subline_index(data, line,
1746 data->lines[line].curr_subline);
1748 /* only use this subline if subline time > 0 */
1749 if (data->sublines[subline_idx].time_mult > 0)
1751 new_subline_refresh = true;
1752 data->lines[line].subline_expire_time = (reset_subline ?
1753 current_tick : data->lines[line].subline_expire_time) +
1754 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1755 break;
1761 return new_subline_refresh;
1764 /* Display a line appropriately according to its alignment format.
1765 format_align contains the text, separated between left, center and right.
1766 line is the index of the line on the screen.
1767 scroll indicates whether the line is a scrolling one or not.
1769 static void write_line(struct screen *display,
1770 struct align_pos *format_align,
1771 int line,
1772 bool scroll)
1775 int left_width = 0, left_xpos;
1776 int center_width = 0, center_xpos;
1777 int right_width = 0, right_xpos;
1778 int ypos;
1779 int space_width;
1780 int string_height;
1781 int scroll_width;
1783 /* calculate different string sizes and positions */
1784 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1785 if (format_align->left != 0) {
1786 display->getstringsize((unsigned char *)format_align->left,
1787 &left_width, &string_height);
1790 if (format_align->right != 0) {
1791 display->getstringsize((unsigned char *)format_align->right,
1792 &right_width, &string_height);
1795 if (format_align->center != 0) {
1796 display->getstringsize((unsigned char *)format_align->center,
1797 &center_width, &string_height);
1800 left_xpos = display->getxmargin();
1801 right_xpos = (display->getwidth() - right_width);
1802 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1804 scroll_width = display->getwidth() - left_xpos;
1806 /* Checks for overlapping strings.
1807 If needed the overlapping strings will be merged, separated by a
1808 space */
1810 /* CASE 1: left and centered string overlap */
1811 /* there is a left string, need to merge left and center */
1812 if ((left_width != 0 && center_width != 0) &&
1813 (left_xpos + left_width + space_width > center_xpos)) {
1814 /* replace the former separator '\0' of left and
1815 center string with a space */
1816 *(--format_align->center) = ' ';
1817 /* calculate the new width and position of the merged string */
1818 left_width = left_width + space_width + center_width;
1819 /* there is no centered string anymore */
1820 center_width = 0;
1822 /* there is no left string, move center to left */
1823 if ((left_width == 0 && center_width != 0) &&
1824 (left_xpos + left_width > center_xpos)) {
1825 /* move the center string to the left string */
1826 format_align->left = format_align->center;
1827 /* calculate the new width and position of the string */
1828 left_width = center_width;
1829 /* there is no centered string anymore */
1830 center_width = 0;
1833 /* CASE 2: centered and right string overlap */
1834 /* there is a right string, need to merge center and right */
1835 if ((center_width != 0 && right_width != 0) &&
1836 (center_xpos + center_width + space_width > right_xpos)) {
1837 /* replace the former separator '\0' of center and
1838 right string with a space */
1839 *(--format_align->right) = ' ';
1840 /* move the center string to the right after merge */
1841 format_align->right = format_align->center;
1842 /* calculate the new width and position of the merged string */
1843 right_width = center_width + space_width + right_width;
1844 right_xpos = (display->getwidth() - right_width);
1845 /* there is no centered string anymore */
1846 center_width = 0;
1848 /* there is no right string, move center to right */
1849 if ((center_width != 0 && right_width == 0) &&
1850 (center_xpos + center_width > right_xpos)) {
1851 /* move the center string to the right string */
1852 format_align->right = format_align->center;
1853 /* calculate the new width and position of the string */
1854 right_width = center_width;
1855 right_xpos = (display->getwidth() - right_width);
1856 /* there is no centered string anymore */
1857 center_width = 0;
1860 /* CASE 3: left and right overlap
1861 There is no center string anymore, either there never
1862 was one or it has been merged in case 1 or 2 */
1863 /* there is a left string, need to merge left and right */
1864 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1865 (left_xpos + left_width + space_width > right_xpos)) {
1866 /* replace the former separator '\0' of left and
1867 right string with a space */
1868 *(--format_align->right) = ' ';
1869 /* calculate the new width and position of the string */
1870 left_width = left_width + space_width + right_width;
1871 /* there is no right string anymore */
1872 right_width = 0;
1874 /* there is no left string, move right to left */
1875 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1876 (left_width > right_xpos)) {
1877 /* move the right string to the left string */
1878 format_align->left = format_align->right;
1879 /* calculate the new width and position of the string */
1880 left_width = right_width;
1881 /* there is no right string anymore */
1882 right_width = 0;
1885 ypos = (line * string_height) + display->getymargin();
1888 if (scroll && ((left_width > scroll_width) ||
1889 (center_width > scroll_width) ||
1890 (right_width > scroll_width)))
1892 display->puts_scroll(0, line,
1893 (unsigned char *)format_align->left);
1895 else
1897 #ifdef HAVE_LCD_BITMAP
1898 /* clear the line first */
1899 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1900 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1901 display->set_drawmode(DRMODE_SOLID);
1902 #endif
1904 /* Nasty hack: we output an empty scrolling string,
1905 which will reset the scroller for that line */
1906 display->puts_scroll(0, line, (unsigned char *)"");
1908 /* print aligned strings */
1909 if (left_width != 0)
1911 display->putsxy(left_xpos, ypos,
1912 (unsigned char *)format_align->left);
1914 if (center_width != 0)
1916 display->putsxy(center_xpos, ypos,
1917 (unsigned char *)format_align->center);
1919 if (right_width != 0)
1921 display->putsxy(right_xpos, ypos,
1922 (unsigned char *)format_align->right);
1927 /* Refresh the WPS according to refresh_mode. */
1928 bool gui_wps_refresh(struct gui_wps *gwps,
1929 int ffwd_offset,
1930 unsigned char refresh_mode)
1932 struct wps_data *data = gwps->data;
1933 struct screen *display = gwps->display;
1934 struct wps_state *state = gwps->state;
1936 if(!gwps || !data || !state || !display)
1937 return false;
1939 int v, line, i, subline_idx;
1940 unsigned char flags;
1941 char linebuf[MAX_PATH];
1943 struct align_pos align;
1944 align.left = NULL;
1945 align.center = NULL;
1946 align.right = NULL;
1948 bool update_line, new_subline_refresh;
1950 #ifdef HAVE_LCD_BITMAP
1951 gui_wps_statusbar_draw(gwps, true);
1953 /* to find out wether the peak meter is enabled we
1954 assume it wasn't until we find a line that contains
1955 the peak meter. We can't use peak_meter_enabled itself
1956 because that would mean to turn off the meter thread
1957 temporarily. (That shouldn't matter unless yield
1958 or sleep is called but who knows...)
1960 bool enable_pm = false;
1962 #endif
1964 /* reset to first subline if refresh all flag is set */
1965 if (refresh_mode == WPS_REFRESH_ALL)
1967 display->clear_display();
1969 for (i = 0; i <= data->num_lines; i++)
1971 data->lines[i].curr_subline = SUBLINE_RESET;
1975 #ifdef HAVE_LCD_CHARCELLS
1976 for (i = 0; i < 8; i++)
1978 if (data->wps_progress_pat[i] == 0)
1979 data->wps_progress_pat[i] = display->get_locked_pattern();
1981 #endif
1983 if (!state->id3)
1985 display->stop_scroll();
1986 return false;
1989 state->ff_rewind_count = ffwd_offset;
1991 for (v = 0; v < data->num_viewports; v++)
1993 display->set_viewport(&data->viewports[v].vp);
1995 if (refresh_mode == WPS_REFRESH_ALL)
1997 display->clear_viewport();
2000 #ifdef HAVE_LCD_BITMAP
2001 /* Set images to not to be displayed */
2002 for (i = 0; i < MAX_IMAGES; i++)
2004 data->img[i].display = -1;
2006 #endif
2008 for (line = data->viewports[v].first_line;
2009 line <= data->viewports[v].last_line; line++)
2011 memset(linebuf, 0, sizeof(linebuf));
2012 update_line = false;
2014 /* get current subline for the line */
2015 new_subline_refresh = update_curr_subline(gwps, line);
2017 subline_idx = wps_subline_index(data, line,
2018 data->lines[line].curr_subline);
2019 flags = data->sublines[subline_idx].line_type;
2021 if (refresh_mode == WPS_REFRESH_ALL || (flags & refresh_mode)
2022 || new_subline_refresh)
2024 /* get_line tells us if we need to update the line */
2025 update_line = get_line(gwps, line, data->lines[line].curr_subline,
2026 &align, linebuf, sizeof(linebuf));
2029 #ifdef HAVE_LCD_BITMAP
2030 /* progressbar */
2031 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2033 /* the progressbar should be alone on its line */
2034 update_line = false;
2035 draw_progressbar(gwps, line - data->viewports[v].first_line);
2038 /* peakmeter */
2039 if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
2041 /* the peakmeter should be alone on its line */
2042 update_line = false;
2044 int h = font_get(display->getfont())->height;
2045 int peak_meter_y = display->getymargin() + (line - data->viewports[v].first_line)* h;
2047 /* The user might decide to have the peak meter in the last
2048 line so that it is only displayed if no status bar is
2049 visible. If so we neither want do draw nor enable the
2050 peak meter. */
2051 if (peak_meter_y + h <= display->getheight()) {
2052 /* found a line with a peak meter -> remember that we must
2053 enable it later */
2054 enable_pm = true;
2055 peak_meter_screen(gwps->display, 0, peak_meter_y,
2056 MIN(h, display->getheight() - peak_meter_y));
2060 #else /* HAVE_LCD_CHARCELL */
2062 /* progressbar */
2063 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2065 if (data->full_line_progressbar)
2066 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2067 else
2068 draw_player_progress(gwps);
2070 #endif
2072 if (update_line)
2074 if (flags & WPS_REFRESH_SCROLL)
2076 /* if the line is a scrolling one we don't want to update
2077 too often, so that it has the time to scroll */
2078 if ((refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2079 write_line(display, &align, line - data->viewports[v].first_line, true);
2081 else
2082 write_line(display, &align, line - data->viewports[v].first_line, false);
2087 #ifdef HAVE_LCD_BITMAP
2088 /* Now display any images in this viewport */
2089 wps_display_images(gwps, &data->viewports[v].vp);
2090 #endif
2093 #ifdef HAVE_LCD_BITMAP
2094 data->peak_meter_enabled = enable_pm;
2095 #endif
2097 /* Restore the default viewport */
2098 display->set_viewport(NULL);
2100 display->update();
2102 #ifdef HAVE_BACKLIGHT
2103 if (global_settings.caption_backlight && state->id3)
2105 /* turn on backlight n seconds before track ends, and turn it off n
2106 seconds into the new track. n == backlight_timeout, or 5s */
2107 int n = global_settings.backlight_timeout * 1000;
2109 if ( n < 1000 )
2110 n = 5000; /* use 5s if backlight is always on or off */
2112 if (((state->id3->elapsed < 1000) ||
2113 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2114 (state->paused == false))
2115 backlight_on();
2117 #endif
2118 #ifdef HAVE_REMOTE_LCD
2119 if (global_settings.remote_caption_backlight && state->id3)
2121 /* turn on remote backlight n seconds before track ends, and turn it
2122 off n seconds into the new track. n == remote_backlight_timeout,
2123 or 5s */
2124 int n = global_settings.remote_backlight_timeout * 1000;
2126 if ( n < 1000 )
2127 n = 5000; /* use 5s if backlight is always on or off */
2129 if (((state->id3->elapsed < 1000) ||
2130 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2131 (state->paused == false))
2132 remote_backlight_on();
2134 #endif
2136 return true;