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 "rbunicode.h"
34 #include "powermgmt.h"
37 #ifdef HAVE_LCD_CHARCELLS
41 #include "mp3_playback.h"
42 #include "backlight.h"
46 #include "scrollbar.h"
49 #ifdef HAVE_LCD_BITMAP
50 #include "peakmeter.h"
59 #if CONFIG_CODEC == SWCODEC
64 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
65 /* 3% of 30min file == 54s step size */
66 #define MIN_FF_REWIND_STEP 500
68 /* draws the statusbar on the given wps-screen */
69 #ifdef HAVE_LCD_BITMAP
70 static void gui_wps_statusbar_draw(struct gui_wps
*wps
, bool force
)
72 bool draw
= global_settings
.statusbar
;
74 if (wps
->data
->wps_sb_tag
)
75 draw
= wps
->data
->show_sb_on_wps
;
78 gui_statusbar_draw(wps
->statusbar
, force
);
81 #define gui_wps_statusbar_draw(wps, force) \
82 gui_statusbar_draw((wps)->statusbar, (force))
86 /* fades the volume */
87 bool wps_fading_out
= false;
88 void fade(bool fade_in
, bool updatewps
)
90 int fp_global_vol
= global_settings
.volume
<< 8;
91 int fp_min_vol
= sound_min(SOUND_VOLUME
) << 8;
92 int fp_step
= (fp_global_vol
- fp_min_vol
) / 30;
94 wps_fading_out
= !fade_in
;
97 int fp_volume
= fp_min_vol
;
99 /* zero out the sound */
100 sound_set_volume(fp_min_vol
>> 8);
102 sleep(HZ
/10); /* let audio thread run */
105 while (fp_volume
< fp_global_vol
- fp_step
) {
106 fp_volume
+= fp_step
;
107 sound_set_volume(fp_volume
>> 8);
111 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_NON_STATIC
);
115 sound_set_volume(global_settings
.volume
);
119 int fp_volume
= fp_global_vol
;
121 while (fp_volume
> fp_min_vol
+ fp_step
) {
122 fp_volume
-= fp_step
;
123 sound_set_volume(fp_volume
>> 8);
127 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_NON_STATIC
);
132 wps_fading_out
= false;
133 #if CONFIG_CODEC != SWCODEC
135 /* let audio thread run and wait for the mas to run out of data */
136 while (!mp3_pause_done())
141 /* reset volume to what it was before the fade */
142 sound_set_volume(global_settings
.volume
);
146 /* return true if screen restore is needed
147 return false otherwise
149 bool update_onvol_change(struct gui_wps
* gwps
)
151 gui_wps_statusbar_draw(gwps
, false);
152 gui_wps_refresh(gwps
, 0, WPS_REFRESH_NON_STATIC
);
154 #ifdef HAVE_LCD_CHARCELLS
155 splashf(0, "Vol: %3d dB",
156 sound_val2phys(SOUND_VOLUME
, global_settings
.volume
));
162 void play_hop(int direction
)
164 if(!wps_state
.id3
|| !wps_state
.id3
->length
165 || global_settings
.skip_length
== 0)
167 #define STEP ((unsigned)global_settings.skip_length*1000)
169 && wps_state
.id3
->length
- wps_state
.id3
->elapsed
< STEP
+1000) {
170 #if CONFIG_CODEC == SWCODEC
171 if(global_settings
.beep
)
172 pcmbuf_beep(1000, 150, 1500*global_settings
.beep
);
176 if((direction
== -1 && wps_state
.id3
->elapsed
< STEP
))
177 wps_state
.id3
->elapsed
= 0;
179 wps_state
.id3
->elapsed
+= STEP
*direction
;
180 if((audio_status() & AUDIO_STATUS_PLAY
) && !wps_state
.paused
) {
181 #if (CONFIG_CODEC == SWCODEC)
182 audio_pre_ff_rewind();
187 audio_ff_rewind(wps_state
.id3
->elapsed
);
188 #if (CONFIG_CODEC != SWCODEC)
189 if (!wps_state
.paused
)
195 bool ffwd_rew(int button
)
197 unsigned int step
= 0; /* current ff/rewind step */
198 unsigned int max_step
= 0; /* maximum ff/rewind step */
199 int ff_rewind_count
= 0; /* current ff/rewind count (in ticks) */
200 int direction
= -1; /* forward=1 or backward=-1 */
204 const long ff_rw_accel
= (global_settings
.ff_rewind_accel
+ 3);
206 if (button
== ACTION_NONE
)
208 status_set_ffmode(0);
215 case ACTION_WPS_SEEKFWD
:
217 case ACTION_WPS_SEEKBACK
:
218 if (wps_state
.ff_rewind
)
222 /* fast forwarding, calc max step relative to end */
223 max_step
= (wps_state
.id3
->length
-
224 (wps_state
.id3
->elapsed
+
226 FF_REWIND_MAX_PERCENT
/ 100;
230 /* rewinding, calc max step relative to start */
231 max_step
= (wps_state
.id3
->elapsed
+ ff_rewind_count
) *
232 FF_REWIND_MAX_PERCENT
/ 100;
235 max_step
= MAX(max_step
, MIN_FF_REWIND_STEP
);
240 ff_rewind_count
+= step
* direction
;
242 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
243 step
+= step
>> ff_rw_accel
;
247 if ( (audio_status() & AUDIO_STATUS_PLAY
) &&
248 wps_state
.id3
&& wps_state
.id3
->length
)
250 if (!wps_state
.paused
)
251 #if (CONFIG_CODEC == SWCODEC)
252 audio_pre_ff_rewind();
256 #if CONFIG_KEYPAD == PLAYER_PAD
258 gui_wps
[i
].display
->stop_scroll();
261 status_set_ffmode(STATUS_FASTFORWARD
);
263 status_set_ffmode(STATUS_FASTBACKWARD
);
265 wps_state
.ff_rewind
= true;
267 step
= 1000 * global_settings
.ff_rewind_min_step
;
274 if ((wps_state
.id3
->elapsed
+ ff_rewind_count
) >
275 wps_state
.id3
->length
)
276 ff_rewind_count
= wps_state
.id3
->length
-
277 wps_state
.id3
->elapsed
;
280 if ((int)(wps_state
.id3
->elapsed
+ ff_rewind_count
) < 0)
281 ff_rewind_count
= -wps_state
.id3
->elapsed
;
285 gui_wps_refresh(&gui_wps
[i
],
286 (wps_state
.wps_time_countup
== false)?
287 ff_rewind_count
:-ff_rewind_count
,
288 WPS_REFRESH_PLAYER_PROGRESS
|
289 WPS_REFRESH_DYNAMIC
);
293 case ACTION_WPS_STOPSEEK
:
294 wps_state
.id3
->elapsed
= wps_state
.id3
->elapsed
+ff_rewind_count
;
295 audio_ff_rewind(wps_state
.id3
->elapsed
);
297 wps_state
.ff_rewind
= false;
298 status_set_ffmode(0);
299 #if (CONFIG_CODEC != SWCODEC)
300 if (!wps_state
.paused
)
303 #ifdef HAVE_LCD_CHARCELLS
310 if(default_event_handler(button
) == SYS_USB_CONNECTED
) {
311 status_set_ffmode(0);
318 button
= get_action(CONTEXT_WPS
|ALLOW_SOFTLOCK
,TIMEOUT_BLOCK
);
323 bool gui_wps_display(void)
326 if (!wps_state
.id3
&& !(audio_status() & AUDIO_STATUS_PLAY
))
328 global_status
.resume_index
= -1;
329 #ifdef HAVE_LCD_BITMAP
330 gui_syncstatusbar_draw(&statusbars
, true);
332 splash(HZ
, ID2P(LANG_END_PLAYLIST
));
339 /* Update the values in the first (default) viewport - in case the user
340 has modified the statusbar or colour settings */
341 #ifdef HAVE_LCD_BITMAP
343 if (gui_wps
[i
].display
->depth
> 1)
345 gui_wps
[i
].data
->viewports
[0].vp
.fg_pattern
= gui_wps
[i
].display
->get_foreground();
346 gui_wps
[i
].data
->viewports
[0].vp
.bg_pattern
= gui_wps
[i
].display
->get_background();
351 gui_wps
[i
].display
->clear_display();
352 if (!gui_wps
[i
].data
->wps_loaded
) {
353 if ( !gui_wps
[i
].data
->num_tokens
) {
354 /* set the default wps for the main-screen */
357 #ifdef HAVE_LCD_BITMAP
359 unload_wps_backdrop();
361 wps_data_load(gui_wps
[i
].data
,
363 "%s%?it<%?in<%in. |>%it|%fn>\n"
364 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
365 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
367 "%al%pc/%pt%ar[%pp:%pe]\n"
368 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
372 wps_data_load(gui_wps
[i
].data
,
374 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
375 "%pc%?ps<*|/>%pt\n", false);
379 /* set the default wps for the remote-screen */
382 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
383 unload_remote_wps_backdrop();
385 wps_data_load(gui_wps
[i
].data
,
387 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
388 "%s%?it<%?in<%in. |>%it|%fn>\n"
389 "%al%pc/%pt%ar[%pp:%pe]\n"
390 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
401 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_ALL
);
406 bool update(struct gui_wps
*gwps
)
408 bool track_changed
= audio_has_changed_track();
409 bool retcode
= false;
411 gwps
->state
->nid3
= audio_next_track();
414 gwps
->display
->stop_scroll();
415 gwps
->state
->id3
= audio_current_track();
417 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
418 && strcmp(gwps
->state
->id3
->path
, curr_cue
->audio_filename
))
420 /* the current cuesheet isn't the right one any more */
422 if (!strcmp(gwps
->state
->id3
->path
, temp_cue
->audio_filename
)) {
423 /* We have the new cuesheet in memory (temp_cue),
424 let's make it the current one ! */
425 memcpy(curr_cue
, temp_cue
, sizeof(struct cuesheet
));
428 /* We need to parse the new cuesheet */
430 char cuepath
[MAX_PATH
];
432 if (look_for_cuesheet_file(gwps
->state
->id3
->path
, cuepath
) &&
433 parse_cuesheet(cuepath
, curr_cue
))
435 gwps
->state
->id3
->cuesheet_type
= 1;
436 strcpy(curr_cue
->audio_filename
, gwps
->state
->id3
->path
);
440 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
443 if (gui_wps_display())
446 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
449 if (gwps
->state
->id3
)
451 strncpy(gwps
->state
->current_track_path
, gwps
->state
->id3
->path
,
452 sizeof(gwps
->state
->current_track_path
));
453 gwps
->state
->current_track_path
[sizeof(gwps
->state
->current_track_path
)-1] = '\0';
457 if (gwps
->state
->id3
)
459 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
460 && (gwps
->state
->id3
->elapsed
< curr_cue
->curr_track
->offset
461 || (curr_cue
->curr_track_idx
< curr_cue
->track_count
- 1
462 && gwps
->state
->id3
->elapsed
>= (curr_cue
->curr_track
+1)->offset
)))
464 /* We've changed tracks within the cuesheet :
465 we need to update the ID3 info and refresh the WPS */
467 cue_find_current_track(curr_cue
, gwps
->state
->id3
->elapsed
);
468 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
470 gwps
->display
->stop_scroll();
471 if (gui_wps_display())
474 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
477 gui_wps_refresh(gwps
, 0, WPS_REFRESH_NON_STATIC
);
480 gui_wps_statusbar_draw(gwps
, false);
486 void display_keylock_text(bool locked
)
490 gui_wps
[i
].display
->stop_scroll();
492 splash(HZ
, locked
? ID2P(LANG_KEYLOCK_ON
) : ID2P(LANG_KEYLOCK_OFF
));
495 #ifdef HAVE_LCD_BITMAP
497 static void draw_progressbar(struct gui_wps
*gwps
,
498 struct progressbar
*pb
)
500 struct screen
*display
= gwps
->display
;
501 struct wps_state
*state
= gwps
->state
;
502 if (pb
->have_bitmap_pb
)
503 gui_bitmap_scrollbar_draw(display
, pb
->bm
,
504 pb
->x
, pb
->y
, pb
->width
, pb
->bm
.height
,
505 state
->id3
->length
? state
->id3
->length
: 1, 0,
506 state
->id3
->length
? state
->id3
->elapsed
507 + state
->ff_rewind_count
: 0,
510 gui_scrollbar_draw(display
, pb
->x
, pb
->y
, pb
->width
, pb
->height
,
511 state
->id3
->length
? state
->id3
->length
: 1, 0,
512 state
->id3
->length
? state
->id3
->elapsed
513 + state
->ff_rewind_count
: 0,
515 #ifdef AB_REPEAT_ENABLE
516 if ( ab_repeat_mode_enabled() && state
->id3
->length
!= 0 )
517 ab_draw_markers(display
, state
->id3
->length
,
518 pb
->x
, pb
->x
+ pb
->width
, pb
->y
, pb
->height
);
521 if ( cuesheet_is_enabled() && state
->id3
->cuesheet_type
)
522 cue_draw_markers(display
, state
->id3
->length
,
523 pb
->x
, pb
->x
+ pb
->width
, pb
->y
+1, pb
->height
-2);
526 /* clears the area where the image was shown */
527 static void clear_image_pos(struct gui_wps
*gwps
, int n
)
531 struct wps_data
*data
= gwps
->data
;
532 gwps
->display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
533 gwps
->display
->fillrect(data
->img
[n
].x
, data
->img
[n
].y
,
534 data
->img
[n
].bm
.width
, data
->img
[n
].subimage_height
);
535 gwps
->display
->set_drawmode(DRMODE_SOLID
);
538 static void wps_draw_image(struct gui_wps
*gwps
, int n
, int subimage
)
540 struct screen
*display
= gwps
->display
;
541 struct wps_data
*data
= gwps
->data
;
542 if(data
->img
[n
].always_display
)
543 display
->set_drawmode(DRMODE_FG
);
545 display
->set_drawmode(DRMODE_SOLID
);
548 if(data
->img
[n
].bm
.format
== FORMAT_MONO
) {
550 display
->mono_bitmap_part(data
->img
[n
].bm
.data
,
551 0, data
->img
[n
].subimage_height
* subimage
,
552 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
553 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
554 data
->img
[n
].subimage_height
);
557 display
->transparent_bitmap_part((fb_data
*)data
->img
[n
].bm
.data
,
558 0, data
->img
[n
].subimage_height
* subimage
,
559 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
560 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
561 data
->img
[n
].subimage_height
);
566 static void wps_display_images(struct gui_wps
*gwps
, struct viewport
* vp
)
568 if(!gwps
|| !gwps
->data
|| !gwps
->display
)
572 struct wps_data
*data
= gwps
->data
;
573 struct screen
*display
= gwps
->display
;
575 for (n
= 0; n
< MAX_IMAGES
; n
++)
577 if (data
->img
[n
].loaded
)
579 if (data
->img
[n
].display
>= 0)
581 wps_draw_image(gwps
, n
, data
->img
[n
].display
);
582 } else if (data
->img
[n
].always_display
&& data
->img
[n
].vp
== vp
)
584 wps_draw_image(gwps
, n
, 0);
588 display
->set_drawmode(DRMODE_SOLID
);
591 #else /* HAVE_LCD_CHARCELL */
593 static bool draw_player_progress(struct gui_wps
*gwps
)
595 struct wps_state
*state
= gwps
->state
;
596 struct screen
*display
= gwps
->display
;
597 unsigned char progress_pattern
[7];
604 if (state
->id3
->length
)
605 pos
= 36 * (state
->id3
->elapsed
+ state
->ff_rewind_count
)
606 / state
->id3
->length
;
608 for (i
= 0; i
< 7; i
++, pos
-= 5)
611 progress_pattern
[i
] = 0x1f;
613 progress_pattern
[i
] = 0x00;
615 progress_pattern
[i
] = 0x1f >> pos
;
618 display
->define_pattern(gwps
->data
->wps_progress_pat
[0], progress_pattern
);
622 static void draw_player_fullbar(struct gui_wps
*gwps
, char* buf
, int buf_size
)
624 static const unsigned char numbers
[10][4] = {
625 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
626 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
627 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
628 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
629 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
630 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
631 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
632 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
633 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
634 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
637 struct wps_state
*state
= gwps
->state
;
638 struct screen
*display
= gwps
->display
;
639 struct wps_data
*data
= gwps
->data
;
640 unsigned char progress_pattern
[7];
649 if (!state
->id3
|| buf_size
< 34) /* worst case: 11x UTF-8 char + \0 */
652 time
= state
->id3
->elapsed
+ state
->ff_rewind_count
;
653 if (state
->id3
->length
)
654 pos
= 55 * time
/ state
->id3
->length
;
656 memset(timestr
, 0, sizeof(timestr
));
657 format_time(timestr
, sizeof(timestr
)-2, time
);
658 timestr
[strlen(timestr
)] = ':'; /* always safe */
660 for (i
= 0; i
< 11; i
++, pos
-= 5)
663 memset(progress_pattern
, 0, sizeof(progress_pattern
));
665 if ((digit
= timestr
[time_idx
]))
670 if (timestr
[time_idx
+ 1] == ':') /* ones, left aligned */
672 memcpy(progress_pattern
, numbers
[digit
], 4);
675 else /* tens, shifted right */
677 for (j
= 0; j
< 4; j
++)
678 progress_pattern
[j
] = numbers
[digit
][j
] >> 1;
680 if (time_idx
> 0) /* not the first group, add colon in front */
682 progress_pattern
[1] |= 0x10;
683 progress_pattern
[3] |= 0x10;
689 progress_pattern
[5] = progress_pattern
[6] = 0x1f;
692 if (pos
> 0 && pos
< 5)
695 progress_pattern
[5] = progress_pattern
[6] = (~0x1f >> pos
) & 0x1f;
698 if (softchar
&& pat_idx
< 8)
700 display
->define_pattern(data
->wps_progress_pat
[pat_idx
],
702 buf
= utf8encode(data
->wps_progress_pat
[pat_idx
], buf
);
706 buf
= utf8encode(' ', buf
);
708 buf
= utf8encode(0xe115, buf
); /* 2/7 _ */
713 #endif /* HAVE_LCD_CHARCELL */
715 static char* get_codectype(const struct mp3entry
* id3
)
717 if (id3
->codectype
< AFMT_NUM_CODECS
) {
718 return (char*)audio_formats
[id3
->codectype
].label
;
724 /* Extract a part from a path.
726 * buf - buffer extract part to.
727 * buf_size - size of buffer.
728 * path - path to extract from.
729 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
730 * parent of parent, etc.
732 * Returns buf if the desired level was found, NULL otherwise.
734 static char* get_dir(char* buf
, int buf_size
, const char* path
, int level
)
737 const char* last_sep
;
740 sep
= path
+ strlen(path
);
755 if (level
|| (last_sep
<= sep
))
758 len
= MIN(last_sep
- sep
, buf_size
- 1);
759 strncpy(buf
, sep
+ 1, len
);
764 /* Return the tag found at index i and write its value in buf.
765 The return value is buf if the tag had a value, or NULL if not.
767 intval is used with conditionals/enums: when this function is called,
768 intval should contain the number of options in the conditional/enum.
769 When this function returns, intval is -1 if the tag is non numeric or,
770 if the tag is numeric, *intval is the enum case we want to go to (between 1
771 and the original value of *intval, inclusive).
772 When not treating a conditional/enum, intval should be NULL.
774 static const char *get_token_value(struct gui_wps
*gwps
,
775 struct wps_token
*token
,
776 char *buf
, int buf_size
,
782 struct wps_data
*data
= gwps
->data
;
783 struct wps_state
*state
= gwps
->state
;
788 struct mp3entry
*id3
;
799 struct tm
* tm
= NULL
;
801 /* if the token is an RTC one, update the time
802 and do the necessary checks */
803 if (token
->type
>= WPS_TOKENS_RTC_BEGIN
804 && token
->type
<= WPS_TOKENS_RTC_END
)
822 case WPS_TOKEN_CHARACTER
:
823 return &(token
->value
.c
);
825 case WPS_TOKEN_STRING
:
826 return data
->strings
[token
->value
.i
];
828 case WPS_TOKEN_TRACK_TIME_ELAPSED
:
829 format_time(buf
, buf_size
,
830 id3
->elapsed
+ state
->ff_rewind_count
);
833 case WPS_TOKEN_TRACK_TIME_REMAINING
:
834 format_time(buf
, buf_size
,
835 id3
->length
- id3
->elapsed
-
836 state
->ff_rewind_count
);
839 case WPS_TOKEN_TRACK_LENGTH
:
840 format_time(buf
, buf_size
, id3
->length
);
843 case WPS_TOKEN_PLAYLIST_ENTRIES
:
844 snprintf(buf
, buf_size
, "%d", playlist_amount());
847 case WPS_TOKEN_PLAYLIST_NAME
:
848 return playlist_name(NULL
, buf
, buf_size
);
850 case WPS_TOKEN_PLAYLIST_POSITION
:
851 snprintf(buf
, buf_size
, "%d", playlist_get_display_index());
854 case WPS_TOKEN_PLAYLIST_SHUFFLE
:
855 if ( global_settings
.playlist_shuffle
)
861 case WPS_TOKEN_VOLUME
:
862 snprintf(buf
, buf_size
, "%d", global_settings
.volume
);
865 if (global_settings
.volume
== sound_min(SOUND_VOLUME
))
869 else if (global_settings
.volume
== 0)
873 else if (global_settings
.volume
> 0)
879 *intval
= (limit
- 3) * (global_settings
.volume
880 - sound_min(SOUND_VOLUME
) - 1)
881 / (-1 - sound_min(SOUND_VOLUME
)) + 2;
886 case WPS_TOKEN_TRACK_ELAPSED_PERCENT
:
887 if (id3
->length
<= 0)
892 *intval
= limit
* (id3
->elapsed
+ state
->ff_rewind_count
)
895 snprintf(buf
, buf_size
, "%d",
896 100*(id3
->elapsed
+ state
->ff_rewind_count
) / id3
->length
);
899 case WPS_TOKEN_METADATA_ARTIST
:
902 case WPS_TOKEN_METADATA_COMPOSER
:
903 return id3
->composer
;
905 case WPS_TOKEN_METADATA_ALBUM
:
908 case WPS_TOKEN_METADATA_ALBUM_ARTIST
:
909 return id3
->albumartist
;
911 case WPS_TOKEN_METADATA_GROUPING
:
912 return id3
->grouping
;
914 case WPS_TOKEN_METADATA_GENRE
:
915 return id3
->genre_string
;
917 case WPS_TOKEN_METADATA_DISC_NUMBER
:
918 if (id3
->disc_string
)
919 return id3
->disc_string
;
921 snprintf(buf
, buf_size
, "%d", id3
->discnum
);
926 case WPS_TOKEN_METADATA_TRACK_NUMBER
:
927 if (id3
->track_string
)
928 return id3
->track_string
;
931 snprintf(buf
, buf_size
, "%d", id3
->tracknum
);
936 case WPS_TOKEN_METADATA_TRACK_TITLE
:
939 case WPS_TOKEN_METADATA_VERSION
:
940 switch (id3
->id3version
)
961 case WPS_TOKEN_METADATA_YEAR
:
962 if( id3
->year_string
)
963 return id3
->year_string
;
966 snprintf(buf
, buf_size
, "%d", id3
->year
);
971 case WPS_TOKEN_METADATA_COMMENT
:
975 case WPS_TOKEN_ALBUMART_DISPLAY
:
976 draw_album_art(gwps
, audio_current_aa_hid(), false);
979 case WPS_TOKEN_ALBUMART_FOUND
:
980 if (audio_current_aa_hid() >= 0) {
986 case WPS_TOKEN_FILE_BITRATE
:
988 snprintf(buf
, buf_size
, "%d", id3
->bitrate
);
993 case WPS_TOKEN_FILE_CODEC
:
996 if(id3
->codectype
== AFMT_UNKNOWN
)
997 *intval
= AFMT_NUM_CODECS
;
999 *intval
= id3
->codectype
;
1001 return get_codectype(id3
);
1003 case WPS_TOKEN_FILE_FREQUENCY
:
1004 snprintf(buf
, buf_size
, "%ld", id3
->frequency
);
1007 case WPS_TOKEN_FILE_FREQUENCY_KHZ
:
1008 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1009 if ((id3
->frequency
% 1000) < 100)
1010 snprintf(buf
, buf_size
, "%ld", id3
->frequency
/ 1000);
1012 snprintf(buf
, buf_size
, "%ld.%d",
1013 id3
->frequency
/ 1000,
1014 (id3
->frequency
% 1000) / 100);
1017 case WPS_TOKEN_FILE_NAME
:
1018 if (get_dir(buf
, buf_size
, id3
->path
, 0)) {
1019 /* Remove extension */
1020 char* sep
= strrchr(buf
, '.');
1030 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION
:
1031 return get_dir(buf
, buf_size
, id3
->path
, 0);
1033 case WPS_TOKEN_FILE_PATH
:
1036 case WPS_TOKEN_FILE_SIZE
:
1037 snprintf(buf
, buf_size
, "%ld", id3
->filesize
/ 1024);
1040 case WPS_TOKEN_FILE_VBR
:
1041 return id3
->vbr
? "(avg)" : NULL
;
1043 case WPS_TOKEN_FILE_DIRECTORY
:
1044 return get_dir(buf
, buf_size
, id3
->path
, token
->value
.i
);
1046 case WPS_TOKEN_BATTERY_PERCENT
:
1048 int l
= battery_level();
1052 limit
= MAX(limit
, 2);
1054 /* First enum is used for "unknown level". */
1055 *intval
= (limit
- 1) * l
/ 100 + 2;
1062 snprintf(buf
, buf_size
, "%d", l
);
1069 case WPS_TOKEN_BATTERY_VOLTS
:
1071 unsigned int v
= battery_voltage();
1072 snprintf(buf
, buf_size
, "%d.%02d", v
/ 1000, (v
% 1000) / 10);
1076 case WPS_TOKEN_BATTERY_TIME
:
1078 int t
= battery_time();
1080 snprintf(buf
, buf_size
, "%dh %dm", t
/ 60, t
% 60);
1087 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED
:
1089 if(charger_input_state
==CHARGER
)
1095 #if CONFIG_CHARGING >= CHARGING_MONITOR
1096 case WPS_TOKEN_BATTERY_CHARGING
:
1098 if (charge_state
== CHARGING
|| charge_state
== TOPOFF
) {
1105 case WPS_TOKEN_BATTERY_SLEEPTIME
:
1107 if (get_sleep_timer() == 0)
1111 format_time(buf
, buf_size
, get_sleep_timer() * 1000);
1116 case WPS_TOKEN_PLAYBACK_STATUS
:
1118 int status
= audio_status();
1120 if (status
== AUDIO_STATUS_PLAY
)
1122 if (wps_fading_out
||
1123 (status
& AUDIO_STATUS_PAUSE
&& !status_get_ffmode()))
1125 if (status_get_ffmode() == STATUS_FASTFORWARD
)
1127 if (status_get_ffmode() == STATUS_FASTBACKWARD
)
1134 snprintf(buf
, buf_size
, "%d", mode
-1);
1138 case WPS_TOKEN_REPEAT_MODE
:
1140 *intval
= global_settings
.repeat_mode
+ 1;
1141 snprintf(buf
, buf_size
, "%d", global_settings
.repeat_mode
);
1143 case WPS_TOKEN_RTC_12HOUR_CFG
:
1145 *intval
= global_settings
.timeformat
+ 1;
1146 snprintf(buf
, buf_size
, "%d", global_settings
.timeformat
);
1149 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1150 /* d: day of month (01..31) */
1151 snprintf(buf
, buf_size
, "%02d", tm
->tm_mday
);
1154 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1155 /* e: day of month, blank padded ( 1..31) */
1156 snprintf(buf
, buf_size
, "%2d", tm
->tm_mday
);
1159 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1160 /* H: hour (00..23) */
1161 snprintf(buf
, buf_size
, "%02d", tm
->tm_hour
);
1164 case WPS_TOKEN_RTC_HOUR_24
:
1165 /* k: hour ( 0..23) */
1166 snprintf(buf
, buf_size
, "%2d", tm
->tm_hour
);
1169 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1170 /* I: hour (01..12) */
1171 snprintf(buf
, buf_size
, "%02d",
1172 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1175 case WPS_TOKEN_RTC_HOUR_12
:
1176 /* l: hour ( 1..12) */
1177 snprintf(buf
, buf_size
, "%2d",
1178 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1181 case WPS_TOKEN_RTC_MONTH
:
1182 /* m: month (01..12) */
1184 *intval
= tm
->tm_mon
+ 1;
1185 snprintf(buf
, buf_size
, "%02d", tm
->tm_mon
+ 1);
1188 case WPS_TOKEN_RTC_MINUTE
:
1189 /* M: minute (00..59) */
1190 snprintf(buf
, buf_size
, "%02d", tm
->tm_min
);
1193 case WPS_TOKEN_RTC_SECOND
:
1194 /* S: second (00..59) */
1195 snprintf(buf
, buf_size
, "%02d", tm
->tm_sec
);
1198 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1199 /* y: last two digits of year (00..99) */
1200 snprintf(buf
, buf_size
, "%02d", tm
->tm_year
% 100);
1203 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1204 /* Y: year (1970...) */
1205 snprintf(buf
, buf_size
, "%04d", tm
->tm_year
+ 1900);
1208 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1209 /* p: upper case AM or PM indicator */
1210 return tm
->tm_hour
/12 == 0 ? "AM" : "PM";
1212 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1213 /* P: lower case am or pm indicator */
1214 return tm
->tm_hour
/12 == 0 ? "am" : "pm";
1216 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1217 /* a: abbreviated weekday name (Sun..Sat) */
1218 return str(LANG_WEEKDAY_SUNDAY
+ tm
->tm_wday
);
1220 case WPS_TOKEN_RTC_MONTH_NAME
:
1221 /* b: abbreviated month name (Jan..Dec) */
1222 return str(LANG_MONTH_JANUARY
+ tm
->tm_mon
);
1224 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1225 /* u: day of week (1..7); 1 is Monday */
1227 *intval
= (tm
->tm_wday
== 0) ? 7 : tm
->tm_wday
;
1228 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
+ 1);
1231 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1232 /* w: day of week (0..6); 0 is Sunday */
1234 *intval
= tm
->tm_wday
+ 1;
1235 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
);
1238 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1239 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1240 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1241 case WPS_TOKEN_RTC_HOUR_24
:
1242 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1243 case WPS_TOKEN_RTC_HOUR_12
:
1244 case WPS_TOKEN_RTC_MONTH
:
1245 case WPS_TOKEN_RTC_MINUTE
:
1246 case WPS_TOKEN_RTC_SECOND
:
1247 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1248 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1249 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1251 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1253 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1254 case WPS_TOKEN_RTC_MONTH_NAME
:
1256 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1257 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1261 #ifdef HAVE_LCD_CHARCELLS
1262 case WPS_TOKEN_PROGRESSBAR
:
1264 char *end
= utf8encode(data
->wps_progress_pat
[0], buf
);
1269 case WPS_TOKEN_PLAYER_PROGRESSBAR
:
1272 /* we need 11 characters (full line) for
1274 strncpy(buf
, " ", buf_size
);
1278 /* Tell the user if we have an OldPlayer */
1279 strncpy(buf
, " <Old LCD> ", buf_size
);
1284 #ifdef HAVE_TAGCACHE
1285 case WPS_TOKEN_DATABASE_PLAYCOUNT
:
1287 *intval
= id3
->playcount
+ 1;
1289 snprintf(buf
, buf_size
, "%ld", id3
->playcount
);
1292 case WPS_TOKEN_DATABASE_RATING
:
1294 *intval
= id3
->rating
+ 1;
1296 snprintf(buf
, buf_size
, "%d", id3
->rating
);
1299 case WPS_TOKEN_DATABASE_AUTOSCORE
:
1301 *intval
= id3
->score
+ 1;
1303 snprintf(buf
, buf_size
, "%d", id3
->score
);
1307 #if (CONFIG_CODEC == SWCODEC)
1308 case WPS_TOKEN_CROSSFADE
:
1310 *intval
= global_settings
.crossfade
+ 1;
1311 snprintf(buf
, buf_size
, "%d", global_settings
.crossfade
);
1314 case WPS_TOKEN_REPLAYGAIN
:
1318 if (global_settings
.replaygain
== 0)
1323 get_replaygain_mode(id3
->track_gain_string
!= NULL
,
1324 id3
->album_gain_string
!= NULL
);
1326 val
= 6; /* no tag */
1330 if (global_settings
.replaygain_type
== REPLAYGAIN_SHUFFLE
)
1345 strncpy(buf
, id3
->track_gain_string
, buf_size
);
1349 strncpy(buf
, id3
->album_gain_string
, buf_size
);
1354 #endif /* (CONFIG_CODEC == SWCODEC) */
1356 #if (CONFIG_CODEC != MAS3507D)
1357 case WPS_TOKEN_SOUND_PITCH
:
1359 int val
= sound_get_pitch();
1360 snprintf(buf
, buf_size
, "%d.%d",
1361 val
/ 10, val
% 10);
1366 case WPS_TOKEN_MAIN_HOLD
:
1367 #ifdef HAS_BUTTON_HOLD
1370 if (is_keys_locked())
1371 #endif /*hold switch or softlock*/
1376 #ifdef HAS_REMOTE_BUTTON_HOLD
1377 case WPS_TOKEN_REMOTE_HOLD
:
1378 if (remote_button_hold())
1384 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1385 case WPS_TOKEN_VLED_HDD
:
1391 case WPS_TOKEN_BUTTON_VOLUME
:
1392 if (data
->button_time_volume
&&
1393 TIME_BEFORE(current_tick
, data
->button_time_volume
+
1402 /* Return the index to the end token for the conditional token at index.
1403 The conditional token can be either a start token or a separator
1404 (i.e. option) token.
1406 static int find_conditional_end(struct wps_data
*data
, int index
)
1409 while (data
->tokens
[ret
].type
!= WPS_TOKEN_CONDITIONAL_END
)
1410 ret
= data
->tokens
[ret
].value
.i
;
1412 /* ret now is the index to the end token for the conditional. */
1416 /* Evaluate the conditional that is at *token_index and return whether a skip
1417 has ocurred. *token_index is updated with the new position.
1419 static bool evaluate_conditional(struct gui_wps
*gwps
, int *token_index
)
1424 struct wps_data
*data
= gwps
->data
;
1427 int cond_index
= *token_index
;
1430 unsigned char num_options
= data
->tokens
[cond_index
].value
.i
& 0xFF;
1431 unsigned char prev_val
= (data
->tokens
[cond_index
].value
.i
& 0xFF00) >> 8;
1433 /* treat ?xx<true> constructs as if they had 2 options. */
1434 if (num_options
< 2)
1437 int intval
= num_options
;
1438 /* get_token_value needs to know the number of options in the enum */
1439 value
= get_token_value(gwps
, &data
->tokens
[cond_index
+ 1],
1440 result
, sizeof(result
), &intval
);
1442 /* intval is now the number of the enum option we want to read,
1443 starting from 1. If intval is -1, we check if value is empty. */
1445 intval
= (value
&& *value
) ? 1 : num_options
;
1446 else if (intval
> num_options
|| intval
< 1)
1447 intval
= num_options
;
1449 data
->tokens
[cond_index
].value
.i
= (intval
<< 8) + num_options
;
1451 /* skip to the appropriate enum case */
1452 int next
= cond_index
+ 2;
1453 for (i
= 1; i
< intval
; i
++)
1455 next
= data
->tokens
[next
].value
.i
;
1457 *token_index
= next
;
1459 if (prev_val
== intval
)
1461 /* Same conditional case as previously. Return without clearing the
1466 cond_end
= find_conditional_end(data
, cond_index
+ 2);
1467 for (i
= cond_index
+ 3; i
< cond_end
; i
++)
1469 #ifdef HAVE_LCD_BITMAP
1470 /* clear all pictures in the conditional and nested ones */
1471 if (data
->tokens
[i
].type
== WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
)
1472 clear_image_pos(gwps
, data
->tokens
[i
].value
.i
& 0xFF);
1474 #ifdef HAVE_ALBUMART
1475 if (data
->tokens
[i
].type
== WPS_TOKEN_ALBUMART_DISPLAY
)
1476 draw_album_art(gwps
, audio_current_aa_hid(), true);
1483 /* Read a (sub)line to the given alignment format buffer.
1484 linebuf is the buffer where the data is actually stored.
1485 align is the alignment format that'll be used to display the text.
1486 The return value indicates whether the line needs to be updated.
1488 static bool get_line(struct gui_wps
*gwps
,
1489 int line
, int subline
,
1490 struct align_pos
*align
,
1494 struct wps_data
*data
= gwps
->data
;
1497 char *buf
= linebuf
; /* will always point to the writing position */
1498 char *linebuf_end
= linebuf
+ linebuf_size
- 1;
1499 int i
, last_token_idx
;
1500 bool update
= false;
1502 /* alignment-related variables */
1504 char* cur_align_start
;
1505 cur_align_start
= buf
;
1506 cur_align
= WPS_ALIGN_LEFT
;
1508 align
->center
= NULL
;
1509 align
->right
= NULL
;
1511 /* Process all tokens of the desired subline */
1512 last_token_idx
= wps_last_token_index(data
, line
, subline
);
1513 for (i
= wps_first_token_index(data
, line
, subline
);
1514 i
<= last_token_idx
; i
++)
1516 switch(data
->tokens
[i
].type
)
1518 case WPS_TOKEN_CONDITIONAL
:
1519 /* place ourselves in the right conditional case */
1520 update
|= evaluate_conditional(gwps
, &i
);
1523 case WPS_TOKEN_CONDITIONAL_OPTION
:
1524 /* we've finished in the curent conditional case,
1525 skip to the end of the conditional structure */
1526 i
= find_conditional_end(data
, i
);
1529 #ifdef HAVE_LCD_BITMAP
1530 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
:
1532 struct gui_img
*img
= data
->img
;
1533 int n
= data
->tokens
[i
].value
.i
& 0xFF;
1534 int subimage
= data
->tokens
[i
].value
.i
>> 8;
1536 if (n
>= 0 && n
< MAX_IMAGES
&& img
[n
].loaded
)
1537 img
[n
].display
= subimage
;
1542 case WPS_TOKEN_ALIGN_LEFT
:
1543 case WPS_TOKEN_ALIGN_CENTER
:
1544 case WPS_TOKEN_ALIGN_RIGHT
:
1545 /* remember where the current aligned text started */
1548 case WPS_ALIGN_LEFT
:
1549 align
->left
= cur_align_start
;
1552 case WPS_ALIGN_CENTER
:
1553 align
->center
= cur_align_start
;
1556 case WPS_ALIGN_RIGHT
:
1557 align
->right
= cur_align_start
;
1560 /* start a new alignment */
1561 switch (data
->tokens
[i
].type
)
1563 case WPS_TOKEN_ALIGN_LEFT
:
1564 cur_align
= WPS_ALIGN_LEFT
;
1566 case WPS_TOKEN_ALIGN_CENTER
:
1567 cur_align
= WPS_ALIGN_CENTER
;
1569 case WPS_TOKEN_ALIGN_RIGHT
:
1570 cur_align
= WPS_ALIGN_RIGHT
;
1576 cur_align_start
= buf
;
1578 case WPS_VIEWPORT_ENABLE
:
1580 char label
= data
->tokens
[i
].value
.i
;
1582 char temp
= VP_DRAW_HIDEABLE
;
1583 for(j
=0;j
<data
->num_viewports
;j
++)
1585 temp
= VP_DRAW_HIDEABLE
;
1586 if ((data
->viewports
[j
].hidden_flags
&VP_DRAW_HIDEABLE
) &&
1587 (data
->viewports
[j
].label
== label
))
1589 if (data
->viewports
[j
].hidden_flags
&VP_DRAW_WASHIDDEN
)
1590 temp
|= VP_DRAW_WASHIDDEN
;
1591 data
->viewports
[j
].hidden_flags
= temp
;
1598 /* get the value of the tag and copy it to the buffer */
1599 const char *value
= get_token_value(gwps
, &data
->tokens
[i
],
1600 temp_buf
, sizeof(temp_buf
), NULL
);
1604 while (*value
&& (buf
< linebuf_end
))
1612 /* close the current alignment */
1615 case WPS_ALIGN_LEFT
:
1616 align
->left
= cur_align_start
;
1619 case WPS_ALIGN_CENTER
:
1620 align
->center
= cur_align_start
;
1623 case WPS_ALIGN_RIGHT
:
1624 align
->right
= cur_align_start
;
1631 static void get_subline_timeout(struct gui_wps
*gwps
, int line
, int subline
)
1633 struct wps_data
*data
= gwps
->data
;
1635 int subline_idx
= wps_subline_index(data
, line
, subline
);
1636 int last_token_idx
= wps_last_token_index(data
, line
, subline
);
1638 data
->sublines
[subline_idx
].time_mult
= DEFAULT_SUBLINE_TIME_MULTIPLIER
;
1640 for (i
= wps_first_token_index(data
, line
, subline
);
1641 i
<= last_token_idx
; i
++)
1643 switch(data
->tokens
[i
].type
)
1645 case WPS_TOKEN_CONDITIONAL
:
1646 /* place ourselves in the right conditional case */
1647 evaluate_conditional(gwps
, &i
);
1650 case WPS_TOKEN_CONDITIONAL_OPTION
:
1651 /* we've finished in the curent conditional case,
1652 skip to the end of the conditional structure */
1653 i
= find_conditional_end(data
, i
);
1656 case WPS_TOKEN_SUBLINE_TIMEOUT
:
1657 data
->sublines
[subline_idx
].time_mult
= data
->tokens
[i
].value
.i
;
1666 /* Calculates which subline should be displayed for the specified line
1667 Returns true iff the subline must be refreshed */
1668 static bool update_curr_subline(struct gui_wps
*gwps
, int line
)
1670 struct wps_data
*data
= gwps
->data
;
1672 int search
, search_start
, num_sublines
;
1674 bool new_subline_refresh
;
1675 bool only_one_subline
;
1677 num_sublines
= data
->lines
[line
].num_sublines
;
1678 reset_subline
= (data
->lines
[line
].curr_subline
== SUBLINE_RESET
);
1679 new_subline_refresh
= false;
1680 only_one_subline
= false;
1682 /* if time to advance to next sub-line */
1683 if (TIME_AFTER(current_tick
, data
->lines
[line
].subline_expire_time
- 1) ||
1686 /* search all sublines until the next subline with time > 0
1687 is found or we get back to the subline we started with */
1691 search_start
= data
->lines
[line
].curr_subline
;
1693 for (search
= 0; search
< num_sublines
; search
++)
1695 data
->lines
[line
].curr_subline
++;
1697 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1698 if (data
->lines
[line
].curr_subline
== num_sublines
)
1700 if (data
->lines
[line
].curr_subline
== 1)
1701 only_one_subline
= true;
1702 data
->lines
[line
].curr_subline
= 0;
1705 /* if back where we started after search or
1706 only one subline is defined on the line */
1707 if (((search
> 0) &&
1708 (data
->lines
[line
].curr_subline
== search_start
)) ||
1711 /* no other subline with a time > 0 exists */
1712 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1714 data
->lines
[line
].subline_expire_time
) + 100 * HZ
;
1719 /* get initial time multiplier for this subline */
1720 get_subline_timeout(gwps
, line
, data
->lines
[line
].curr_subline
);
1722 int subline_idx
= wps_subline_index(data
, line
,
1723 data
->lines
[line
].curr_subline
);
1725 /* only use this subline if subline time > 0 */
1726 if (data
->sublines
[subline_idx
].time_mult
> 0)
1728 new_subline_refresh
= true;
1729 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1730 current_tick
: data
->lines
[line
].subline_expire_time
) +
1731 BASE_SUBLINE_TIME
*data
->sublines
[subline_idx
].time_mult
;
1738 return new_subline_refresh
;
1741 /* Display a line appropriately according to its alignment format.
1742 format_align contains the text, separated between left, center and right.
1743 line is the index of the line on the screen.
1744 scroll indicates whether the line is a scrolling one or not.
1746 static void write_line(struct screen
*display
,
1747 struct align_pos
*format_align
,
1752 int left_width
= 0, left_xpos
;
1753 int center_width
= 0, center_xpos
;
1754 int right_width
= 0, right_xpos
;
1760 /* calculate different string sizes and positions */
1761 display
->getstringsize((unsigned char *)" ", &space_width
, &string_height
);
1762 if (format_align
->left
!= 0) {
1763 display
->getstringsize((unsigned char *)format_align
->left
,
1764 &left_width
, &string_height
);
1767 if (format_align
->right
!= 0) {
1768 display
->getstringsize((unsigned char *)format_align
->right
,
1769 &right_width
, &string_height
);
1772 if (format_align
->center
!= 0) {
1773 display
->getstringsize((unsigned char *)format_align
->center
,
1774 ¢er_width
, &string_height
);
1778 right_xpos
= (display
->getwidth() - right_width
);
1779 center_xpos
= (display
->getwidth() + left_xpos
- center_width
) / 2;
1781 scroll_width
= display
->getwidth() - left_xpos
;
1783 /* Checks for overlapping strings.
1784 If needed the overlapping strings will be merged, separated by a
1787 /* CASE 1: left and centered string overlap */
1788 /* there is a left string, need to merge left and center */
1789 if ((left_width
!= 0 && center_width
!= 0) &&
1790 (left_xpos
+ left_width
+ space_width
> center_xpos
)) {
1791 /* replace the former separator '\0' of left and
1792 center string with a space */
1793 *(--format_align
->center
) = ' ';
1794 /* calculate the new width and position of the merged string */
1795 left_width
= left_width
+ space_width
+ center_width
;
1796 /* there is no centered string anymore */
1799 /* there is no left string, move center to left */
1800 if ((left_width
== 0 && center_width
!= 0) &&
1801 (left_xpos
+ left_width
> center_xpos
)) {
1802 /* move the center string to the left string */
1803 format_align
->left
= format_align
->center
;
1804 /* calculate the new width and position of the string */
1805 left_width
= center_width
;
1806 /* there is no centered string anymore */
1810 /* CASE 2: centered and right string overlap */
1811 /* there is a right string, need to merge center and right */
1812 if ((center_width
!= 0 && right_width
!= 0) &&
1813 (center_xpos
+ center_width
+ space_width
> right_xpos
)) {
1814 /* replace the former separator '\0' of center and
1815 right string with a space */
1816 *(--format_align
->right
) = ' ';
1817 /* move the center string to the right after merge */
1818 format_align
->right
= format_align
->center
;
1819 /* calculate the new width and position of the merged string */
1820 right_width
= center_width
+ space_width
+ right_width
;
1821 right_xpos
= (display
->getwidth() - right_width
);
1822 /* there is no centered string anymore */
1825 /* there is no right string, move center to right */
1826 if ((center_width
!= 0 && right_width
== 0) &&
1827 (center_xpos
+ center_width
> right_xpos
)) {
1828 /* move the center string to the right string */
1829 format_align
->right
= format_align
->center
;
1830 /* calculate the new width and position of the string */
1831 right_width
= center_width
;
1832 right_xpos
= (display
->getwidth() - right_width
);
1833 /* there is no centered string anymore */
1837 /* CASE 3: left and right overlap
1838 There is no center string anymore, either there never
1839 was one or it has been merged in case 1 or 2 */
1840 /* there is a left string, need to merge left and right */
1841 if ((left_width
!= 0 && center_width
== 0 && right_width
!= 0) &&
1842 (left_xpos
+ left_width
+ space_width
> right_xpos
)) {
1843 /* replace the former separator '\0' of left and
1844 right string with a space */
1845 *(--format_align
->right
) = ' ';
1846 /* calculate the new width and position of the string */
1847 left_width
= left_width
+ space_width
+ right_width
;
1848 /* there is no right string anymore */
1851 /* there is no left string, move right to left */
1852 if ((left_width
== 0 && center_width
== 0 && right_width
!= 0) &&
1853 (left_width
> right_xpos
)) {
1854 /* move the right string to the left string */
1855 format_align
->left
= format_align
->right
;
1856 /* calculate the new width and position of the string */
1857 left_width
= right_width
;
1858 /* there is no right string anymore */
1862 ypos
= (line
* string_height
);
1865 if (scroll
&& ((left_width
> scroll_width
) ||
1866 (center_width
> scroll_width
) ||
1867 (right_width
> scroll_width
)))
1869 display
->puts_scroll(0, line
,
1870 (unsigned char *)format_align
->left
);
1874 #ifdef HAVE_LCD_BITMAP
1875 /* clear the line first */
1876 display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1877 display
->fillrect(left_xpos
, ypos
, display
->getwidth(), string_height
);
1878 display
->set_drawmode(DRMODE_SOLID
);
1881 /* Nasty hack: we output an empty scrolling string,
1882 which will reset the scroller for that line */
1883 display
->puts_scroll(0, line
, (unsigned char *)"");
1885 /* print aligned strings */
1886 if (left_width
!= 0)
1888 display
->putsxy(left_xpos
, ypos
,
1889 (unsigned char *)format_align
->left
);
1891 if (center_width
!= 0)
1893 display
->putsxy(center_xpos
, ypos
,
1894 (unsigned char *)format_align
->center
);
1896 if (right_width
!= 0)
1898 display
->putsxy(right_xpos
, ypos
,
1899 (unsigned char *)format_align
->right
);
1904 /* Refresh the WPS according to refresh_mode. */
1905 bool gui_wps_refresh(struct gui_wps
*gwps
,
1907 unsigned char refresh_mode
)
1909 struct wps_data
*data
= gwps
->data
;
1910 struct screen
*display
= gwps
->display
;
1911 struct wps_state
*state
= gwps
->state
;
1913 if(!gwps
|| !data
|| !state
|| !display
)
1916 int v
, line
, i
, subline_idx
;
1917 unsigned char flags
;
1918 char linebuf
[MAX_PATH
];
1919 unsigned char vp_refresh_mode
;
1921 struct align_pos align
;
1923 align
.center
= NULL
;
1926 bool update_line
, new_subline_refresh
;
1928 #ifdef HAVE_LCD_BITMAP
1929 gui_wps_statusbar_draw(gwps
, true);
1931 /* to find out wether the peak meter is enabled we
1932 assume it wasn't until we find a line that contains
1933 the peak meter. We can't use peak_meter_enabled itself
1934 because that would mean to turn off the meter thread
1935 temporarily. (That shouldn't matter unless yield
1936 or sleep is called but who knows...)
1938 bool enable_pm
= false;
1942 /* reset to first subline if refresh all flag is set */
1943 if (refresh_mode
== WPS_REFRESH_ALL
)
1945 display
->set_viewport(&data
->viewports
[0].vp
);
1946 display
->clear_viewport();
1948 for (i
= 0; i
<= data
->num_lines
; i
++)
1950 data
->lines
[i
].curr_subline
= SUBLINE_RESET
;
1954 #ifdef HAVE_LCD_CHARCELLS
1955 for (i
= 0; i
< 8; i
++)
1957 if (data
->wps_progress_pat
[i
] == 0)
1958 data
->wps_progress_pat
[i
] = display
->get_locked_pattern();
1964 display
->stop_scroll();
1968 state
->ff_rewind_count
= ffwd_offset
;
1970 /* disable any viewports which are conditionally displayed */
1971 for (v
= 0; v
< data
->num_viewports
; v
++)
1973 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDEABLE
)
1975 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
)
1976 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
1978 data
->viewports
[v
].hidden_flags
|= VP_DRAW_HIDDEN
;
1981 for (v
= 0; v
< data
->num_viewports
; v
++)
1983 display
->set_viewport(&data
->viewports
[v
].vp
);
1984 vp_refresh_mode
= refresh_mode
;
1986 #ifdef HAVE_LCD_BITMAP
1987 /* Set images to not to be displayed */
1988 for (i
= 0; i
< MAX_IMAGES
; i
++)
1990 data
->img
[i
].display
= -1;
1993 /* dont redraw the viewport if its disabled */
1994 if ((data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
))
1996 if (!(data
->viewports
[v
].hidden_flags
&VP_DRAW_WASHIDDEN
))
1997 display
->scroll_stop(&data
->viewports
[v
].vp
);
1998 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
2001 else if (((data
->viewports
[v
].hidden_flags
&
2002 (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
))
2003 == (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
)))
2005 vp_refresh_mode
= WPS_REFRESH_ALL
;
2006 data
->viewports
[v
].hidden_flags
= VP_DRAW_HIDEABLE
;
2008 if (vp_refresh_mode
== WPS_REFRESH_ALL
)
2010 display
->clear_viewport();
2013 for (line
= data
->viewports
[v
].first_line
;
2014 line
<= data
->viewports
[v
].last_line
; line
++)
2016 memset(linebuf
, 0, sizeof(linebuf
));
2017 update_line
= false;
2019 /* get current subline for the line */
2020 new_subline_refresh
= update_curr_subline(gwps
, line
);
2022 subline_idx
= wps_subline_index(data
, line
,
2023 data
->lines
[line
].curr_subline
);
2024 flags
= data
->sublines
[subline_idx
].line_type
;
2026 if (vp_refresh_mode
== WPS_REFRESH_ALL
|| (flags
& vp_refresh_mode
)
2027 || new_subline_refresh
)
2029 /* get_line tells us if we need to update the line */
2030 update_line
= get_line(gwps
, line
, data
->lines
[line
].curr_subline
,
2031 &align
, linebuf
, sizeof(linebuf
));
2033 #ifdef HAVE_LCD_BITMAP
2035 if (flags
& vp_refresh_mode
& WPS_REFRESH_PEAK_METER
)
2037 /* the peakmeter should be alone on its line */
2038 update_line
= false;
2040 int h
= font_get(data
->viewports
[v
].vp
.font
)->height
;
2041 int peak_meter_y
= (line
- data
->viewports
[v
].first_line
)* h
;
2043 /* The user might decide to have the peak meter in the last
2044 line so that it is only displayed if no status bar is
2045 visible. If so we neither want do draw nor enable the
2047 if (peak_meter_y
+ h
<= display
->getheight()) {
2048 /* found a line with a peak meter -> remember that we must
2051 peak_meter_enabled
= true;
2052 peak_meter_screen(gwps
->display
, 0, peak_meter_y
,
2053 MIN(h
, display
->getheight() - peak_meter_y
));
2057 peak_meter_enabled
= false;
2061 #else /* HAVE_LCD_CHARCELL */
2064 if (flags
& vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2066 if (data
->full_line_progressbar
)
2067 draw_player_fullbar(gwps
, linebuf
, sizeof(linebuf
));
2069 draw_player_progress(gwps
);
2074 /* conditionals clear the line which means if the %Vd is put into the default
2075 viewport there will be a blank line.
2076 To get around this we dont allow any actual drawing to happen in the
2077 deault vp if other vp's are defined */
2078 ((data
->num_viewports
>1 && v
!=0) || data
->num_viewports
== 1))
2080 if (flags
& WPS_REFRESH_SCROLL
)
2082 /* if the line is a scrolling one we don't want to update
2083 too often, so that it has the time to scroll */
2084 if ((vp_refresh_mode
& WPS_REFRESH_SCROLL
) || new_subline_refresh
)
2085 write_line(display
, &align
, line
- data
->viewports
[v
].first_line
, true);
2088 write_line(display
, &align
, line
- data
->viewports
[v
].first_line
, false);
2092 #ifdef HAVE_LCD_BITMAP
2094 if (vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2096 if (data
->viewports
[v
].pb
)
2097 draw_progressbar(gwps
, data
->viewports
[v
].pb
);
2099 /* Now display any images in this viewport */
2100 wps_display_images(gwps
, &data
->viewports
[v
].vp
);
2104 #ifdef HAVE_LCD_BITMAP
2105 data
->peak_meter_enabled
= enable_pm
;
2108 /* Restore the default viewport */
2109 display
->set_viewport(NULL
);
2113 #ifdef HAVE_BACKLIGHT
2114 if (global_settings
.caption_backlight
&& state
->id3
)
2116 /* turn on backlight n seconds before track ends, and turn it off n
2117 seconds into the new track. n == backlight_timeout, or 5s */
2118 int n
= global_settings
.backlight_timeout
* 1000;
2121 n
= 5000; /* use 5s if backlight is always on or off */
2123 if (((state
->id3
->elapsed
< 1000) ||
2124 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2125 (state
->paused
== false))
2129 #ifdef HAVE_REMOTE_LCD
2130 if (global_settings
.remote_caption_backlight
&& state
->id3
)
2132 /* turn on remote backlight n seconds before track ends, and turn it
2133 off n seconds into the new track. n == remote_backlight_timeout,
2135 int n
= global_settings
.remote_backlight_timeout
* 1000;
2138 n
= 5000; /* use 5s if backlight is always on or off */
2140 if (((state
->id3
->elapsed
< 1000) ||
2141 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2142 (state
->paused
== false))
2143 remote_backlight_on();