1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2002-2007 Björn Stenberg
11 * Copyright (C) 2007-2008 Nicolas Pennequin
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "gwps-common.h"
29 #include "settings_list.h"
30 #include "rbunicode.h"
35 #include "powermgmt.h"
38 #ifdef HAVE_LCD_CHARCELLS
42 #include "mp3_playback.h"
43 #include "backlight.h"
47 #include "scrollbar.h"
50 #ifdef HAVE_LCD_BITMAP
51 #include "peakmeter.h"
60 #if CONFIG_CODEC == SWCODEC
65 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
66 /* 3% of 30min file == 54s step size */
67 #define MIN_FF_REWIND_STEP 500
69 /* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds
70 (possibly with a decimal fraction) but stored as integer values.
71 E.g. 2.5 is stored as 25. This means 25 tenth of a second, i.e. 25 units.
73 #define TIMEOUT_UNIT (HZ/10) /* I.e. 0.1 sec */
74 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* In TIMEOUT_UNIT's */
77 /* draws the statusbar on the given wps-screen */
78 #ifdef HAVE_LCD_BITMAP
79 static void gui_wps_statusbar_draw(struct gui_wps
*wps
, bool force
)
81 bool draw
= global_settings
.statusbar
;
83 if (wps
->data
->wps_sb_tag
)
84 draw
= wps
->data
->show_sb_on_wps
;
87 gui_statusbar_draw(wps
->statusbar
, force
);
90 #define gui_wps_statusbar_draw(wps, force) \
91 gui_statusbar_draw((wps)->statusbar, (force))
95 /* fades the volume */
96 bool wps_fading_out
= false;
97 void fade(bool fade_in
, bool updatewps
)
99 int fp_global_vol
= global_settings
.volume
<< 8;
100 int fp_min_vol
= sound_min(SOUND_VOLUME
) << 8;
101 int fp_step
= (fp_global_vol
- fp_min_vol
) / 30;
103 wps_fading_out
= !fade_in
;
106 int fp_volume
= fp_min_vol
;
108 /* zero out the sound */
109 sound_set_volume(fp_min_vol
>> 8);
111 sleep(HZ
/10); /* let audio thread run */
114 while (fp_volume
< fp_global_vol
- fp_step
) {
115 fp_volume
+= fp_step
;
116 sound_set_volume(fp_volume
>> 8);
120 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_NON_STATIC
);
124 sound_set_volume(global_settings
.volume
);
128 int fp_volume
= fp_global_vol
;
130 while (fp_volume
> fp_min_vol
+ fp_step
) {
131 fp_volume
-= fp_step
;
132 sound_set_volume(fp_volume
>> 8);
136 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_NON_STATIC
);
141 wps_fading_out
= false;
142 #if CONFIG_CODEC != SWCODEC
144 /* let audio thread run and wait for the mas to run out of data */
145 while (!mp3_pause_done())
150 /* reset volume to what it was before the fade */
151 sound_set_volume(global_settings
.volume
);
155 /* return true if screen restore is needed
156 return false otherwise
158 bool update_onvol_change(struct gui_wps
* gwps
)
160 gui_wps_statusbar_draw(gwps
, false);
161 gui_wps_refresh(gwps
, 0, WPS_REFRESH_NON_STATIC
);
163 #ifdef HAVE_LCD_CHARCELLS
164 splashf(0, "Vol: %3d dB",
165 sound_val2phys(SOUND_VOLUME
, global_settings
.volume
));
171 void play_hop(int direction
)
173 if(!wps_state
.id3
|| !wps_state
.id3
->length
174 || global_settings
.skip_length
== 0)
176 #define STEP ((unsigned)global_settings.skip_length*1000)
178 && wps_state
.id3
->length
- wps_state
.id3
->elapsed
< STEP
+1000) {
179 #if CONFIG_CODEC == SWCODEC
180 if(global_settings
.beep
)
181 pcmbuf_beep(1000, 150, 1500*global_settings
.beep
);
185 if((direction
== -1 && wps_state
.id3
->elapsed
< STEP
))
186 wps_state
.id3
->elapsed
= 0;
188 wps_state
.id3
->elapsed
+= STEP
*direction
;
189 if((audio_status() & AUDIO_STATUS_PLAY
) && !wps_state
.paused
) {
190 #if (CONFIG_CODEC == SWCODEC)
191 audio_pre_ff_rewind();
196 audio_ff_rewind(wps_state
.id3
->elapsed
);
197 #if (CONFIG_CODEC != SWCODEC)
198 if (!wps_state
.paused
)
204 bool ffwd_rew(int button
)
206 unsigned int step
= 0; /* current ff/rewind step */
207 unsigned int max_step
= 0; /* maximum ff/rewind step */
208 int ff_rewind_count
= 0; /* current ff/rewind count (in ticks) */
209 int direction
= -1; /* forward=1 or backward=-1 */
213 const long ff_rw_accel
= (global_settings
.ff_rewind_accel
+ 3);
215 if (button
== ACTION_NONE
)
217 status_set_ffmode(0);
224 case ACTION_WPS_SEEKFWD
:
226 case ACTION_WPS_SEEKBACK
:
227 if (wps_state
.ff_rewind
)
231 /* fast forwarding, calc max step relative to end */
232 max_step
= (wps_state
.id3
->length
-
233 (wps_state
.id3
->elapsed
+
235 FF_REWIND_MAX_PERCENT
/ 100;
239 /* rewinding, calc max step relative to start */
240 max_step
= (wps_state
.id3
->elapsed
+ ff_rewind_count
) *
241 FF_REWIND_MAX_PERCENT
/ 100;
244 max_step
= MAX(max_step
, MIN_FF_REWIND_STEP
);
249 ff_rewind_count
+= step
* direction
;
251 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
252 step
+= step
>> ff_rw_accel
;
256 if ( (audio_status() & AUDIO_STATUS_PLAY
) &&
257 wps_state
.id3
&& wps_state
.id3
->length
)
259 if (!wps_state
.paused
)
260 #if (CONFIG_CODEC == SWCODEC)
261 audio_pre_ff_rewind();
265 #if CONFIG_KEYPAD == PLAYER_PAD
267 gui_wps
[i
].display
->stop_scroll();
270 status_set_ffmode(STATUS_FASTFORWARD
);
272 status_set_ffmode(STATUS_FASTBACKWARD
);
274 wps_state
.ff_rewind
= true;
276 step
= 1000 * global_settings
.ff_rewind_min_step
;
283 if ((wps_state
.id3
->elapsed
+ ff_rewind_count
) >
284 wps_state
.id3
->length
)
285 ff_rewind_count
= wps_state
.id3
->length
-
286 wps_state
.id3
->elapsed
;
289 if ((int)(wps_state
.id3
->elapsed
+ ff_rewind_count
) < 0)
290 ff_rewind_count
= -wps_state
.id3
->elapsed
;
294 gui_wps_refresh(&gui_wps
[i
],
295 (wps_state
.wps_time_countup
== false)?
296 ff_rewind_count
:-ff_rewind_count
,
297 WPS_REFRESH_PLAYER_PROGRESS
|
298 WPS_REFRESH_DYNAMIC
);
302 case ACTION_WPS_STOPSEEK
:
303 wps_state
.id3
->elapsed
= wps_state
.id3
->elapsed
+ff_rewind_count
;
304 audio_ff_rewind(wps_state
.id3
->elapsed
);
306 wps_state
.ff_rewind
= false;
307 status_set_ffmode(0);
308 #if (CONFIG_CODEC != SWCODEC)
309 if (!wps_state
.paused
)
312 #ifdef HAVE_LCD_CHARCELLS
319 if(default_event_handler(button
) == SYS_USB_CONNECTED
) {
320 status_set_ffmode(0);
327 button
= get_action(CONTEXT_WPS
|ALLOW_SOFTLOCK
,TIMEOUT_BLOCK
);
332 bool gui_wps_display(void)
335 if (!wps_state
.id3
&& !(audio_status() & AUDIO_STATUS_PLAY
))
337 global_status
.resume_index
= -1;
338 #ifdef HAVE_LCD_BITMAP
339 gui_syncstatusbar_draw(&statusbars
, true);
341 splash(HZ
, ID2P(LANG_END_PLAYLIST
));
348 /* Update the values in the first (default) viewport - in case the user
349 has modified the statusbar or colour settings */
350 #ifdef HAVE_LCD_BITMAP
352 if (gui_wps
[i
].display
->depth
> 1)
354 gui_wps
[i
].data
->viewports
[0].vp
.fg_pattern
= gui_wps
[i
].display
->get_foreground();
355 gui_wps
[i
].data
->viewports
[0].vp
.bg_pattern
= gui_wps
[i
].display
->get_background();
360 gui_wps
[i
].display
->clear_display();
361 if (!gui_wps
[i
].data
->wps_loaded
) {
362 if ( !gui_wps
[i
].data
->num_tokens
) {
363 /* set the default wps for the main-screen */
366 #ifdef HAVE_LCD_BITMAP
368 unload_wps_backdrop();
370 wps_data_load(gui_wps
[i
].data
,
372 "%s%?it<%?in<%in. |>%it|%fn>\n"
373 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
374 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
376 "%al%pc/%pt%ar[%pp:%pe]\n"
377 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
381 wps_data_load(gui_wps
[i
].data
,
383 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
384 "%pc%?ps<*|/>%pt\n", false);
388 /* set the default wps for the remote-screen */
391 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
392 unload_remote_wps_backdrop();
394 wps_data_load(gui_wps
[i
].data
,
396 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
397 "%s%?it<%?in<%in. |>%it|%fn>\n"
398 "%al%pc/%pt%ar[%pp:%pe]\n"
399 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
410 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_ALL
);
415 bool update(struct gui_wps
*gwps
)
417 bool track_changed
= audio_has_changed_track();
418 bool retcode
= false;
420 gwps
->state
->nid3
= audio_next_track();
423 gwps
->display
->stop_scroll();
424 gwps
->state
->id3
= audio_current_track();
426 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
427 && strcmp(gwps
->state
->id3
->path
, curr_cue
->audio_filename
))
429 /* the current cuesheet isn't the right one any more */
431 if (!strcmp(gwps
->state
->id3
->path
, temp_cue
->audio_filename
)) {
432 /* We have the new cuesheet in memory (temp_cue),
433 let's make it the current one ! */
434 memcpy(curr_cue
, temp_cue
, sizeof(struct cuesheet
));
437 /* We need to parse the new cuesheet */
439 char cuepath
[MAX_PATH
];
441 if (look_for_cuesheet_file(gwps
->state
->id3
->path
, cuepath
) &&
442 parse_cuesheet(cuepath
, curr_cue
))
444 gwps
->state
->id3
->cuesheet_type
= 1;
445 strcpy(curr_cue
->audio_filename
, gwps
->state
->id3
->path
);
449 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
452 if (gui_wps_display())
455 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
458 if (gwps
->state
->id3
)
460 strncpy(gwps
->state
->current_track_path
, gwps
->state
->id3
->path
,
461 sizeof(gwps
->state
->current_track_path
));
462 gwps
->state
->current_track_path
[sizeof(gwps
->state
->current_track_path
)-1] = '\0';
466 if (gwps
->state
->id3
)
468 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
469 && (gwps
->state
->id3
->elapsed
< curr_cue
->curr_track
->offset
470 || (curr_cue
->curr_track_idx
< curr_cue
->track_count
- 1
471 && gwps
->state
->id3
->elapsed
>= (curr_cue
->curr_track
+1)->offset
)))
473 /* We've changed tracks within the cuesheet :
474 we need to update the ID3 info and refresh the WPS */
476 cue_find_current_track(curr_cue
, gwps
->state
->id3
->elapsed
);
477 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
479 gwps
->display
->stop_scroll();
480 if (gui_wps_display())
483 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
486 gui_wps_refresh(gwps
, 0, WPS_REFRESH_NON_STATIC
);
489 gui_wps_statusbar_draw(gwps
, false);
495 void display_keylock_text(bool locked
)
499 gui_wps
[i
].display
->stop_scroll();
501 splash(HZ
, locked
? ID2P(LANG_KEYLOCK_ON
) : ID2P(LANG_KEYLOCK_OFF
));
504 #ifdef HAVE_LCD_BITMAP
506 static void draw_progressbar(struct gui_wps
*gwps
,
507 struct progressbar
*pb
)
509 struct screen
*display
= gwps
->display
;
510 struct wps_state
*state
= gwps
->state
;
511 if (pb
->have_bitmap_pb
)
512 gui_bitmap_scrollbar_draw(display
, pb
->bm
,
513 pb
->x
, pb
->y
, pb
->width
, pb
->bm
.height
,
514 state
->id3
->length
? state
->id3
->length
: 1, 0,
515 state
->id3
->length
? state
->id3
->elapsed
516 + state
->ff_rewind_count
: 0,
519 gui_scrollbar_draw(display
, pb
->x
, pb
->y
, pb
->width
, pb
->height
,
520 state
->id3
->length
? state
->id3
->length
: 1, 0,
521 state
->id3
->length
? state
->id3
->elapsed
522 + state
->ff_rewind_count
: 0,
524 #ifdef AB_REPEAT_ENABLE
525 if ( ab_repeat_mode_enabled() && state
->id3
->length
!= 0 )
526 ab_draw_markers(display
, state
->id3
->length
,
527 pb
->x
, pb
->x
+ pb
->width
, pb
->y
, pb
->height
);
530 if ( cuesheet_is_enabled() && state
->id3
->cuesheet_type
)
531 cue_draw_markers(display
, state
->id3
->length
,
532 pb
->x
, pb
->x
+ pb
->width
, pb
->y
+1, pb
->height
-2);
535 /* clears the area where the image was shown */
536 static void clear_image_pos(struct gui_wps
*gwps
, int n
)
540 struct wps_data
*data
= gwps
->data
;
541 gwps
->display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
542 gwps
->display
->fillrect(data
->img
[n
].x
, data
->img
[n
].y
,
543 data
->img
[n
].bm
.width
, data
->img
[n
].subimage_height
);
544 gwps
->display
->set_drawmode(DRMODE_SOLID
);
547 static void wps_draw_image(struct gui_wps
*gwps
, int n
, int subimage
)
549 struct screen
*display
= gwps
->display
;
550 struct wps_data
*data
= gwps
->data
;
551 if(data
->img
[n
].always_display
)
552 display
->set_drawmode(DRMODE_FG
);
554 display
->set_drawmode(DRMODE_SOLID
);
557 if(data
->img
[n
].bm
.format
== FORMAT_MONO
) {
559 display
->mono_bitmap_part(data
->img
[n
].bm
.data
,
560 0, data
->img
[n
].subimage_height
* subimage
,
561 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
562 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
563 data
->img
[n
].subimage_height
);
566 display
->transparent_bitmap_part((fb_data
*)data
->img
[n
].bm
.data
,
567 0, data
->img
[n
].subimage_height
* subimage
,
568 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
569 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
570 data
->img
[n
].subimage_height
);
575 static void wps_display_images(struct gui_wps
*gwps
, struct viewport
* vp
)
577 if(!gwps
|| !gwps
->data
|| !gwps
->display
)
581 struct wps_data
*data
= gwps
->data
;
582 struct screen
*display
= gwps
->display
;
584 for (n
= 0; n
< MAX_IMAGES
; n
++)
586 if (data
->img
[n
].loaded
)
588 if (data
->img
[n
].display
>= 0)
590 wps_draw_image(gwps
, n
, data
->img
[n
].display
);
591 } else if (data
->img
[n
].always_display
&& data
->img
[n
].vp
== vp
)
593 wps_draw_image(gwps
, n
, 0);
597 display
->set_drawmode(DRMODE_SOLID
);
600 #else /* HAVE_LCD_CHARCELL */
602 static bool draw_player_progress(struct gui_wps
*gwps
)
604 struct wps_state
*state
= gwps
->state
;
605 struct screen
*display
= gwps
->display
;
606 unsigned char progress_pattern
[7];
613 if (state
->id3
->length
)
614 pos
= 36 * (state
->id3
->elapsed
+ state
->ff_rewind_count
)
615 / state
->id3
->length
;
617 for (i
= 0; i
< 7; i
++, pos
-= 5)
620 progress_pattern
[i
] = 0x1fu
;
622 progress_pattern
[i
] = 0x00u
;
624 progress_pattern
[i
] = 0x1fu
>> pos
;
627 display
->define_pattern(gwps
->data
->wps_progress_pat
[0], progress_pattern
);
631 static void draw_player_fullbar(struct gui_wps
*gwps
, char* buf
, int buf_size
)
633 static const unsigned char numbers
[10][4] = {
634 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
635 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
636 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
637 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
638 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
639 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
640 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
641 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
642 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
643 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
646 struct wps_state
*state
= gwps
->state
;
647 struct screen
*display
= gwps
->display
;
648 struct wps_data
*data
= gwps
->data
;
649 unsigned char progress_pattern
[7];
658 if (!state
->id3
|| buf_size
< 34) /* worst case: 11x UTF-8 char + \0 */
661 time
= state
->id3
->elapsed
+ state
->ff_rewind_count
;
662 if (state
->id3
->length
)
663 pos
= 55 * time
/ state
->id3
->length
;
665 memset(timestr
, 0, sizeof(timestr
));
666 format_time(timestr
, sizeof(timestr
)-2, time
);
667 timestr
[strlen(timestr
)] = ':'; /* always safe */
669 for (i
= 0; i
< 11; i
++, pos
-= 5)
672 memset(progress_pattern
, 0, sizeof(progress_pattern
));
674 if ((digit
= timestr
[time_idx
]))
679 if (timestr
[time_idx
+ 1] == ':') /* ones, left aligned */
681 memcpy(progress_pattern
, numbers
[digit
], 4);
684 else /* tens, shifted right */
686 for (j
= 0; j
< 4; j
++)
687 progress_pattern
[j
] = numbers
[digit
][j
] >> 1;
689 if (time_idx
> 0) /* not the first group, add colon in front */
691 progress_pattern
[1] |= 0x10u
;
692 progress_pattern
[3] |= 0x10u
;
698 progress_pattern
[5] = progress_pattern
[6] = 0x1fu
;
701 if (pos
> 0 && pos
< 5)
704 progress_pattern
[5] = progress_pattern
[6] = (~0x1fu
>> pos
) & 0x1fu
;
707 if (softchar
&& pat_idx
< 8)
709 display
->define_pattern(data
->wps_progress_pat
[pat_idx
],
711 buf
= utf8encode(data
->wps_progress_pat
[pat_idx
], buf
);
715 buf
= utf8encode(' ', buf
);
717 buf
= utf8encode(0xe115, buf
); /* 2/7 _ */
722 #endif /* HAVE_LCD_CHARCELL */
724 static char* get_codectype(const struct mp3entry
* id3
)
726 if (id3
->codectype
< AFMT_NUM_CODECS
) {
727 return (char*)audio_formats
[id3
->codectype
].label
;
733 /* Extract a part from a path.
735 * buf - buffer extract part to.
736 * buf_size - size of buffer.
737 * path - path to extract from.
738 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
739 * parent of parent, etc.
741 * Returns buf if the desired level was found, NULL otherwise.
743 static char* get_dir(char* buf
, int buf_size
, const char* path
, int level
)
746 const char* last_sep
;
749 sep
= path
+ strlen(path
);
764 if (level
|| (last_sep
<= sep
))
767 len
= MIN(last_sep
- sep
, buf_size
- 1);
768 strncpy(buf
, sep
+ 1, len
);
773 /* Return the tag found at index i and write its value in buf.
774 The return value is buf if the tag had a value, or NULL if not.
776 intval is used with conditionals/enums: when this function is called,
777 intval should contain the number of options in the conditional/enum.
778 When this function returns, intval is -1 if the tag is non numeric or,
779 if the tag is numeric, *intval is the enum case we want to go to (between 1
780 and the original value of *intval, inclusive).
781 When not treating a conditional/enum, intval should be NULL.
783 static const char *get_token_value(struct gui_wps
*gwps
,
784 struct wps_token
*token
,
785 char *buf
, int buf_size
,
791 struct wps_data
*data
= gwps
->data
;
792 struct wps_state
*state
= gwps
->state
;
797 struct mp3entry
*id3
;
808 struct tm
* tm
= NULL
;
810 /* if the token is an RTC one, update the time
811 and do the necessary checks */
812 if (token
->type
>= WPS_TOKENS_RTC_BEGIN
813 && token
->type
<= WPS_TOKENS_RTC_END
)
831 case WPS_TOKEN_CHARACTER
:
832 return &(token
->value
.c
);
834 case WPS_TOKEN_STRING
:
835 return data
->strings
[token
->value
.i
];
837 case WPS_TOKEN_TRACK_TIME_ELAPSED
:
838 format_time(buf
, buf_size
,
839 id3
->elapsed
+ state
->ff_rewind_count
);
842 case WPS_TOKEN_TRACK_TIME_REMAINING
:
843 format_time(buf
, buf_size
,
844 id3
->length
- id3
->elapsed
-
845 state
->ff_rewind_count
);
848 case WPS_TOKEN_TRACK_LENGTH
:
849 format_time(buf
, buf_size
, id3
->length
);
852 case WPS_TOKEN_PLAYLIST_ENTRIES
:
853 snprintf(buf
, buf_size
, "%d", playlist_amount());
856 case WPS_TOKEN_PLAYLIST_NAME
:
857 return playlist_name(NULL
, buf
, buf_size
);
859 case WPS_TOKEN_PLAYLIST_POSITION
:
860 snprintf(buf
, buf_size
, "%d", playlist_get_display_index());
863 case WPS_TOKEN_PLAYLIST_SHUFFLE
:
864 if ( global_settings
.playlist_shuffle
)
870 case WPS_TOKEN_VOLUME
:
871 snprintf(buf
, buf_size
, "%d", global_settings
.volume
);
874 if (global_settings
.volume
== sound_min(SOUND_VOLUME
))
878 else if (global_settings
.volume
== 0)
882 else if (global_settings
.volume
> 0)
888 *intval
= (limit
- 3) * (global_settings
.volume
889 - sound_min(SOUND_VOLUME
) - 1)
890 / (-1 - sound_min(SOUND_VOLUME
)) + 2;
895 case WPS_TOKEN_TRACK_ELAPSED_PERCENT
:
896 if (id3
->length
<= 0)
901 *intval
= limit
* (id3
->elapsed
+ state
->ff_rewind_count
)
904 snprintf(buf
, buf_size
, "%d",
905 100*(id3
->elapsed
+ state
->ff_rewind_count
) / id3
->length
);
908 case WPS_TOKEN_METADATA_ARTIST
:
911 case WPS_TOKEN_METADATA_COMPOSER
:
912 return id3
->composer
;
914 case WPS_TOKEN_METADATA_ALBUM
:
917 case WPS_TOKEN_METADATA_ALBUM_ARTIST
:
918 return id3
->albumartist
;
920 case WPS_TOKEN_METADATA_GROUPING
:
921 return id3
->grouping
;
923 case WPS_TOKEN_METADATA_GENRE
:
924 return id3
->genre_string
;
926 case WPS_TOKEN_METADATA_DISC_NUMBER
:
927 if (id3
->disc_string
)
928 return id3
->disc_string
;
930 snprintf(buf
, buf_size
, "%d", id3
->discnum
);
935 case WPS_TOKEN_METADATA_TRACK_NUMBER
:
936 if (id3
->track_string
)
937 return id3
->track_string
;
940 snprintf(buf
, buf_size
, "%d", id3
->tracknum
);
945 case WPS_TOKEN_METADATA_TRACK_TITLE
:
948 case WPS_TOKEN_METADATA_VERSION
:
949 switch (id3
->id3version
)
970 case WPS_TOKEN_METADATA_YEAR
:
971 if( id3
->year_string
)
972 return id3
->year_string
;
975 snprintf(buf
, buf_size
, "%d", id3
->year
);
980 case WPS_TOKEN_METADATA_COMMENT
:
984 case WPS_TOKEN_ALBUMART_DISPLAY
:
985 draw_album_art(gwps
, audio_current_aa_hid(), false);
988 case WPS_TOKEN_ALBUMART_FOUND
:
989 if (audio_current_aa_hid() >= 0) {
995 case WPS_TOKEN_FILE_BITRATE
:
997 snprintf(buf
, buf_size
, "%d", id3
->bitrate
);
1002 case WPS_TOKEN_FILE_CODEC
:
1005 if(id3
->codectype
== AFMT_UNKNOWN
)
1006 *intval
= AFMT_NUM_CODECS
;
1008 *intval
= id3
->codectype
;
1010 return get_codectype(id3
);
1012 case WPS_TOKEN_FILE_FREQUENCY
:
1013 snprintf(buf
, buf_size
, "%ld", id3
->frequency
);
1016 case WPS_TOKEN_FILE_FREQUENCY_KHZ
:
1017 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1018 if ((id3
->frequency
% 1000) < 100)
1019 snprintf(buf
, buf_size
, "%ld", id3
->frequency
/ 1000);
1021 snprintf(buf
, buf_size
, "%ld.%d",
1022 id3
->frequency
/ 1000,
1023 (id3
->frequency
% 1000) / 100);
1026 case WPS_TOKEN_FILE_NAME
:
1027 if (get_dir(buf
, buf_size
, id3
->path
, 0)) {
1028 /* Remove extension */
1029 char* sep
= strrchr(buf
, '.');
1039 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION
:
1040 return get_dir(buf
, buf_size
, id3
->path
, 0);
1042 case WPS_TOKEN_FILE_PATH
:
1045 case WPS_TOKEN_FILE_SIZE
:
1046 snprintf(buf
, buf_size
, "%ld", id3
->filesize
/ 1024);
1049 case WPS_TOKEN_FILE_VBR
:
1050 return id3
->vbr
? "(avg)" : NULL
;
1052 case WPS_TOKEN_FILE_DIRECTORY
:
1053 return get_dir(buf
, buf_size
, id3
->path
, token
->value
.i
);
1055 case WPS_TOKEN_BATTERY_PERCENT
:
1057 int l
= battery_level();
1061 limit
= MAX(limit
, 2);
1063 /* First enum is used for "unknown level". */
1064 *intval
= (limit
- 1) * l
/ 100 + 2;
1071 snprintf(buf
, buf_size
, "%d", l
);
1078 case WPS_TOKEN_BATTERY_VOLTS
:
1080 unsigned int v
= battery_voltage();
1081 snprintf(buf
, buf_size
, "%d.%02d", v
/ 1000, (v
% 1000) / 10);
1085 case WPS_TOKEN_BATTERY_TIME
:
1087 int t
= battery_time();
1089 snprintf(buf
, buf_size
, "%dh %dm", t
/ 60, t
% 60);
1096 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED
:
1098 if(charger_input_state
==CHARGER
)
1104 #if CONFIG_CHARGING >= CHARGING_MONITOR
1105 case WPS_TOKEN_BATTERY_CHARGING
:
1107 if (charge_state
== CHARGING
|| charge_state
== TOPOFF
) {
1114 case WPS_TOKEN_BATTERY_SLEEPTIME
:
1116 if (get_sleep_timer() == 0)
1120 format_time(buf
, buf_size
, get_sleep_timer() * 1000);
1125 case WPS_TOKEN_PLAYBACK_STATUS
:
1127 int status
= audio_status();
1129 if (status
== AUDIO_STATUS_PLAY
)
1131 if (wps_fading_out
||
1132 (status
& AUDIO_STATUS_PAUSE
&& !status_get_ffmode()))
1134 if (status_get_ffmode() == STATUS_FASTFORWARD
)
1136 if (status_get_ffmode() == STATUS_FASTBACKWARD
)
1143 snprintf(buf
, buf_size
, "%d", mode
-1);
1147 case WPS_TOKEN_REPEAT_MODE
:
1149 *intval
= global_settings
.repeat_mode
+ 1;
1150 snprintf(buf
, buf_size
, "%d", global_settings
.repeat_mode
);
1152 case WPS_TOKEN_RTC_12HOUR_CFG
:
1154 *intval
= global_settings
.timeformat
+ 1;
1155 snprintf(buf
, buf_size
, "%d", global_settings
.timeformat
);
1158 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1159 /* d: day of month (01..31) */
1160 snprintf(buf
, buf_size
, "%02d", tm
->tm_mday
);
1163 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1164 /* e: day of month, blank padded ( 1..31) */
1165 snprintf(buf
, buf_size
, "%2d", tm
->tm_mday
);
1168 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1169 /* H: hour (00..23) */
1170 snprintf(buf
, buf_size
, "%02d", tm
->tm_hour
);
1173 case WPS_TOKEN_RTC_HOUR_24
:
1174 /* k: hour ( 0..23) */
1175 snprintf(buf
, buf_size
, "%2d", tm
->tm_hour
);
1178 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1179 /* I: hour (01..12) */
1180 snprintf(buf
, buf_size
, "%02d",
1181 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1184 case WPS_TOKEN_RTC_HOUR_12
:
1185 /* l: hour ( 1..12) */
1186 snprintf(buf
, buf_size
, "%2d",
1187 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1190 case WPS_TOKEN_RTC_MONTH
:
1191 /* m: month (01..12) */
1193 *intval
= tm
->tm_mon
+ 1;
1194 snprintf(buf
, buf_size
, "%02d", tm
->tm_mon
+ 1);
1197 case WPS_TOKEN_RTC_MINUTE
:
1198 /* M: minute (00..59) */
1199 snprintf(buf
, buf_size
, "%02d", tm
->tm_min
);
1202 case WPS_TOKEN_RTC_SECOND
:
1203 /* S: second (00..59) */
1204 snprintf(buf
, buf_size
, "%02d", tm
->tm_sec
);
1207 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1208 /* y: last two digits of year (00..99) */
1209 snprintf(buf
, buf_size
, "%02d", tm
->tm_year
% 100);
1212 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1213 /* Y: year (1970...) */
1214 snprintf(buf
, buf_size
, "%04d", tm
->tm_year
+ 1900);
1217 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1218 /* p: upper case AM or PM indicator */
1219 return tm
->tm_hour
/12 == 0 ? "AM" : "PM";
1221 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1222 /* P: lower case am or pm indicator */
1223 return tm
->tm_hour
/12 == 0 ? "am" : "pm";
1225 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1226 /* a: abbreviated weekday name (Sun..Sat) */
1227 return str(LANG_WEEKDAY_SUNDAY
+ tm
->tm_wday
);
1229 case WPS_TOKEN_RTC_MONTH_NAME
:
1230 /* b: abbreviated month name (Jan..Dec) */
1231 return str(LANG_MONTH_JANUARY
+ tm
->tm_mon
);
1233 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1234 /* u: day of week (1..7); 1 is Monday */
1236 *intval
= (tm
->tm_wday
== 0) ? 7 : tm
->tm_wday
;
1237 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
+ 1);
1240 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1241 /* w: day of week (0..6); 0 is Sunday */
1243 *intval
= tm
->tm_wday
+ 1;
1244 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
);
1247 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1248 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1249 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1250 case WPS_TOKEN_RTC_HOUR_24
:
1251 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1252 case WPS_TOKEN_RTC_HOUR_12
:
1253 case WPS_TOKEN_RTC_MONTH
:
1254 case WPS_TOKEN_RTC_MINUTE
:
1255 case WPS_TOKEN_RTC_SECOND
:
1256 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1257 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1258 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1260 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1262 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1263 case WPS_TOKEN_RTC_MONTH_NAME
:
1265 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1266 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1270 #ifdef HAVE_LCD_CHARCELLS
1271 case WPS_TOKEN_PROGRESSBAR
:
1273 char *end
= utf8encode(data
->wps_progress_pat
[0], buf
);
1278 case WPS_TOKEN_PLAYER_PROGRESSBAR
:
1281 /* we need 11 characters (full line) for
1283 strncpy(buf
, " ", buf_size
);
1287 /* Tell the user if we have an OldPlayer */
1288 strncpy(buf
, " <Old LCD> ", buf_size
);
1293 #ifdef HAVE_TAGCACHE
1294 case WPS_TOKEN_DATABASE_PLAYCOUNT
:
1296 *intval
= id3
->playcount
+ 1;
1298 snprintf(buf
, buf_size
, "%ld", id3
->playcount
);
1301 case WPS_TOKEN_DATABASE_RATING
:
1303 *intval
= id3
->rating
+ 1;
1305 snprintf(buf
, buf_size
, "%d", id3
->rating
);
1308 case WPS_TOKEN_DATABASE_AUTOSCORE
:
1310 *intval
= id3
->score
+ 1;
1312 snprintf(buf
, buf_size
, "%d", id3
->score
);
1316 #if (CONFIG_CODEC == SWCODEC)
1317 case WPS_TOKEN_CROSSFADE
:
1319 *intval
= global_settings
.crossfade
+ 1;
1320 snprintf(buf
, buf_size
, "%d", global_settings
.crossfade
);
1323 case WPS_TOKEN_REPLAYGAIN
:
1327 if (global_settings
.replaygain
== 0)
1332 get_replaygain_mode(id3
->track_gain_string
!= NULL
,
1333 id3
->album_gain_string
!= NULL
);
1335 val
= 6; /* no tag */
1339 if (global_settings
.replaygain_type
== REPLAYGAIN_SHUFFLE
)
1354 strncpy(buf
, id3
->track_gain_string
, buf_size
);
1358 strncpy(buf
, id3
->album_gain_string
, buf_size
);
1363 #endif /* (CONFIG_CODEC == SWCODEC) */
1365 #if (CONFIG_CODEC != MAS3507D)
1366 case WPS_TOKEN_SOUND_PITCH
:
1368 int val
= sound_get_pitch();
1369 snprintf(buf
, buf_size
, "%d.%d",
1370 val
/ 10, val
% 10);
1375 case WPS_TOKEN_MAIN_HOLD
:
1376 #ifdef HAS_BUTTON_HOLD
1379 if (is_keys_locked())
1380 #endif /*hold switch or softlock*/
1385 #ifdef HAS_REMOTE_BUTTON_HOLD
1386 case WPS_TOKEN_REMOTE_HOLD
:
1387 if (remote_button_hold())
1393 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1394 case WPS_TOKEN_VLED_HDD
:
1400 case WPS_TOKEN_BUTTON_VOLUME
:
1401 if (data
->button_time_volume
&&
1402 TIME_BEFORE(current_tick
, data
->button_time_volume
+
1403 token
->value
.i
* TIMEOUT_UNIT
))
1407 case WPS_TOKEN_SETTING
:
1411 /* Handle contionals */
1412 const struct settings_list
*s
= settings
+token
->value
.i
;
1413 switch (s
->flags
&F_T_MASK
)
1418 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1419 /* shouldn't overflow since colors are stored
1421 * but this is pretty useless anyway */
1422 *intval
= *(int*)s
->setting
+ 1;
1423 else if (s
->cfg_vals
== NULL
)
1424 /* %?St|name|<1st choice|2nd choice|...> */
1425 *intval
= (*(int*)s
->setting
-s
->int_setting
->min
)
1426 /s
->int_setting
->step
+ 1;
1428 /* %?St|name|<1st choice|2nd choice|...> */
1429 /* Not sure about this one. cfg_name/vals are
1430 * indexed from 0 right? */
1431 *intval
= *(int*)s
->setting
+ 1;
1434 /* %?St|name|<if true|if false> */
1435 *intval
= *(bool*)s
->setting
?1:2;
1438 /* %?St|name|<if non empty string|if empty>
1439 * The string's emptyness discards the setting's
1440 * prefix and suffix */
1441 *intval
= ((char*)s
->setting
)[0]?1:2;
1444 /* This shouldn't happen ... but you never know */
1449 cfg_to_string(token
->value
.i
,buf
,buf_size
);
1458 /* Return the index to the end token for the conditional token at index.
1459 The conditional token can be either a start token or a separator
1460 (i.e. option) token.
1462 static int find_conditional_end(struct wps_data
*data
, int index
)
1465 while (data
->tokens
[ret
].type
!= WPS_TOKEN_CONDITIONAL_END
)
1466 ret
= data
->tokens
[ret
].value
.i
;
1468 /* ret now is the index to the end token for the conditional. */
1472 /* Evaluate the conditional that is at *token_index and return whether a skip
1473 has ocurred. *token_index is updated with the new position.
1475 static bool evaluate_conditional(struct gui_wps
*gwps
, int *token_index
)
1480 struct wps_data
*data
= gwps
->data
;
1483 int cond_index
= *token_index
;
1486 unsigned char num_options
= data
->tokens
[cond_index
].value
.i
& 0xFF;
1487 unsigned char prev_val
= (data
->tokens
[cond_index
].value
.i
& 0xFF00) >> 8;
1489 /* treat ?xx<true> constructs as if they had 2 options. */
1490 if (num_options
< 2)
1493 int intval
= num_options
;
1494 /* get_token_value needs to know the number of options in the enum */
1495 value
= get_token_value(gwps
, &data
->tokens
[cond_index
+ 1],
1496 result
, sizeof(result
), &intval
);
1498 /* intval is now the number of the enum option we want to read,
1499 starting from 1. If intval is -1, we check if value is empty. */
1501 intval
= (value
&& *value
) ? 1 : num_options
;
1502 else if (intval
> num_options
|| intval
< 1)
1503 intval
= num_options
;
1505 data
->tokens
[cond_index
].value
.i
= (intval
<< 8) + num_options
;
1507 /* skip to the appropriate enum case */
1508 int next
= cond_index
+ 2;
1509 for (i
= 1; i
< intval
; i
++)
1511 next
= data
->tokens
[next
].value
.i
;
1513 *token_index
= next
;
1515 if (prev_val
== intval
)
1517 /* Same conditional case as previously. Return without clearing the
1522 cond_end
= find_conditional_end(data
, cond_index
+ 2);
1523 for (i
= cond_index
+ 3; i
< cond_end
; i
++)
1525 #ifdef HAVE_LCD_BITMAP
1526 /* clear all pictures in the conditional and nested ones */
1527 if (data
->tokens
[i
].type
== WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
)
1528 clear_image_pos(gwps
, data
->tokens
[i
].value
.i
& 0xFF);
1530 #ifdef HAVE_ALBUMART
1531 if (data
->tokens
[i
].type
== WPS_TOKEN_ALBUMART_DISPLAY
)
1532 draw_album_art(gwps
, audio_current_aa_hid(), true);
1539 /* Read a (sub)line to the given alignment format buffer.
1540 linebuf is the buffer where the data is actually stored.
1541 align is the alignment format that'll be used to display the text.
1542 The return value indicates whether the line needs to be updated.
1544 static bool get_line(struct gui_wps
*gwps
,
1545 int line
, int subline
,
1546 struct align_pos
*align
,
1550 struct wps_data
*data
= gwps
->data
;
1553 char *buf
= linebuf
; /* will always point to the writing position */
1554 char *linebuf_end
= linebuf
+ linebuf_size
- 1;
1555 int i
, last_token_idx
;
1556 bool update
= false;
1558 /* alignment-related variables */
1560 char* cur_align_start
;
1561 cur_align_start
= buf
;
1562 cur_align
= WPS_ALIGN_LEFT
;
1564 align
->center
= NULL
;
1565 align
->right
= NULL
;
1567 /* Process all tokens of the desired subline */
1568 last_token_idx
= wps_last_token_index(data
, line
, subline
);
1569 for (i
= wps_first_token_index(data
, line
, subline
);
1570 i
<= last_token_idx
; i
++)
1572 switch(data
->tokens
[i
].type
)
1574 case WPS_TOKEN_CONDITIONAL
:
1575 /* place ourselves in the right conditional case */
1576 update
|= evaluate_conditional(gwps
, &i
);
1579 case WPS_TOKEN_CONDITIONAL_OPTION
:
1580 /* we've finished in the curent conditional case,
1581 skip to the end of the conditional structure */
1582 i
= find_conditional_end(data
, i
);
1585 #ifdef HAVE_LCD_BITMAP
1586 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
:
1588 struct gui_img
*img
= data
->img
;
1589 int n
= data
->tokens
[i
].value
.i
& 0xFF;
1590 int subimage
= data
->tokens
[i
].value
.i
>> 8;
1592 if (n
>= 0 && n
< MAX_IMAGES
&& img
[n
].loaded
)
1593 img
[n
].display
= subimage
;
1598 case WPS_TOKEN_ALIGN_LEFT
:
1599 case WPS_TOKEN_ALIGN_CENTER
:
1600 case WPS_TOKEN_ALIGN_RIGHT
:
1601 /* remember where the current aligned text started */
1604 case WPS_ALIGN_LEFT
:
1605 align
->left
= cur_align_start
;
1608 case WPS_ALIGN_CENTER
:
1609 align
->center
= cur_align_start
;
1612 case WPS_ALIGN_RIGHT
:
1613 align
->right
= cur_align_start
;
1616 /* start a new alignment */
1617 switch (data
->tokens
[i
].type
)
1619 case WPS_TOKEN_ALIGN_LEFT
:
1620 cur_align
= WPS_ALIGN_LEFT
;
1622 case WPS_TOKEN_ALIGN_CENTER
:
1623 cur_align
= WPS_ALIGN_CENTER
;
1625 case WPS_TOKEN_ALIGN_RIGHT
:
1626 cur_align
= WPS_ALIGN_RIGHT
;
1632 cur_align_start
= buf
;
1634 case WPS_VIEWPORT_ENABLE
:
1636 char label
= data
->tokens
[i
].value
.i
;
1638 char temp
= VP_DRAW_HIDEABLE
;
1639 for(j
=0;j
<data
->num_viewports
;j
++)
1641 temp
= VP_DRAW_HIDEABLE
;
1642 if ((data
->viewports
[j
].hidden_flags
&VP_DRAW_HIDEABLE
) &&
1643 (data
->viewports
[j
].label
== label
))
1645 if (data
->viewports
[j
].hidden_flags
&VP_DRAW_WASHIDDEN
)
1646 temp
|= VP_DRAW_WASHIDDEN
;
1647 data
->viewports
[j
].hidden_flags
= temp
;
1654 /* get the value of the tag and copy it to the buffer */
1655 const char *value
= get_token_value(gwps
, &data
->tokens
[i
],
1656 temp_buf
, sizeof(temp_buf
), NULL
);
1660 while (*value
&& (buf
< linebuf_end
))
1668 /* close the current alignment */
1671 case WPS_ALIGN_LEFT
:
1672 align
->left
= cur_align_start
;
1675 case WPS_ALIGN_CENTER
:
1676 align
->center
= cur_align_start
;
1679 case WPS_ALIGN_RIGHT
:
1680 align
->right
= cur_align_start
;
1687 static void get_subline_timeout(struct gui_wps
*gwps
, int line
, int subline
)
1689 struct wps_data
*data
= gwps
->data
;
1691 int subline_idx
= wps_subline_index(data
, line
, subline
);
1692 int last_token_idx
= wps_last_token_index(data
, line
, subline
);
1694 data
->sublines
[subline_idx
].time_mult
= DEFAULT_SUBLINE_TIME_MULTIPLIER
;
1696 for (i
= wps_first_token_index(data
, line
, subline
);
1697 i
<= last_token_idx
; i
++)
1699 switch(data
->tokens
[i
].type
)
1701 case WPS_TOKEN_CONDITIONAL
:
1702 /* place ourselves in the right conditional case */
1703 evaluate_conditional(gwps
, &i
);
1706 case WPS_TOKEN_CONDITIONAL_OPTION
:
1707 /* we've finished in the curent conditional case,
1708 skip to the end of the conditional structure */
1709 i
= find_conditional_end(data
, i
);
1712 case WPS_TOKEN_SUBLINE_TIMEOUT
:
1713 data
->sublines
[subline_idx
].time_mult
= data
->tokens
[i
].value
.i
;
1722 /* Calculates which subline should be displayed for the specified line
1723 Returns true iff the subline must be refreshed */
1724 static bool update_curr_subline(struct gui_wps
*gwps
, int line
)
1726 struct wps_data
*data
= gwps
->data
;
1728 int search
, search_start
, num_sublines
;
1730 bool new_subline_refresh
;
1731 bool only_one_subline
;
1733 num_sublines
= data
->lines
[line
].num_sublines
;
1734 reset_subline
= (data
->lines
[line
].curr_subline
== SUBLINE_RESET
);
1735 new_subline_refresh
= false;
1736 only_one_subline
= false;
1738 /* if time to advance to next sub-line */
1739 if (TIME_AFTER(current_tick
, data
->lines
[line
].subline_expire_time
- 1) ||
1742 /* search all sublines until the next subline with time > 0
1743 is found or we get back to the subline we started with */
1747 search_start
= data
->lines
[line
].curr_subline
;
1749 for (search
= 0; search
< num_sublines
; search
++)
1751 data
->lines
[line
].curr_subline
++;
1753 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1754 if (data
->lines
[line
].curr_subline
== num_sublines
)
1756 if (data
->lines
[line
].curr_subline
== 1)
1757 only_one_subline
= true;
1758 data
->lines
[line
].curr_subline
= 0;
1761 /* if back where we started after search or
1762 only one subline is defined on the line */
1763 if (((search
> 0) &&
1764 (data
->lines
[line
].curr_subline
== search_start
)) ||
1767 /* no other subline with a time > 0 exists */
1768 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1770 data
->lines
[line
].subline_expire_time
) + 100 * HZ
;
1775 /* get initial time multiplier for this subline */
1776 get_subline_timeout(gwps
, line
, data
->lines
[line
].curr_subline
);
1778 int subline_idx
= wps_subline_index(data
, line
,
1779 data
->lines
[line
].curr_subline
);
1781 /* only use this subline if subline time > 0 */
1782 if (data
->sublines
[subline_idx
].time_mult
> 0)
1784 new_subline_refresh
= true;
1785 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1786 current_tick
: data
->lines
[line
].subline_expire_time
) +
1787 TIMEOUT_UNIT
*data
->sublines
[subline_idx
].time_mult
;
1794 return new_subline_refresh
;
1797 /* Display a line appropriately according to its alignment format.
1798 format_align contains the text, separated between left, center and right.
1799 line is the index of the line on the screen.
1800 scroll indicates whether the line is a scrolling one or not.
1802 static void write_line(struct screen
*display
,
1803 struct align_pos
*format_align
,
1808 int left_width
= 0, left_xpos
;
1809 int center_width
= 0, center_xpos
;
1810 int right_width
= 0, right_xpos
;
1816 /* calculate different string sizes and positions */
1817 display
->getstringsize((unsigned char *)" ", &space_width
, &string_height
);
1818 if (format_align
->left
!= 0) {
1819 display
->getstringsize((unsigned char *)format_align
->left
,
1820 &left_width
, &string_height
);
1823 if (format_align
->right
!= 0) {
1824 display
->getstringsize((unsigned char *)format_align
->right
,
1825 &right_width
, &string_height
);
1828 if (format_align
->center
!= 0) {
1829 display
->getstringsize((unsigned char *)format_align
->center
,
1830 ¢er_width
, &string_height
);
1834 right_xpos
= (display
->getwidth() - right_width
);
1835 center_xpos
= (display
->getwidth() + left_xpos
- center_width
) / 2;
1837 scroll_width
= display
->getwidth() - left_xpos
;
1839 /* Checks for overlapping strings.
1840 If needed the overlapping strings will be merged, separated by a
1843 /* CASE 1: left and centered string overlap */
1844 /* there is a left string, need to merge left and center */
1845 if ((left_width
!= 0 && center_width
!= 0) &&
1846 (left_xpos
+ left_width
+ space_width
> center_xpos
)) {
1847 /* replace the former separator '\0' of left and
1848 center string with a space */
1849 *(--format_align
->center
) = ' ';
1850 /* calculate the new width and position of the merged string */
1851 left_width
= left_width
+ space_width
+ center_width
;
1852 /* there is no centered string anymore */
1855 /* there is no left string, move center to left */
1856 if ((left_width
== 0 && center_width
!= 0) &&
1857 (left_xpos
+ left_width
> center_xpos
)) {
1858 /* move the center string to the left string */
1859 format_align
->left
= format_align
->center
;
1860 /* calculate the new width and position of the string */
1861 left_width
= center_width
;
1862 /* there is no centered string anymore */
1866 /* CASE 2: centered and right string overlap */
1867 /* there is a right string, need to merge center and right */
1868 if ((center_width
!= 0 && right_width
!= 0) &&
1869 (center_xpos
+ center_width
+ space_width
> right_xpos
)) {
1870 /* replace the former separator '\0' of center and
1871 right string with a space */
1872 *(--format_align
->right
) = ' ';
1873 /* move the center string to the right after merge */
1874 format_align
->right
= format_align
->center
;
1875 /* calculate the new width and position of the merged string */
1876 right_width
= center_width
+ space_width
+ right_width
;
1877 right_xpos
= (display
->getwidth() - right_width
);
1878 /* there is no centered string anymore */
1881 /* there is no right string, move center to right */
1882 if ((center_width
!= 0 && right_width
== 0) &&
1883 (center_xpos
+ center_width
> right_xpos
)) {
1884 /* move the center string to the right string */
1885 format_align
->right
= format_align
->center
;
1886 /* calculate the new width and position of the string */
1887 right_width
= center_width
;
1888 right_xpos
= (display
->getwidth() - right_width
);
1889 /* there is no centered string anymore */
1893 /* CASE 3: left and right overlap
1894 There is no center string anymore, either there never
1895 was one or it has been merged in case 1 or 2 */
1896 /* there is a left string, need to merge left and right */
1897 if ((left_width
!= 0 && center_width
== 0 && right_width
!= 0) &&
1898 (left_xpos
+ left_width
+ space_width
> right_xpos
)) {
1899 /* replace the former separator '\0' of left and
1900 right string with a space */
1901 *(--format_align
->right
) = ' ';
1902 /* calculate the new width and position of the string */
1903 left_width
= left_width
+ space_width
+ right_width
;
1904 /* there is no right string anymore */
1907 /* there is no left string, move right to left */
1908 if ((left_width
== 0 && center_width
== 0 && right_width
!= 0) &&
1909 (left_width
> right_xpos
)) {
1910 /* move the right string to the left string */
1911 format_align
->left
= format_align
->right
;
1912 /* calculate the new width and position of the string */
1913 left_width
= right_width
;
1914 /* there is no right string anymore */
1918 ypos
= (line
* string_height
);
1921 if (scroll
&& ((left_width
> scroll_width
) ||
1922 (center_width
> scroll_width
) ||
1923 (right_width
> scroll_width
)))
1925 display
->puts_scroll(0, line
,
1926 (unsigned char *)format_align
->left
);
1930 #ifdef HAVE_LCD_BITMAP
1931 /* clear the line first */
1932 display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1933 display
->fillrect(left_xpos
, ypos
, display
->getwidth(), string_height
);
1934 display
->set_drawmode(DRMODE_SOLID
);
1937 /* Nasty hack: we output an empty scrolling string,
1938 which will reset the scroller for that line */
1939 display
->puts_scroll(0, line
, (unsigned char *)"");
1941 /* print aligned strings */
1942 if (left_width
!= 0)
1944 display
->putsxy(left_xpos
, ypos
,
1945 (unsigned char *)format_align
->left
);
1947 if (center_width
!= 0)
1949 display
->putsxy(center_xpos
, ypos
,
1950 (unsigned char *)format_align
->center
);
1952 if (right_width
!= 0)
1954 display
->putsxy(right_xpos
, ypos
,
1955 (unsigned char *)format_align
->right
);
1960 /* Refresh the WPS according to refresh_mode. */
1961 bool gui_wps_refresh(struct gui_wps
*gwps
,
1963 unsigned char refresh_mode
)
1965 struct wps_data
*data
= gwps
->data
;
1966 struct screen
*display
= gwps
->display
;
1967 struct wps_state
*state
= gwps
->state
;
1969 if(!gwps
|| !data
|| !state
|| !display
)
1972 int v
, line
, i
, subline_idx
;
1973 unsigned char flags
;
1974 char linebuf
[MAX_PATH
];
1975 unsigned char vp_refresh_mode
;
1977 struct align_pos align
;
1979 align
.center
= NULL
;
1982 bool update_line
, new_subline_refresh
;
1984 #ifdef HAVE_LCD_BITMAP
1985 gui_wps_statusbar_draw(gwps
, true);
1987 /* to find out wether the peak meter is enabled we
1988 assume it wasn't until we find a line that contains
1989 the peak meter. We can't use peak_meter_enabled itself
1990 because that would mean to turn off the meter thread
1991 temporarily. (That shouldn't matter unless yield
1992 or sleep is called but who knows...)
1994 bool enable_pm
= false;
1998 /* reset to first subline if refresh all flag is set */
1999 if (refresh_mode
== WPS_REFRESH_ALL
)
2001 display
->set_viewport(&data
->viewports
[0].vp
);
2002 display
->clear_viewport();
2004 for (i
= 0; i
<= data
->num_lines
; i
++)
2006 data
->lines
[i
].curr_subline
= SUBLINE_RESET
;
2010 #ifdef HAVE_LCD_CHARCELLS
2011 for (i
= 0; i
< 8; i
++)
2013 if (data
->wps_progress_pat
[i
] == 0)
2014 data
->wps_progress_pat
[i
] = display
->get_locked_pattern();
2020 display
->stop_scroll();
2024 state
->ff_rewind_count
= ffwd_offset
;
2026 /* disable any viewports which are conditionally displayed */
2027 for (v
= 0; v
< data
->num_viewports
; v
++)
2029 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDEABLE
)
2031 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
)
2032 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
2034 data
->viewports
[v
].hidden_flags
|= VP_DRAW_HIDDEN
;
2037 for (v
= 0; v
< data
->num_viewports
; v
++)
2039 display
->set_viewport(&data
->viewports
[v
].vp
);
2040 vp_refresh_mode
= refresh_mode
;
2042 #ifdef HAVE_LCD_BITMAP
2043 /* Set images to not to be displayed */
2044 for (i
= 0; i
< MAX_IMAGES
; i
++)
2046 data
->img
[i
].display
= -1;
2049 /* dont redraw the viewport if its disabled */
2050 if ((data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
))
2052 if (!(data
->viewports
[v
].hidden_flags
&VP_DRAW_WASHIDDEN
))
2053 display
->scroll_stop(&data
->viewports
[v
].vp
);
2054 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
2057 else if (((data
->viewports
[v
].hidden_flags
&
2058 (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
))
2059 == (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
)))
2061 vp_refresh_mode
= WPS_REFRESH_ALL
;
2062 data
->viewports
[v
].hidden_flags
= VP_DRAW_HIDEABLE
;
2064 if (vp_refresh_mode
== WPS_REFRESH_ALL
)
2066 display
->clear_viewport();
2069 for (line
= data
->viewports
[v
].first_line
;
2070 line
<= data
->viewports
[v
].last_line
; line
++)
2072 memset(linebuf
, 0, sizeof(linebuf
));
2073 update_line
= false;
2075 /* get current subline for the line */
2076 new_subline_refresh
= update_curr_subline(gwps
, line
);
2078 subline_idx
= wps_subline_index(data
, line
,
2079 data
->lines
[line
].curr_subline
);
2080 flags
= data
->sublines
[subline_idx
].line_type
;
2082 if (vp_refresh_mode
== WPS_REFRESH_ALL
|| (flags
& vp_refresh_mode
)
2083 || new_subline_refresh
)
2085 /* get_line tells us if we need to update the line */
2086 update_line
= get_line(gwps
, line
, data
->lines
[line
].curr_subline
,
2087 &align
, linebuf
, sizeof(linebuf
));
2089 #ifdef HAVE_LCD_BITMAP
2091 if (flags
& vp_refresh_mode
& WPS_REFRESH_PEAK_METER
)
2093 /* the peakmeter should be alone on its line */
2094 update_line
= false;
2096 int h
= font_get(data
->viewports
[v
].vp
.font
)->height
;
2097 int peak_meter_y
= (line
- data
->viewports
[v
].first_line
)* h
;
2099 /* The user might decide to have the peak meter in the last
2100 line so that it is only displayed if no status bar is
2101 visible. If so we neither want do draw nor enable the
2103 if (peak_meter_y
+ h
<= display
->getheight()) {
2104 /* found a line with a peak meter -> remember that we must
2107 peak_meter_enabled
= true;
2108 peak_meter_screen(gwps
->display
, 0, peak_meter_y
,
2109 MIN(h
, display
->getheight() - peak_meter_y
));
2113 peak_meter_enabled
= false;
2117 #else /* HAVE_LCD_CHARCELL */
2120 if (flags
& vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2122 if (data
->full_line_progressbar
)
2123 draw_player_fullbar(gwps
, linebuf
, sizeof(linebuf
));
2125 draw_player_progress(gwps
);
2130 /* conditionals clear the line which means if the %Vd is put into the default
2131 viewport there will be a blank line.
2132 To get around this we dont allow any actual drawing to happen in the
2133 deault vp if other vp's are defined */
2134 ((data
->num_viewports
>1 && v
!=0) || data
->num_viewports
== 1))
2136 if (flags
& WPS_REFRESH_SCROLL
)
2138 /* if the line is a scrolling one we don't want to update
2139 too often, so that it has the time to scroll */
2140 if ((vp_refresh_mode
& WPS_REFRESH_SCROLL
) || new_subline_refresh
)
2141 write_line(display
, &align
, line
- data
->viewports
[v
].first_line
, true);
2144 write_line(display
, &align
, line
- data
->viewports
[v
].first_line
, false);
2148 #ifdef HAVE_LCD_BITMAP
2150 if (vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2152 if (data
->viewports
[v
].pb
)
2153 draw_progressbar(gwps
, data
->viewports
[v
].pb
);
2155 /* Now display any images in this viewport */
2156 wps_display_images(gwps
, &data
->viewports
[v
].vp
);
2160 #ifdef HAVE_LCD_BITMAP
2161 data
->peak_meter_enabled
= enable_pm
;
2164 /* Restore the default viewport */
2165 display
->set_viewport(NULL
);
2169 #ifdef HAVE_BACKLIGHT
2170 if (global_settings
.caption_backlight
&& state
->id3
)
2172 /* turn on backlight n seconds before track ends, and turn it off n
2173 seconds into the new track. n == backlight_timeout, or 5s */
2174 int n
= global_settings
.backlight_timeout
* 1000;
2177 n
= 5000; /* use 5s if backlight is always on or off */
2179 if (((state
->id3
->elapsed
< 1000) ||
2180 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2181 (state
->paused
== false))
2185 #ifdef HAVE_REMOTE_LCD
2186 if (global_settings
.remote_caption_backlight
&& state
->id3
)
2188 /* turn on remote backlight n seconds before track ends, and turn it
2189 off n seconds into the new track. n == remote_backlight_timeout,
2191 int n
= global_settings
.remote_backlight_timeout
* 1000;
2194 n
= 5000; /* use 5s if backlight is always on or off */
2196 if (((state
->id3
->elapsed
< 1000) ||
2197 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2198 (state
->paused
== false))
2199 remote_backlight_on();