Hopefully fix FS#8506 (OF cant be loaded on some PP targets). also hopefully fixes...
[Rockbox.git] / apps / gui / gwps-common.c
blobf2b444f7da0c489f0a737e14d176c801d80ecd17
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"
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 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
83 /* fades the volume */
84 void fade(bool fade_in)
86 int fp_global_vol = global_settings.volume << 8;
87 int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
88 int fp_step = (fp_global_vol - fp_min_vol) / 30;
90 if (fade_in) {
91 /* fade in */
92 int fp_volume = fp_min_vol;
94 /* zero out the sound */
95 sound_set_volume(fp_min_vol >> 8);
97 sleep(HZ/10); /* let audio thread run */
98 audio_resume();
100 while (fp_volume < fp_global_vol - fp_step) {
101 fp_volume += fp_step;
102 sound_set_volume(fp_volume >> 8);
103 sleep(1);
105 sound_set_volume(global_settings.volume);
107 else {
108 /* fade out */
109 int fp_volume = fp_global_vol;
111 while (fp_volume > fp_min_vol + fp_step) {
112 fp_volume -= fp_step;
113 sound_set_volume(fp_volume >> 8);
114 sleep(1);
116 audio_pause();
118 #if CONFIG_CODEC != SWCODEC
119 #ifndef SIMULATOR
120 /* let audio thread run and wait for the mas to run out of data */
121 while (!mp3_pause_done())
122 #endif
123 sleep(HZ/10);
124 #endif
126 /* reset volume to what it was before the fade */
127 sound_set_volume(global_settings.volume);
131 /* return true if screen restore is needed
132 return false otherwise
134 bool update_onvol_change(struct gui_wps * gwps)
136 gui_wps_statusbar_draw(gwps, false);
137 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
139 #ifdef HAVE_LCD_CHARCELLS
140 gui_splash(gwps->display, 0, "Vol: %3d dB",
141 sound_val2phys(SOUND_VOLUME, global_settings.volume));
142 return true;
143 #endif
144 return false;
147 bool ffwd_rew(int button)
149 unsigned int step = 0; /* current ff/rewind step */
150 unsigned int max_step = 0; /* maximum ff/rewind step */
151 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
152 int direction = -1; /* forward=1 or backward=-1 */
153 long accel_tick = 0; /* next time at which to bump the step size */
154 bool exit = false;
155 bool usb = false;
156 int i = 0;
158 if (button == ACTION_NONE)
160 status_set_ffmode(0);
161 return usb;
163 while (!exit)
165 switch ( button )
167 case ACTION_WPS_SEEKFWD:
168 direction = 1;
169 case ACTION_WPS_SEEKBACK:
170 if (wps_state.ff_rewind)
172 if (direction == 1)
174 /* fast forwarding, calc max step relative to end */
175 max_step = (wps_state.id3->length -
176 (wps_state.id3->elapsed +
177 ff_rewind_count)) *
178 FF_REWIND_MAX_PERCENT / 100;
180 else
182 /* rewinding, calc max step relative to start */
183 max_step = (wps_state.id3->elapsed + ff_rewind_count) *
184 FF_REWIND_MAX_PERCENT / 100;
187 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
189 if (step > max_step)
190 step = max_step;
192 ff_rewind_count += step * direction;
194 if (global_settings.ff_rewind_accel != 0 &&
195 current_tick >= accel_tick)
197 step *= 2;
198 accel_tick = current_tick +
199 global_settings.ff_rewind_accel*HZ;
202 else
204 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
205 wps_state.id3 && wps_state.id3->length )
207 if (!wps_state.paused)
208 #if (CONFIG_CODEC == SWCODEC)
209 audio_pre_ff_rewind();
210 #else
211 audio_pause();
212 #endif
213 #if CONFIG_KEYPAD == PLAYER_PAD
214 FOR_NB_SCREENS(i)
215 gui_wps[i].display->stop_scroll();
216 #endif
217 if (direction > 0)
218 status_set_ffmode(STATUS_FASTFORWARD);
219 else
220 status_set_ffmode(STATUS_FASTBACKWARD);
222 wps_state.ff_rewind = true;
224 step = 1000 * global_settings.ff_rewind_min_step;
226 accel_tick = current_tick +
227 global_settings.ff_rewind_accel*HZ;
229 else
230 break;
233 if (direction > 0) {
234 if ((wps_state.id3->elapsed + ff_rewind_count) >
235 wps_state.id3->length)
236 ff_rewind_count = wps_state.id3->length -
237 wps_state.id3->elapsed;
239 else {
240 if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0)
241 ff_rewind_count = -wps_state.id3->elapsed;
244 FOR_NB_SCREENS(i)
245 gui_wps_refresh(&gui_wps[i],
246 (wps_state.wps_time_countup == false)?
247 ff_rewind_count:-ff_rewind_count,
248 WPS_REFRESH_PLAYER_PROGRESS |
249 WPS_REFRESH_DYNAMIC);
251 break;
253 case ACTION_WPS_STOPSEEK:
254 wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count;
255 audio_ff_rewind(wps_state.id3->elapsed);
256 ff_rewind_count = 0;
257 wps_state.ff_rewind = false;
258 status_set_ffmode(0);
259 #if (CONFIG_CODEC != SWCODEC)
260 if (!wps_state.paused)
261 audio_resume();
262 #endif
263 #ifdef HAVE_LCD_CHARCELLS
264 gui_wps_display();
265 #endif
266 exit = true;
267 break;
269 default:
270 if(default_event_handler(button) == SYS_USB_CONNECTED) {
271 status_set_ffmode(0);
272 usb = true;
273 exit = true;
275 break;
277 if (!exit)
278 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_BLOCK);
280 return usb;
283 bool gui_wps_display(void)
285 int i;
286 if (!wps_state.id3 && !(audio_status() & AUDIO_STATUS_PLAY))
288 global_status.resume_index = -1;
289 #ifdef HAVE_LCD_BITMAP
290 gui_syncstatusbar_draw(&statusbars, true);
291 #endif
292 gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST));
293 return true;
295 else
297 FOR_NB_SCREENS(i)
299 /* Update the values in the first (default) viewport - in case the user
300 has modified the statusbar or colour settings */
301 #ifdef HAVE_LCD_BITMAP
302 gui_wps[i].data->viewports[0].vp.ymargin = gui_wps[i].display->getymargin();
303 #if LCD_DEPTH > 1
304 if (gui_wps[i].display->depth > 1)
306 gui_wps[i].data->viewports[0].vp.fg_pattern = gui_wps[i].display->get_foreground();
307 gui_wps[i].data->viewports[0].vp.bg_pattern = gui_wps[i].display->get_background();
309 #endif
310 #endif
312 gui_wps[i].display->clear_display();
313 if (!gui_wps[i].data->wps_loaded) {
314 if ( !gui_wps[i].data->num_tokens ) {
315 /* set the default wps for the main-screen */
316 if(i == 0)
318 #ifdef HAVE_LCD_BITMAP
319 #if LCD_DEPTH > 1
320 unload_wps_backdrop();
321 #endif
322 wps_data_load(gui_wps[i].data,
323 gui_wps[i].display,
324 "%s%?it<%?in<%in. |>%it|%fn>\n"
325 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
326 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
327 "\n"
328 "%al%pc/%pt%ar[%pp:%pe]\n"
329 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
330 "%pb\n"
331 "%pm\n", false);
332 #else
333 wps_data_load(gui_wps[i].data,
334 gui_wps[i].display,
335 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
336 "%pc%?ps<*|/>%pt\n", false);
337 #endif
339 #if NB_SCREENS == 2
340 /* set the default wps for the remote-screen */
341 else if(i == 1)
343 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
344 unload_remote_wps_backdrop();
345 #endif
346 wps_data_load(gui_wps[i].data,
347 gui_wps[i].display,
348 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
349 "%s%?it<%?in<%in. |>%it|%fn>\n"
350 "%al%pc/%pt%ar[%pp:%pe]\n"
351 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
352 "%pb\n", false);
354 #endif
359 yield();
360 FOR_NB_SCREENS(i)
362 gui_wps_refresh(&gui_wps[i], 0, WPS_REFRESH_ALL);
364 return false;
367 bool update(struct gui_wps *gwps)
369 bool track_changed = audio_has_changed_track();
370 bool retcode = false;
372 gwps->state->nid3 = audio_next_track();
373 if (track_changed)
375 gwps->display->stop_scroll();
376 gwps->state->id3 = audio_current_track();
378 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
379 && strcmp(gwps->state->id3->path, curr_cue->audio_filename))
381 /* the current cuesheet isn't the right one any more */
383 if (!strcmp(gwps->state->id3->path, temp_cue->audio_filename)) {
384 /* We have the new cuesheet in memory (temp_cue),
385 let's make it the current one ! */
386 memcpy(curr_cue, temp_cue, sizeof(struct cuesheet));
388 else {
389 /* We need to parse the new cuesheet */
391 char cuepath[MAX_PATH];
393 if (look_for_cuesheet_file(gwps->state->id3->path, cuepath) &&
394 parse_cuesheet(cuepath, curr_cue))
396 gwps->state->id3->cuesheet_type = 1;
397 strcpy(curr_cue->audio_filename, gwps->state->id3->path);
401 cue_spoof_id3(curr_cue, gwps->state->id3);
404 if (gui_wps_display())
405 retcode = true;
406 else{
407 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
410 if (gwps->state->id3)
412 strncpy(gwps->state->current_track_path, gwps->state->id3->path,
413 sizeof(gwps->state->current_track_path));
414 gwps->state->current_track_path[sizeof(gwps->state->current_track_path)-1] = '\0';
418 if (gwps->state->id3)
420 if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type
421 && (gwps->state->id3->elapsed < curr_cue->curr_track->offset
422 || (curr_cue->curr_track_idx < curr_cue->track_count - 1
423 && gwps->state->id3->elapsed >= (curr_cue->curr_track+1)->offset)))
425 /* We've changed tracks within the cuesheet :
426 we need to update the ID3 info and refresh the WPS */
428 cue_find_current_track(curr_cue, gwps->state->id3->elapsed);
429 cue_spoof_id3(curr_cue, gwps->state->id3);
431 gwps->display->stop_scroll();
432 if (gui_wps_display())
433 retcode = true;
434 else
435 gui_wps_refresh(gwps, 0, WPS_REFRESH_ALL);
437 else
438 gui_wps_refresh(gwps, 0, WPS_REFRESH_NON_STATIC);
441 gui_wps_statusbar_draw(gwps, false);
443 return retcode;
447 void display_keylock_text(bool locked)
449 char* s;
450 int i;
451 FOR_NB_SCREENS(i)
452 gui_wps[i].display->stop_scroll();
454 if(locked)
455 s = str(LANG_KEYLOCK_ON);
456 else
457 s = str(LANG_KEYLOCK_OFF);
458 gui_syncsplash(HZ, s);
461 #ifdef HAVE_LCD_BITMAP
463 static void draw_progressbar(struct gui_wps *gwps, int line)
465 struct wps_data *data = gwps->data;
466 struct screen *display = gwps->display;
467 struct wps_state *state = gwps->state;
468 int h = font_get(display->getfont())->height;
470 int sb_y;
471 if (data->progress_top < 0)
472 sb_y = line*h + display->getymargin() +
473 ((h > data->progress_height + 1)
474 ? (h - data->progress_height) / 2 : 1);
475 else
476 sb_y = data->progress_top;
478 if (!data->progress_end)
479 data->progress_end=display->getwidth();
481 if (gwps->data->progressbar.have_bitmap_pb)
482 gui_bitmap_scrollbar_draw(display, data->progressbar.bm,
483 data->progress_start, sb_y,
484 data->progress_end-data->progress_start,
485 data->progressbar.bm.height,
486 state->id3->length ? state->id3->length : 1, 0,
487 state->id3->length ? state->id3->elapsed
488 + state->ff_rewind_count : 0,
489 HORIZONTAL);
490 else
491 gui_scrollbar_draw(display, data->progress_start, sb_y,
492 data->progress_end-data->progress_start,
493 data->progress_height,
494 state->id3->length ? state->id3->length : 1, 0,
495 state->id3->length ? state->id3->elapsed
496 + state->ff_rewind_count : 0,
497 HORIZONTAL);
499 #ifdef AB_REPEAT_ENABLE
500 if ( ab_repeat_mode_enabled() && state->id3->length != 0 )
501 ab_draw_markers(display, state->id3->length,
502 data->progress_start, data->progress_end, sb_y,
503 data->progress_height);
504 #endif
506 if ( cuesheet_is_enabled() && state->id3->cuesheet_type )
507 cue_draw_markers(display, state->id3->length,
508 data->progress_start, data->progress_end,
509 sb_y+1, data->progress_height-2);
512 /* clears the area where the image was shown */
513 static void clear_image_pos(struct gui_wps *gwps, int n)
515 if(!gwps)
516 return;
517 struct wps_data *data = gwps->data;
518 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
519 gwps->display->fillrect(data->img[n].x, data->img[n].y,
520 data->img[n].bm.width, data->img[n].subimage_height);
521 gwps->display->set_drawmode(DRMODE_SOLID);
524 static void wps_draw_image(struct gui_wps *gwps, int n, int subimage)
526 struct screen *display = gwps->display;
527 struct wps_data *data = gwps->data;
528 if(data->img[n].always_display)
529 display->set_drawmode(DRMODE_FG);
530 else
531 display->set_drawmode(DRMODE_SOLID);
533 #if LCD_DEPTH > 1
534 if(data->img[n].bm.format == FORMAT_MONO) {
535 #endif
536 display->mono_bitmap_part(data->img[n].bm.data,
537 0, data->img[n].subimage_height * subimage,
538 data->img[n].bm.width, data->img[n].x,
539 data->img[n].y, data->img[n].bm.width,
540 data->img[n].subimage_height);
541 #if LCD_DEPTH > 1
542 } else {
543 display->transparent_bitmap_part((fb_data *)data->img[n].bm.data,
544 0, data->img[n].subimage_height * subimage,
545 data->img[n].bm.width, data->img[n].x,
546 data->img[n].y, data->img[n].bm.width,
547 data->img[n].subimage_height);
549 #endif
552 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
554 if(!gwps || !gwps->data || !gwps->display)
555 return;
557 int n;
558 struct wps_data *data = gwps->data;
559 struct screen *display = gwps->display;
561 for (n = 0; n < MAX_IMAGES; n++)
563 if (data->img[n].loaded)
565 if (data->img[n].display >= 0)
567 wps_draw_image(gwps, n, data->img[n].display);
568 } else if (data->img[n].always_display && data->img[n].vp == vp)
570 wps_draw_image(gwps, n, 0);
574 display->set_drawmode(DRMODE_SOLID);
577 #else /* HAVE_LCD_CHARCELL */
579 static bool draw_player_progress(struct gui_wps *gwps)
581 struct wps_state *state = gwps->state;
582 struct screen *display = gwps->display;
583 unsigned char progress_pattern[7];
584 int pos = 0;
585 int i;
587 if (!state->id3)
588 return false;
590 if (state->id3->length)
591 pos = 36 * (state->id3->elapsed + state->ff_rewind_count)
592 / state->id3->length;
594 for (i = 0; i < 7; i++, pos -= 5)
596 if (pos <= 0)
597 progress_pattern[i] = 0x1f;
598 else if (pos >= 5)
599 progress_pattern[i] = 0x00;
600 else
601 progress_pattern[i] = 0x1f >> pos;
604 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
605 return true;
608 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
610 static const unsigned char numbers[10][4] = {
611 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
612 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
613 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
614 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
615 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
616 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
617 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
618 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
619 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
620 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
623 struct wps_state *state = gwps->state;
624 struct screen *display = gwps->display;
625 struct wps_data *data = gwps->data;
626 unsigned char progress_pattern[7];
627 char timestr[10];
628 int time;
629 int time_idx = 0;
630 int pos = 0;
631 int pat_idx = 1;
632 int digit, i, j;
633 bool softchar;
635 if (!state->id3 || buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
636 return;
638 time = state->id3->elapsed + state->ff_rewind_count;
639 if (state->id3->length)
640 pos = 55 * time / state->id3->length;
642 memset(timestr, 0, sizeof(timestr));
643 format_time(timestr, sizeof(timestr)-2, time);
644 timestr[strlen(timestr)] = ':'; /* always safe */
646 for (i = 0; i < 11; i++, pos -= 5)
648 softchar = false;
649 memset(progress_pattern, 0, sizeof(progress_pattern));
651 if ((digit = timestr[time_idx]))
653 softchar = true;
654 digit -= '0';
656 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
658 memcpy(progress_pattern, numbers[digit], 4);
659 time_idx += 2;
661 else /* tens, shifted right */
663 for (j = 0; j < 4; j++)
664 progress_pattern[j] = numbers[digit][j] >> 1;
666 if (time_idx > 0) /* not the first group, add colon in front */
668 progress_pattern[1] |= 0x10;
669 progress_pattern[3] |= 0x10;
671 time_idx++;
674 if (pos >= 5)
675 progress_pattern[5] = progress_pattern[6] = 0x1f;
678 if (pos > 0 && pos < 5)
680 softchar = true;
681 progress_pattern[5] = progress_pattern[6] = (~0x1f >> pos) & 0x1f;
684 if (softchar && pat_idx < 8)
686 display->define_pattern(data->wps_progress_pat[pat_idx],
687 progress_pattern);
688 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
689 pat_idx++;
691 else if (pos <= 0)
692 buf = utf8encode(' ', buf);
693 else
694 buf = utf8encode(0xe115, buf); /* 2/7 _ */
696 *buf = '\0';
699 #endif /* HAVE_LCD_CHARCELL */
701 /* Extract a part from a path.
703 * buf - buffer extract part to.
704 * buf_size - size of buffer.
705 * path - path to extract from.
706 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
707 * parent of parent, etc.
709 * Returns buf if the desired level was found, NULL otherwise.
711 static char* get_dir(char* buf, int buf_size, const char* path, int level)
713 const char* sep;
714 const char* last_sep;
715 int len;
717 sep = path + strlen(path);
718 last_sep = sep;
720 while (sep > path)
722 if ('/' == *(--sep))
724 if (!level)
725 break;
727 level--;
728 last_sep = sep - 1;
732 if (level || (last_sep <= sep))
733 return NULL;
735 len = MIN(last_sep - sep, buf_size - 1);
736 strncpy(buf, sep + 1, len);
737 buf[len] = 0;
738 return buf;
741 /* Return the tag found at index i and write its value in buf.
742 The return value is buf if the tag had a value, or NULL if not.
744 intval is used with conditionals/enums: when this function is called,
745 intval should contain the number of options in the conditional/enum.
746 When this function returns, intval is -1 if the tag is non numeric or,
747 if the tag is numeric, intval is the enum case we want to go to.
748 When not treating a conditional/enum, intval should be NULL.
750 static char *get_token_value(struct gui_wps *gwps,
751 struct wps_token *token,
752 char *buf, int buf_size,
753 int *intval)
755 if (!gwps)
756 return NULL;
758 struct wps_data *data = gwps->data;
759 struct wps_state *state = gwps->state;
761 if (!data || !state)
762 return NULL;
764 struct mp3entry *id3;
766 if (token->next)
767 id3 = state->nid3;
768 else
769 id3 = state->id3;
771 if (!id3)
772 return NULL;
774 #if CONFIG_RTC
775 struct tm* tm = NULL;
777 /* if the token is an RTC one, update the time
778 and do the necessary checks */
779 if (token->type >= WPS_TOKENS_RTC_BEGIN
780 && token->type <= WPS_TOKENS_RTC_END)
782 tm = get_time();
784 if (!valid_time(tm))
785 return NULL;
787 #endif
789 int limit = 1;
790 if (intval)
792 limit = *intval;
793 *intval = -1;
796 switch (token->type)
798 case WPS_TOKEN_CHARACTER:
799 return &(token->value.c);
801 case WPS_TOKEN_STRING:
802 return data->strings[token->value.i];
804 case WPS_TOKEN_TRACK_TIME_ELAPSED:
805 format_time(buf, buf_size,
806 id3->elapsed + state->ff_rewind_count);
807 return buf;
809 case WPS_TOKEN_TRACK_TIME_REMAINING:
810 format_time(buf, buf_size,
811 id3->length - id3->elapsed -
812 state->ff_rewind_count);
813 return buf;
815 case WPS_TOKEN_TRACK_LENGTH:
816 format_time(buf, buf_size, id3->length);
817 return buf;
819 case WPS_TOKEN_PLAYLIST_ENTRIES:
820 snprintf(buf, buf_size, "%d", playlist_amount());
821 return buf;
823 case WPS_TOKEN_PLAYLIST_NAME:
824 return playlist_name(NULL, buf, buf_size);
826 case WPS_TOKEN_PLAYLIST_POSITION:
827 snprintf(buf, buf_size, "%d",
828 playlist_get_display_index());
829 return buf;
831 case WPS_TOKEN_PLAYLIST_SHUFFLE:
832 if ( global_settings.playlist_shuffle )
833 return "s";
834 else
835 return NULL;
836 break;
838 case WPS_TOKEN_VOLUME:
839 snprintf(buf, buf_size, "%d", global_settings.volume);
840 if (intval)
842 if (global_settings.volume == sound_min(SOUND_VOLUME))
844 *intval = 1;
846 else if (global_settings.volume == 0)
848 *intval = limit - 1;
850 else if (global_settings.volume > 0)
852 *intval = limit;
854 else
856 *intval = (limit - 3) * (global_settings.volume
857 - sound_min(SOUND_VOLUME) - 1)
858 / (-1 - sound_min(SOUND_VOLUME)) + 2;
861 return buf;
863 case WPS_TOKEN_TRACK_ELAPSED_PERCENT:
864 if (id3->length <= 0)
865 return NULL;
867 if (intval)
869 *intval = limit * (id3->elapsed + state->ff_rewind_count)
870 / id3->length + 1;
872 snprintf(buf, buf_size, "%d",
873 100*(id3->elapsed + state->ff_rewind_count) / id3->length);
874 return buf;
876 case WPS_TOKEN_METADATA_ARTIST:
877 return id3->artist;
879 case WPS_TOKEN_METADATA_COMPOSER:
880 return id3->composer;
882 case WPS_TOKEN_METADATA_ALBUM:
883 return id3->album;
885 case WPS_TOKEN_METADATA_ALBUM_ARTIST:
886 return id3->albumartist;
888 case WPS_TOKEN_METADATA_GROUPING:
889 return id3->grouping;
891 case WPS_TOKEN_METADATA_GENRE:
892 return id3->genre_string;
894 case WPS_TOKEN_METADATA_DISC_NUMBER:
895 if (id3->disc_string)
896 return id3->disc_string;
897 if (id3->discnum) {
898 snprintf(buf, buf_size, "%d", id3->discnum);
899 return buf;
901 return NULL;
903 case WPS_TOKEN_METADATA_TRACK_NUMBER:
904 if (id3->track_string)
905 return id3->track_string;
907 if (id3->tracknum) {
908 snprintf(buf, buf_size, "%d", id3->tracknum);
909 return buf;
911 return NULL;
913 case WPS_TOKEN_METADATA_TRACK_TITLE:
914 return id3->title;
916 case WPS_TOKEN_METADATA_VERSION:
917 switch (id3->id3version)
919 case ID3_VER_1_0:
920 return "1";
922 case ID3_VER_1_1:
923 return "1.1";
925 case ID3_VER_2_2:
926 return "2.2";
928 case ID3_VER_2_3:
929 return "2.3";
931 case ID3_VER_2_4:
932 return "2.4";
934 default:
935 return NULL;
938 case WPS_TOKEN_METADATA_YEAR:
939 if( id3->year_string )
940 return id3->year_string;
942 if (id3->year) {
943 snprintf(buf, buf_size, "%d", id3->year);
944 return buf;
946 return NULL;
948 case WPS_TOKEN_METADATA_COMMENT:
949 return id3->comment;
951 #ifdef HAVE_ALBUMART
952 case WPS_TOKEN_ALBUMART_DISPLAY:
953 draw_album_art(gwps, audio_current_aa_hid(), false);
954 return NULL;
956 case WPS_TOKEN_ALBUMART_FOUND:
957 if (audio_current_aa_hid() >= 0) {
958 snprintf(buf, buf_size, "C");
959 return buf;
961 return NULL;
962 #endif
964 case WPS_TOKEN_FILE_BITRATE:
965 if(id3->bitrate)
966 snprintf(buf, buf_size, "%d", id3->bitrate);
967 else
968 snprintf(buf, buf_size, "?");
969 return buf;
971 case WPS_TOKEN_FILE_CODEC:
972 if (intval)
974 if(id3->codectype == AFMT_UNKNOWN)
975 *intval = AFMT_NUM_CODECS;
976 else
977 *intval = id3->codectype;
979 return id3_get_codec(id3);
981 case WPS_TOKEN_FILE_FREQUENCY:
982 snprintf(buf, buf_size, "%ld", id3->frequency);
983 return buf;
985 case WPS_TOKEN_FILE_FREQUENCY_KHZ:
986 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
987 if ((id3->frequency % 1000) < 100)
988 snprintf(buf, buf_size, "%ld", id3->frequency / 1000);
989 else
990 snprintf(buf, buf_size, "%ld.%d",
991 id3->frequency / 1000,
992 (id3->frequency % 1000) / 100);
993 return buf;
995 case WPS_TOKEN_FILE_NAME:
996 if (get_dir(buf, buf_size, id3->path, 0)) {
997 /* Remove extension */
998 char* sep = strrchr(buf, '.');
999 if (NULL != sep) {
1000 *sep = 0;
1002 return buf;
1004 else {
1005 return NULL;
1008 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION:
1009 return get_dir(buf, buf_size, id3->path, 0);
1011 case WPS_TOKEN_FILE_PATH:
1012 return id3->path;
1014 case WPS_TOKEN_FILE_SIZE:
1015 snprintf(buf, buf_size, "%ld", id3->filesize / 1024);
1016 return buf;
1018 case WPS_TOKEN_FILE_VBR:
1019 return id3->vbr ? "(avg)" : NULL;
1021 case WPS_TOKEN_FILE_DIRECTORY:
1022 return get_dir(buf, buf_size, id3->path, token->value.i);
1024 case WPS_TOKEN_BATTERY_PERCENT:
1026 int l = battery_level();
1028 if (intval)
1030 limit = MAX(limit, 2);
1031 if (l > -1) {
1032 /* First enum is used for "unknown level". */
1033 *intval = (limit - 1) * l / 100 + 2;
1034 } else {
1035 *intval = 1;
1039 if (l > -1) {
1040 snprintf(buf, buf_size, "%d", l);
1041 return buf;
1042 } else {
1043 return "?";
1047 case WPS_TOKEN_BATTERY_VOLTS:
1049 unsigned int v = battery_voltage();
1050 snprintf(buf, buf_size, "%d.%02d", v / 1000, (v % 1000) / 10);
1051 return buf;
1054 case WPS_TOKEN_BATTERY_TIME:
1056 int t = battery_time();
1057 if (t >= 0)
1058 snprintf(buf, buf_size, "%dh %dm", t / 60, t % 60);
1059 else
1060 strncpy(buf, "?h ?m", buf_size);
1061 return buf;
1064 #if CONFIG_CHARGING
1065 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
1067 if(charger_input_state==CHARGER)
1068 return "p";
1069 else
1070 return NULL;
1072 #endif
1073 #if CONFIG_CHARGING >= CHARGING_MONITOR
1074 case WPS_TOKEN_BATTERY_CHARGING:
1076 if (charge_state == CHARGING || charge_state == TOPOFF) {
1077 return "c";
1078 } else {
1079 return NULL;
1082 #endif
1083 case WPS_TOKEN_BATTERY_SLEEPTIME:
1085 if (get_sleep_timer() == 0)
1086 return NULL;
1087 else
1089 format_time(buf, buf_size, get_sleep_timer() * 1000);
1090 return buf;
1094 case WPS_TOKEN_PLAYBACK_STATUS:
1096 int status = audio_status();
1097 int mode = 1;
1098 if (status == AUDIO_STATUS_PLAY && \
1099 !(status & AUDIO_STATUS_PAUSE))
1100 mode = 2;
1101 if (audio_status() & AUDIO_STATUS_PAUSE && \
1102 (! status_get_ffmode()))
1103 mode = 3;
1104 if (status_get_ffmode() == STATUS_FASTFORWARD)
1105 mode = 4;
1106 if (status_get_ffmode() == STATUS_FASTBACKWARD)
1107 mode = 5;
1109 if (intval) {
1110 *intval = mode;
1113 snprintf(buf, buf_size, "%d", mode);
1114 return buf;
1117 case WPS_TOKEN_REPEAT_MODE:
1118 if (intval)
1119 *intval = global_settings.repeat_mode + 1;
1120 snprintf(buf, buf_size, "%d", *intval);
1121 return buf;
1122 case WPS_TOKEN_RTC_12HOUR_CFG:
1123 if (intval)
1124 *intval = global_settings.timeformat + 1;
1125 snprintf(buf, buf_size, "%d", *intval);
1126 return buf;
1127 #if CONFIG_RTC
1128 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1129 /* d: day of month (01..31) */
1130 snprintf(buf, buf_size, "%02d", tm->tm_mday);
1131 return buf;
1133 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1134 /* e: day of month, blank padded ( 1..31) */
1135 snprintf(buf, buf_size, "%2d", tm->tm_mday);
1136 return buf;
1138 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1139 /* H: hour (00..23) */
1140 snprintf(buf, buf_size, "%02d", tm->tm_hour);
1141 return buf;
1143 case WPS_TOKEN_RTC_HOUR_24:
1144 /* k: hour ( 0..23) */
1145 snprintf(buf, buf_size, "%2d", tm->tm_hour);
1146 return buf;
1148 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1149 /* I: hour (01..12) */
1150 snprintf(buf, buf_size, "%02d",
1151 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1152 return buf;
1154 case WPS_TOKEN_RTC_HOUR_12:
1155 /* l: hour ( 1..12) */
1156 snprintf(buf, buf_size, "%2d",
1157 (tm->tm_hour % 12 == 0) ? 12 : tm->tm_hour % 12);
1158 return buf;
1160 case WPS_TOKEN_RTC_MONTH:
1161 /* m: month (01..12) */
1162 if (intval)
1163 *intval = tm->tm_mon + 1;
1164 snprintf(buf, buf_size, "%02d", tm->tm_mon + 1);
1165 return buf;
1167 case WPS_TOKEN_RTC_MINUTE:
1168 /* M: minute (00..59) */
1169 snprintf(buf, buf_size, "%02d", tm->tm_min);
1170 return buf;
1172 case WPS_TOKEN_RTC_SECOND:
1173 /* S: second (00..59) */
1174 snprintf(buf, buf_size, "%02d", tm->tm_sec);
1175 return buf;
1177 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1178 /* y: last two digits of year (00..99) */
1179 snprintf(buf, buf_size, "%02d", tm->tm_year % 100);
1180 return buf;
1182 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1183 /* Y: year (1970...) */
1184 snprintf(buf, buf_size, "%04d", tm->tm_year + 1900);
1185 return buf;
1187 case WPS_TOKEN_RTC_AM_PM_UPPER:
1188 /* p: upper case AM or PM indicator */
1189 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "AM" : "PM");
1190 return buf;
1192 case WPS_TOKEN_RTC_AM_PM_LOWER:
1193 /* P: lower case am or pm indicator */
1194 snprintf(buf, buf_size, (tm->tm_hour/12 == 0) ? "am" : "pm");
1195 return buf;
1197 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1198 /* a: abbreviated weekday name (Sun..Sat) */
1199 snprintf(buf, buf_size, "%s",str(LANG_WEEKDAY_SUNDAY + tm->tm_wday));
1200 return buf;
1202 case WPS_TOKEN_RTC_MONTH_NAME:
1203 /* b: abbreviated month name (Jan..Dec) */
1204 snprintf(buf, buf_size, "%s",str(LANG_MONTH_JANUARY + tm->tm_mon));
1205 return buf;
1207 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1208 /* u: day of week (1..7); 1 is Monday */
1209 if (intval)
1210 *intval = (tm->tm_wday == 0) ? 7 : tm->tm_wday;
1211 snprintf(buf, buf_size, "%1d", tm->tm_wday + 1);
1212 return buf;
1214 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1215 /* w: day of week (0..6); 0 is Sunday */
1216 if (intval)
1217 *intval = tm->tm_wday + 1;
1218 snprintf(buf, buf_size, "%1d", tm->tm_wday);
1219 return buf;
1220 #else
1221 case WPS_TOKEN_RTC_DAY_OF_MONTH:
1222 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED:
1223 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED:
1224 case WPS_TOKEN_RTC_HOUR_24:
1225 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED:
1226 case WPS_TOKEN_RTC_HOUR_12:
1227 case WPS_TOKEN_RTC_MONTH:
1228 case WPS_TOKEN_RTC_MINUTE:
1229 case WPS_TOKEN_RTC_SECOND:
1230 case WPS_TOKEN_RTC_AM_PM_UPPER:
1231 case WPS_TOKEN_RTC_AM_PM_LOWER:
1232 case WPS_TOKEN_RTC_YEAR_2_DIGITS:
1233 strncpy(buf, "--", buf_size);
1234 return buf;
1235 case WPS_TOKEN_RTC_YEAR_4_DIGITS:
1236 strncpy(buf, "----", buf_size);
1237 return buf;
1238 case WPS_TOKEN_RTC_WEEKDAY_NAME:
1239 case WPS_TOKEN_RTC_MONTH_NAME:
1240 strncpy(buf, "---", buf_size);
1241 return buf;
1242 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON:
1243 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN:
1244 strncpy(buf, "-", buf_size);
1245 return buf;
1246 #endif
1248 #ifdef HAVE_LCD_CHARCELLS
1249 case WPS_TOKEN_PROGRESSBAR:
1251 char *end = utf8encode(data->wps_progress_pat[0], buf);
1252 *end = '\0';
1253 return buf;
1256 case WPS_TOKEN_PLAYER_PROGRESSBAR:
1257 if(is_new_player())
1259 /* we need 11 characters (full line) for
1260 progress-bar */
1261 snprintf(buf, buf_size, " ");
1263 else
1265 /* Tell the user if we have an OldPlayer */
1266 snprintf(buf, buf_size, " <Old LCD> ");
1268 return buf;
1269 #endif
1271 #ifdef HAVE_TAGCACHE
1272 case WPS_TOKEN_DATABASE_PLAYCOUNT:
1273 if (intval) {
1274 *intval = id3->playcount + 1;
1276 snprintf(buf, buf_size, "%ld", id3->playcount);
1277 return buf;
1279 case WPS_TOKEN_DATABASE_RATING:
1280 if (intval) {
1281 *intval = id3->rating + 1;
1283 snprintf(buf, buf_size, "%d", id3->rating);
1284 return buf;
1286 case WPS_TOKEN_DATABASE_AUTOSCORE:
1287 if (intval)
1288 *intval = id3->score + 1;
1290 snprintf(buf, buf_size, "%d", id3->score);
1291 return buf;
1292 #endif
1294 #if (CONFIG_CODEC == SWCODEC)
1295 case WPS_TOKEN_CROSSFADE:
1296 if (intval)
1297 *intval = global_settings.crossfade + 1;
1298 snprintf(buf, buf_size, "%d", global_settings.crossfade);
1299 return buf;
1301 case WPS_TOKEN_REPLAYGAIN:
1303 int val;
1305 if (global_settings.replaygain == 0)
1306 val = 1; /* off */
1307 else
1309 int type =
1310 get_replaygain_mode(id3->track_gain_string != NULL,
1311 id3->album_gain_string != NULL);
1312 if (type < 0)
1313 val = 6; /* no tag */
1314 else
1315 val = type + 2;
1317 if (global_settings.replaygain_type == REPLAYGAIN_SHUFFLE)
1318 val += 2;
1321 if (intval)
1322 *intval = val;
1324 switch (val)
1326 case 1:
1327 case 6:
1328 return "+0.00 dB";
1329 break;
1330 case 2:
1331 case 4:
1332 strncpy(buf, id3->track_gain_string, buf_size);
1333 break;
1334 case 3:
1335 case 5:
1336 strncpy(buf, id3->album_gain_string, buf_size);
1337 break;
1339 return buf;
1341 #endif /* (CONFIG_CODEC == SWCODEC) */
1343 #if (CONFIG_CODEC != MAS3507D)
1344 case WPS_TOKEN_SOUND_PITCH:
1346 int val = sound_get_pitch();
1347 snprintf(buf, buf_size, "%d.%d",
1348 val / 10, val % 10);
1349 return buf;
1351 #endif
1353 case WPS_TOKEN_MAIN_HOLD:
1354 #ifdef HAS_BUTTON_HOLD
1355 if (button_hold())
1356 #else
1357 if (is_keys_locked())
1358 #endif /*hold switch or softlock*/
1359 return "h";
1360 else
1361 return NULL;
1363 #ifdef HAS_REMOTE_BUTTON_HOLD
1364 case WPS_TOKEN_REMOTE_HOLD:
1365 if (remote_button_hold())
1366 return "r";
1367 else
1368 return NULL;
1369 #endif
1371 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1372 case WPS_TOKEN_VLED_HDD:
1373 if(led_read(HZ/2))
1374 return "h";
1375 else
1376 return NULL;
1377 #endif
1379 #ifdef HAVE_LCD_BITMAP
1380 case WPS_TOKEN_LEFTMARGIN:
1381 gwps->display->setmargins(token->value.i,
1382 gwps->display->getymargin());
1383 return NULL;
1384 #endif
1386 default:
1387 return NULL;
1391 /* Return the index to the end token for the conditional token at index.
1392 The conditional token can be either a start token or a separator
1393 (i.e. option) token.
1395 static int find_conditional_end(struct wps_data *data, int index)
1397 int ret = index;
1398 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
1399 ret = data->tokens[ret].value.i;
1401 /* ret now is the index to the end token for the conditional. */
1402 return ret;
1405 /* Return the index of the appropriate case for the conditional
1406 that starts at cond_index.
1408 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
1410 if (!gwps)
1411 return false;
1413 struct wps_data *data = gwps->data;
1415 int i, cond_end;
1416 int cond_index = *token_index;
1417 char result[128], *value;
1418 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
1419 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
1421 /* treat ?xx<true> constructs as if they had 2 options. */
1422 if (num_options < 2)
1423 num_options = 2;
1425 int intval = num_options;
1426 /* get_token_value needs to know the number of options in the enum */
1427 value = get_token_value(gwps, &data->tokens[cond_index + 1],
1428 result, sizeof(result), &intval);
1430 /* intval is now the number of the enum option we want to read,
1431 starting from 1. If intval is -1, we check if value is empty. */
1432 if (intval == -1)
1433 intval = (value && *value) ? 1 : num_options;
1434 else if (intval > num_options || intval < 1)
1435 intval = num_options;
1437 data->tokens[cond_index].value.i = (intval << 8) + num_options;
1439 /* skip to the right enum case */
1440 int next = cond_index + 2;
1441 for (i = 1; i < intval; i++)
1443 next = data->tokens[next].value.i;
1445 *token_index = next;
1447 if (prev_val == intval)
1449 /* Same conditional case as previously. Return without clearing the
1450 pictures */
1451 return false;
1454 cond_end = find_conditional_end(data, cond_index + 2);
1455 for (i = cond_index + 3; i < cond_end; i++)
1457 #ifdef HAVE_LCD_BITMAP
1458 /* clear all pictures in the conditional and nested ones */
1459 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
1460 clear_image_pos(gwps, data->tokens[i].value.i & 0xFF);
1461 #endif
1462 #ifdef HAVE_ALBUMART
1463 if (data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
1464 draw_album_art(gwps, audio_current_aa_hid(), true);
1465 #endif
1468 return true;
1471 /* Read a (sub)line to the given alignment format buffer.
1472 linebuf is the buffer where the data is actually stored.
1473 align is the alignment format that'll be used to display the text.
1474 The return value indicates whether the line needs to be updated.
1476 static bool get_line(struct gui_wps *gwps,
1477 int line, int subline,
1478 struct align_pos *align,
1479 char *linebuf,
1480 int linebuf_size)
1482 struct wps_data *data = gwps->data;
1484 char temp_buf[128];
1485 char *buf = linebuf; /* will always point to the writing position */
1486 char *linebuf_end = linebuf + linebuf_size - 1;
1487 int i, last_token_idx;
1488 bool update = false;
1490 /* alignment-related variables */
1491 int cur_align;
1492 char* cur_align_start;
1493 cur_align_start = buf;
1494 cur_align = WPS_ALIGN_LEFT;
1495 align->left = NULL;
1496 align->center = NULL;
1497 align->right = NULL;
1499 #ifdef HAVE_LCD_BITMAP
1500 /* Reset margins - only bitmap targets modify them */
1501 gwps->display->setmargins(0, gwps->display->getymargin());
1502 #endif
1504 /* Process all tokens of the desired subline */
1505 last_token_idx = wps_last_token_index(data, line, subline);
1506 for (i = wps_first_token_index(data, line, subline);
1507 i <= last_token_idx; i++)
1509 switch(data->tokens[i].type)
1511 case WPS_TOKEN_CONDITIONAL:
1512 /* place ourselves in the right conditional case */
1513 update |= evaluate_conditional(gwps, &i);
1514 break;
1516 case WPS_TOKEN_CONDITIONAL_OPTION:
1517 /* we've finished in the curent conditional case,
1518 skip to the end of the conditional structure */
1519 i = find_conditional_end(data, i);
1520 break;
1522 #ifdef HAVE_LCD_BITMAP
1523 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
1525 struct gui_img *img = data->img;
1526 int n = data->tokens[i].value.i & 0xFF;
1527 int subimage = data->tokens[i].value.i >> 8;
1529 if (n >= 0 && n < MAX_IMAGES && img[n].loaded)
1530 img[n].display = subimage;
1531 break;
1533 #endif
1535 case WPS_TOKEN_ALIGN_LEFT:
1536 case WPS_TOKEN_ALIGN_CENTER:
1537 case WPS_TOKEN_ALIGN_RIGHT:
1538 /* remember where the current aligned text started */
1539 switch (cur_align)
1541 case WPS_ALIGN_LEFT:
1542 align->left = cur_align_start;
1543 break;
1545 case WPS_ALIGN_CENTER:
1546 align->center = cur_align_start;
1547 break;
1549 case WPS_ALIGN_RIGHT:
1550 align->right = cur_align_start;
1551 break;
1553 /* start a new alignment */
1554 switch (data->tokens[i].type)
1556 case WPS_TOKEN_ALIGN_LEFT:
1557 cur_align = WPS_ALIGN_LEFT;
1558 break;
1559 case WPS_TOKEN_ALIGN_CENTER:
1560 cur_align = WPS_ALIGN_CENTER;
1561 break;
1562 case WPS_TOKEN_ALIGN_RIGHT:
1563 cur_align = WPS_ALIGN_RIGHT;
1564 break;
1565 default:
1566 break;
1568 *buf++ = 0;
1569 cur_align_start = buf;
1570 break;
1572 default:
1574 /* get the value of the tag and copy it to the buffer */
1575 char *value = get_token_value(gwps, &data->tokens[i],
1576 temp_buf, sizeof(temp_buf), NULL);
1577 if (value)
1579 update = true;
1580 while (*value && (buf < linebuf_end))
1581 *buf++ = *value++;
1583 break;
1588 /* close the current alignment */
1589 switch (cur_align)
1591 case WPS_ALIGN_LEFT:
1592 align->left = cur_align_start;
1593 break;
1595 case WPS_ALIGN_CENTER:
1596 align->center = cur_align_start;
1597 break;
1599 case WPS_ALIGN_RIGHT:
1600 align->right = cur_align_start;
1601 break;
1604 return update;
1607 static void get_subline_timeout(struct gui_wps *gwps, int line, int subline)
1609 struct wps_data *data = gwps->data;
1610 int i;
1611 int subline_idx = wps_subline_index(data, line, subline);
1612 int last_token_idx = wps_last_token_index(data, line, subline);
1614 data->sublines[subline_idx].time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
1616 for (i = wps_first_token_index(data, line, subline);
1617 i <= last_token_idx; i++)
1619 switch(data->tokens[i].type)
1621 case WPS_TOKEN_CONDITIONAL:
1622 /* place ourselves in the right conditional case */
1623 evaluate_conditional(gwps, &i);
1624 break;
1626 case WPS_TOKEN_CONDITIONAL_OPTION:
1627 /* we've finished in the curent conditional case,
1628 skip to the end of the conditional structure */
1629 i = find_conditional_end(data, i);
1630 break;
1632 case WPS_TOKEN_SUBLINE_TIMEOUT:
1633 data->sublines[subline_idx].time_mult = data->tokens[i].value.i;
1634 break;
1636 default:
1637 break;
1642 /* Calculates which subline should be displayed for the specified line
1643 Returns true iff the subline must be refreshed */
1644 static bool update_curr_subline(struct gui_wps *gwps, int line)
1646 struct wps_data *data = gwps->data;
1648 int search, search_start, num_sublines;
1649 bool reset_subline;
1650 bool new_subline_refresh;
1651 bool only_one_subline;
1653 num_sublines = data->lines[line].num_sublines;
1654 reset_subline = (data->lines[line].curr_subline == SUBLINE_RESET);
1655 new_subline_refresh = false;
1656 only_one_subline = false;
1658 /* if time to advance to next sub-line */
1659 if (TIME_AFTER(current_tick, data->lines[line].subline_expire_time - 1) ||
1660 reset_subline)
1662 /* search all sublines until the next subline with time > 0
1663 is found or we get back to the subline we started with */
1664 if (reset_subline)
1665 search_start = 0;
1666 else
1667 search_start = data->lines[line].curr_subline;
1669 for (search = 0; search < num_sublines; search++)
1671 data->lines[line].curr_subline++;
1673 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1674 if (data->lines[line].curr_subline == num_sublines)
1676 if (data->lines[line].curr_subline == 1)
1677 only_one_subline = true;
1678 data->lines[line].curr_subline = 0;
1681 /* if back where we started after search or
1682 only one subline is defined on the line */
1683 if (((search > 0) &&
1684 (data->lines[line].curr_subline == search_start)) ||
1685 only_one_subline)
1687 /* no other subline with a time > 0 exists */
1688 data->lines[line].subline_expire_time = (reset_subline ?
1689 current_tick :
1690 data->lines[line].subline_expire_time) + 100 * HZ;
1691 break;
1693 else
1695 /* get initial time multiplier for this subline */
1696 get_subline_timeout(gwps, line, data->lines[line].curr_subline);
1698 int subline_idx = wps_subline_index(data, line,
1699 data->lines[line].curr_subline);
1701 /* only use this subline if subline time > 0 */
1702 if (data->sublines[subline_idx].time_mult > 0)
1704 new_subline_refresh = true;
1705 data->lines[line].subline_expire_time = (reset_subline ?
1706 current_tick : data->lines[line].subline_expire_time) +
1707 BASE_SUBLINE_TIME*data->sublines[subline_idx].time_mult;
1708 break;
1714 return new_subline_refresh;
1717 /* Display a line appropriately according to its alignment format.
1718 format_align contains the text, separated between left, center and right.
1719 line is the index of the line on the screen.
1720 scroll indicates whether the line is a scrolling one or not.
1722 static void write_line(struct screen *display,
1723 struct align_pos *format_align,
1724 int line,
1725 bool scroll)
1728 int left_width = 0, left_xpos;
1729 int center_width = 0, center_xpos;
1730 int right_width = 0, right_xpos;
1731 int ypos;
1732 int space_width;
1733 int string_height;
1734 int scroll_width;
1736 /* calculate different string sizes and positions */
1737 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1738 if (format_align->left != 0) {
1739 display->getstringsize((unsigned char *)format_align->left,
1740 &left_width, &string_height);
1743 if (format_align->right != 0) {
1744 display->getstringsize((unsigned char *)format_align->right,
1745 &right_width, &string_height);
1748 if (format_align->center != 0) {
1749 display->getstringsize((unsigned char *)format_align->center,
1750 &center_width, &string_height);
1753 left_xpos = display->getxmargin();
1754 right_xpos = (display->getwidth() - right_width);
1755 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1757 scroll_width = display->getwidth() - left_xpos;
1759 /* Checks for overlapping strings.
1760 If needed the overlapping strings will be merged, separated by a
1761 space */
1763 /* CASE 1: left and centered string overlap */
1764 /* there is a left string, need to merge left and center */
1765 if ((left_width != 0 && center_width != 0) &&
1766 (left_xpos + left_width + space_width > center_xpos)) {
1767 /* replace the former separator '\0' of left and
1768 center string with a space */
1769 *(--format_align->center) = ' ';
1770 /* calculate the new width and position of the merged string */
1771 left_width = left_width + space_width + center_width;
1772 /* there is no centered string anymore */
1773 center_width = 0;
1775 /* there is no left string, move center to left */
1776 if ((left_width == 0 && center_width != 0) &&
1777 (left_xpos + left_width > center_xpos)) {
1778 /* move the center string to the left string */
1779 format_align->left = format_align->center;
1780 /* calculate the new width and position of the string */
1781 left_width = center_width;
1782 /* there is no centered string anymore */
1783 center_width = 0;
1786 /* CASE 2: centered and right string overlap */
1787 /* there is a right string, need to merge center and right */
1788 if ((center_width != 0 && right_width != 0) &&
1789 (center_xpos + center_width + space_width > right_xpos)) {
1790 /* replace the former separator '\0' of center and
1791 right string with a space */
1792 *(--format_align->right) = ' ';
1793 /* move the center string to the right after merge */
1794 format_align->right = format_align->center;
1795 /* calculate the new width and position of the merged string */
1796 right_width = center_width + space_width + right_width;
1797 right_xpos = (display->getwidth() - right_width);
1798 /* there is no centered string anymore */
1799 center_width = 0;
1801 /* there is no right string, move center to right */
1802 if ((center_width != 0 && right_width == 0) &&
1803 (center_xpos + center_width > right_xpos)) {
1804 /* move the center string to the right string */
1805 format_align->right = format_align->center;
1806 /* calculate the new width and position of the string */
1807 right_width = center_width;
1808 right_xpos = (display->getwidth() - right_width);
1809 /* there is no centered string anymore */
1810 center_width = 0;
1813 /* CASE 3: left and right overlap
1814 There is no center string anymore, either there never
1815 was one or it has been merged in case 1 or 2 */
1816 /* there is a left string, need to merge left and right */
1817 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1818 (left_xpos + left_width + space_width > right_xpos)) {
1819 /* replace the former separator '\0' of left and
1820 right string with a space */
1821 *(--format_align->right) = ' ';
1822 /* calculate the new width and position of the string */
1823 left_width = left_width + space_width + right_width;
1824 /* there is no right string anymore */
1825 right_width = 0;
1827 /* there is no left string, move right to left */
1828 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1829 (left_width > right_xpos)) {
1830 /* move the right string to the left string */
1831 format_align->left = format_align->right;
1832 /* calculate the new width and position of the string */
1833 left_width = right_width;
1834 /* there is no right string anymore */
1835 right_width = 0;
1838 ypos = (line * string_height) + display->getymargin();
1841 if (scroll && ((left_width > scroll_width) ||
1842 (center_width > scroll_width) ||
1843 (right_width > scroll_width)))
1845 display->puts_scroll(0, line,
1846 (unsigned char *)format_align->left);
1848 else
1850 #ifdef HAVE_LCD_BITMAP
1851 /* clear the line first */
1852 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1853 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1854 display->set_drawmode(DRMODE_SOLID);
1855 #endif
1857 /* Nasty hack: we output an empty scrolling string,
1858 which will reset the scroller for that line */
1859 display->puts_scroll(0, line, (unsigned char *)"");
1861 /* print aligned strings */
1862 if (left_width != 0)
1864 display->putsxy(left_xpos, ypos,
1865 (unsigned char *)format_align->left);
1867 if (center_width != 0)
1869 display->putsxy(center_xpos, ypos,
1870 (unsigned char *)format_align->center);
1872 if (right_width != 0)
1874 display->putsxy(right_xpos, ypos,
1875 (unsigned char *)format_align->right);
1880 /* Refresh the WPS according to refresh_mode. */
1881 bool gui_wps_refresh(struct gui_wps *gwps,
1882 int ffwd_offset,
1883 unsigned char refresh_mode)
1885 struct wps_data *data = gwps->data;
1886 struct screen *display = gwps->display;
1887 struct wps_state *state = gwps->state;
1889 if(!gwps || !data || !state || !display)
1890 return false;
1892 int v, line, i, subline_idx;
1893 unsigned char flags;
1894 char linebuf[MAX_PATH];
1896 struct align_pos align;
1897 align.left = NULL;
1898 align.center = NULL;
1899 align.right = NULL;
1901 bool update_line, new_subline_refresh;
1903 #ifdef HAVE_LCD_BITMAP
1904 gui_wps_statusbar_draw(gwps, true);
1906 /* to find out wether the peak meter is enabled we
1907 assume it wasn't until we find a line that contains
1908 the peak meter. We can't use peak_meter_enabled itself
1909 because that would mean to turn off the meter thread
1910 temporarily. (That shouldn't matter unless yield
1911 or sleep is called but who knows...)
1913 bool enable_pm = false;
1915 #endif
1917 /* reset to first subline if refresh all flag is set */
1918 if (refresh_mode == WPS_REFRESH_ALL)
1920 display->clear_display();
1922 for (i = 0; i <= data->num_lines; i++)
1924 data->lines[i].curr_subline = SUBLINE_RESET;
1928 #ifdef HAVE_LCD_CHARCELLS
1929 for (i = 0; i < 8; i++)
1931 if (data->wps_progress_pat[i] == 0)
1932 data->wps_progress_pat[i] = display->get_locked_pattern();
1934 #endif
1936 if (!state->id3)
1938 display->stop_scroll();
1939 return false;
1942 state->ff_rewind_count = ffwd_offset;
1944 for (v = 0; v < data->num_viewports; v++)
1946 display->set_viewport(&data->viewports[v].vp);
1948 if (refresh_mode == WPS_REFRESH_ALL)
1950 display->clear_viewport();
1953 #ifdef HAVE_LCD_BITMAP
1954 /* Set images to not to be displayed */
1955 for (i = 0; i < MAX_IMAGES; i++)
1957 data->img[i].display = -1;
1959 #endif
1961 for (line = data->viewports[v].first_line;
1962 line <= data->viewports[v].last_line; line++)
1964 memset(linebuf, 0, sizeof(linebuf));
1965 update_line = false;
1967 /* get current subline for the line */
1968 new_subline_refresh = update_curr_subline(gwps, line);
1970 subline_idx = wps_subline_index(data, line,
1971 data->lines[line].curr_subline);
1972 flags = data->sublines[subline_idx].line_type;
1974 if (refresh_mode == WPS_REFRESH_ALL || (flags & refresh_mode)
1975 || new_subline_refresh)
1977 /* get_line tells us if we need to update the line */
1978 update_line = get_line(gwps, line, data->lines[line].curr_subline,
1979 &align, linebuf, sizeof(linebuf));
1982 #ifdef HAVE_LCD_BITMAP
1983 /* progressbar */
1984 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1986 /* the progressbar should be alone on its line */
1987 update_line = false;
1988 draw_progressbar(gwps, line - data->viewports[v].first_line);
1991 /* peakmeter */
1992 if (flags & refresh_mode & WPS_REFRESH_PEAK_METER)
1994 /* the peakmeter should be alone on its line */
1995 update_line = false;
1997 int h = font_get(display->getfont())->height;
1998 int peak_meter_y = display->getymargin() + (line - data->viewports[v].first_line)* h;
2000 /* The user might decide to have the peak meter in the last
2001 line so that it is only displayed if no status bar is
2002 visible. If so we neither want do draw nor enable the
2003 peak meter. */
2004 if (peak_meter_y + h <= display->getheight()) {
2005 /* found a line with a peak meter -> remember that we must
2006 enable it later */
2007 enable_pm = true;
2008 peak_meter_screen(gwps->display, 0, peak_meter_y,
2009 MIN(h, display->getheight() - peak_meter_y));
2013 #else /* HAVE_LCD_CHARCELL */
2015 /* progressbar */
2016 if (flags & refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
2018 if (data->full_line_progressbar)
2019 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
2020 else
2021 draw_player_progress(gwps);
2023 #endif
2025 if (update_line)
2027 if (flags & WPS_REFRESH_SCROLL)
2029 /* if the line is a scrolling one we don't want to update
2030 too often, so that it has the time to scroll */
2031 if ((refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
2032 write_line(display, &align, line - data->viewports[v].first_line, true);
2034 else
2035 write_line(display, &align, line - data->viewports[v].first_line, false);
2040 #ifdef HAVE_LCD_BITMAP
2041 /* Now display any images in this viewport */
2042 wps_display_images(gwps, &data->viewports[v].vp);
2043 #endif
2046 #ifdef HAVE_LCD_BITMAP
2047 data->peak_meter_enabled = enable_pm;
2048 #endif
2050 /* Restore the default viewport */
2051 display->set_viewport(NULL);
2053 display->update();
2055 #ifdef HAVE_BACKLIGHT
2056 if (global_settings.caption_backlight && state->id3)
2058 /* turn on backlight n seconds before track ends, and turn it off n
2059 seconds into the new track. n == backlight_timeout, or 5s */
2060 int n = global_settings.backlight_timeout * 1000;
2062 if ( n < 1000 )
2063 n = 5000; /* use 5s if backlight is always on or off */
2065 if (((state->id3->elapsed < 1000) ||
2066 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2067 (state->paused == false))
2068 backlight_on();
2070 #endif
2071 #ifdef HAVE_REMOTE_LCD
2072 if (global_settings.remote_caption_backlight && state->id3)
2074 /* turn on remote backlight n seconds before track ends, and turn it
2075 off n seconds into the new track. n == remote_backlight_timeout,
2076 or 5s */
2077 int n = global_settings.remote_backlight_timeout * 1000;
2079 if ( n < 1000 )
2080 n = 5000; /* use 5s if backlight is always on or off */
2082 if (((state->id3->elapsed < 1000) ||
2083 ((state->id3->length - state->id3->elapsed) < (unsigned)n)) &&
2084 (state->paused == false))
2085 remote_backlight_on();
2087 #endif
2089 return true;