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
67 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
68 /* 3% of 30min file == 54s step size */
69 #define MIN_FF_REWIND_STEP 500
71 /* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds
72 (possibly with a decimal fraction) but stored as integer values.
73 E.g. 2.5 is stored as 25. This means 25 tenth of a second, i.e. 25 units.
75 #define TIMEOUT_UNIT (HZ/10) /* I.e. 0.1 sec */
76 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* In TIMEOUT_UNIT's */
79 /* fades the volume */
80 bool wps_fading_out
= false;
81 void fade(bool fade_in
, bool updatewps
)
83 int fp_global_vol
= global_settings
.volume
<< 8;
84 int fp_min_vol
= sound_min(SOUND_VOLUME
) << 8;
85 int fp_step
= (fp_global_vol
- fp_min_vol
) / 30;
87 wps_fading_out
= !fade_in
;
90 int fp_volume
= fp_min_vol
;
92 /* zero out the sound */
93 sound_set_volume(fp_min_vol
>> 8);
95 sleep(HZ
/10); /* let audio thread run */
98 while (fp_volume
< fp_global_vol
- fp_step
) {
100 sound_set_volume(fp_volume
>> 8);
104 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_NON_STATIC
);
108 sound_set_volume(global_settings
.volume
);
112 int fp_volume
= fp_global_vol
;
114 while (fp_volume
> fp_min_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
);
125 wps_fading_out
= false;
126 #if CONFIG_CODEC != SWCODEC
128 /* let audio thread run and wait for the mas to run out of data */
129 while (!mp3_pause_done())
134 /* reset volume to what it was before the fade */
135 sound_set_volume(global_settings
.volume
);
139 /* return true if screen restore is needed
140 return false otherwise
142 bool update_onvol_change(struct gui_wps
* gwps
)
144 gui_wps_refresh(gwps
, 0, WPS_REFRESH_NON_STATIC
);
146 #ifdef HAVE_LCD_CHARCELLS
147 splashf(0, "Vol: %3d dB",
148 sound_val2phys(SOUND_VOLUME
, global_settings
.volume
));
154 void play_hop(int direction
)
156 if(!wps_state
.id3
|| !wps_state
.id3
->length
157 || global_settings
.skip_length
== 0)
159 #define STEP ((unsigned)global_settings.skip_length*1000)
161 && wps_state
.id3
->length
- wps_state
.id3
->elapsed
< STEP
+1000) {
162 #if CONFIG_CODEC == SWCODEC
163 if(global_settings
.beep
)
164 pcmbuf_beep(1000, 150, 1500*global_settings
.beep
);
168 if((direction
== -1 && wps_state
.id3
->elapsed
< STEP
))
169 wps_state
.id3
->elapsed
= 0;
171 wps_state
.id3
->elapsed
+= STEP
*direction
;
172 if((audio_status() & AUDIO_STATUS_PLAY
) && !wps_state
.paused
) {
173 #if (CONFIG_CODEC == SWCODEC)
174 audio_pre_ff_rewind();
179 audio_ff_rewind(wps_state
.id3
->elapsed
);
180 #if (CONFIG_CODEC != SWCODEC)
181 if (!wps_state
.paused
)
187 bool ffwd_rew(int button
)
189 unsigned int step
= 0; /* current ff/rewind step */
190 unsigned int max_step
= 0; /* maximum ff/rewind step */
191 int ff_rewind_count
= 0; /* current ff/rewind count (in ticks) */
192 int direction
= -1; /* forward=1 or backward=-1 */
196 const long ff_rw_accel
= (global_settings
.ff_rewind_accel
+ 3);
198 if (button
== ACTION_NONE
)
200 status_set_ffmode(0);
207 case ACTION_WPS_SEEKFWD
:
209 case ACTION_WPS_SEEKBACK
:
210 if (wps_state
.ff_rewind
)
214 /* fast forwarding, calc max step relative to end */
215 max_step
= (wps_state
.id3
->length
-
216 (wps_state
.id3
->elapsed
+
218 FF_REWIND_MAX_PERCENT
/ 100;
222 /* rewinding, calc max step relative to start */
223 max_step
= (wps_state
.id3
->elapsed
+ ff_rewind_count
) *
224 FF_REWIND_MAX_PERCENT
/ 100;
227 max_step
= MAX(max_step
, MIN_FF_REWIND_STEP
);
232 ff_rewind_count
+= step
* direction
;
234 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
235 step
+= step
>> ff_rw_accel
;
239 if ( (audio_status() & AUDIO_STATUS_PLAY
) &&
240 wps_state
.id3
&& wps_state
.id3
->length
)
242 if (!wps_state
.paused
)
243 #if (CONFIG_CODEC == SWCODEC)
244 audio_pre_ff_rewind();
248 #if CONFIG_KEYPAD == PLAYER_PAD
250 gui_wps
[i
].display
->stop_scroll();
253 status_set_ffmode(STATUS_FASTFORWARD
);
255 status_set_ffmode(STATUS_FASTBACKWARD
);
257 wps_state
.ff_rewind
= true;
259 step
= 1000 * global_settings
.ff_rewind_min_step
;
266 if ((wps_state
.id3
->elapsed
+ ff_rewind_count
) >
267 wps_state
.id3
->length
)
268 ff_rewind_count
= wps_state
.id3
->length
-
269 wps_state
.id3
->elapsed
;
272 if ((int)(wps_state
.id3
->elapsed
+ ff_rewind_count
) < 0)
273 ff_rewind_count
= -wps_state
.id3
->elapsed
;
277 gui_wps_refresh(&gui_wps
[i
],
278 (wps_state
.wps_time_countup
== false)?
279 ff_rewind_count
:-ff_rewind_count
,
280 WPS_REFRESH_PLAYER_PROGRESS
|
281 WPS_REFRESH_DYNAMIC
);
285 case ACTION_WPS_STOPSEEK
:
286 wps_state
.id3
->elapsed
= wps_state
.id3
->elapsed
+ff_rewind_count
;
287 audio_ff_rewind(wps_state
.id3
->elapsed
);
289 wps_state
.ff_rewind
= false;
290 status_set_ffmode(0);
291 #if (CONFIG_CODEC != SWCODEC)
292 if (!wps_state
.paused
)
295 #ifdef HAVE_LCD_CHARCELLS
302 if(default_event_handler(button
) == SYS_USB_CONNECTED
) {
303 status_set_ffmode(0);
310 button
= get_action(CONTEXT_WPS
|ALLOW_SOFTLOCK
,TIMEOUT_BLOCK
);
315 bool gui_wps_display(void)
318 if (!wps_state
.id3
&& !(audio_status() & AUDIO_STATUS_PLAY
))
320 global_status
.resume_index
= -1;
321 splash(HZ
, ID2P(LANG_END_PLAYLIST
));
328 /* Update the values in the first (default) viewport - in case the user
329 has modified the statusbar or colour settings */
330 #ifdef HAVE_LCD_BITMAP
332 if (gui_wps
[i
].display
->depth
> 1)
334 gui_wps
[i
].data
->viewports
[0].vp
.fg_pattern
= gui_wps
[i
].display
->get_foreground();
335 gui_wps
[i
].data
->viewports
[0].vp
.bg_pattern
= gui_wps
[i
].display
->get_background();
339 gui_wps
[i
].display
->clear_display();
340 if (!gui_wps
[i
].data
->wps_loaded
) {
341 if ( !gui_wps
[i
].data
->num_tokens
) {
342 /* set the default wps for the main-screen */
345 #ifdef HAVE_LCD_BITMAP
347 unload_wps_backdrop();
349 wps_data_load(gui_wps
[i
].data
,
351 "%s%?it<%?in<%in. |>%it|%fn>\n"
352 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
353 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
355 "%al%pc/%pt%ar[%pp:%pe]\n"
356 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
360 wps_data_load(gui_wps
[i
].data
,
362 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
363 "%pc%?ps<*|/>%pt\n", false);
367 /* set the default wps for the remote-screen */
370 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
371 unload_remote_wps_backdrop();
373 wps_data_load(gui_wps
[i
].data
,
375 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
376 "%s%?it<%?in<%in. |>%it|%fn>\n"
377 "%al%pc/%pt%ar[%pp:%pe]\n"
378 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
389 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_ALL
);
390 #ifdef HAVE_LCD_BITMAP
391 /* temporary work around so the statusbar doesnt disappear when the WPS
392 * is first entered. This should be removed when the
393 * WPS-statusbar handling is fixed up a bit more */
394 bool draw
= global_settings
.statusbar
;
395 if (gui_wps
[i
].data
->wps_sb_tag
)
396 draw
= gui_wps
[i
].data
->show_sb_on_wps
;
398 gui_statusbar_draw(&statusbars
.statusbars
[i
], true);
404 bool update(struct gui_wps
*gwps
)
406 bool track_changed
= audio_has_changed_track();
407 bool retcode
= false;
409 gwps
->state
->nid3
= audio_next_track();
412 gwps
->display
->stop_scroll();
413 gwps
->state
->id3
= audio_current_track();
415 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
416 && strcmp(gwps
->state
->id3
->path
, curr_cue
->audio_filename
))
418 /* the current cuesheet isn't the right one any more */
419 /* We need to parse the new cuesheet */
421 char cuepath
[MAX_PATH
];
423 if (look_for_cuesheet_file(gwps
->state
->id3
->path
, cuepath
) &&
424 parse_cuesheet(cuepath
, curr_cue
))
426 gwps
->state
->id3
->cuesheet_type
= 1;
427 strcpy(curr_cue
->audio_filename
, gwps
->state
->id3
->path
);
430 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
433 if (gui_wps_display())
436 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
440 if (gwps
->state
->id3
)
442 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
443 && (gwps
->state
->id3
->elapsed
< curr_cue
->curr_track
->offset
444 || (curr_cue
->curr_track_idx
< curr_cue
->track_count
- 1
445 && gwps
->state
->id3
->elapsed
>= (curr_cue
->curr_track
+1)->offset
)))
447 /* We've changed tracks within the cuesheet :
448 we need to update the ID3 info and refresh the WPS */
450 cue_find_current_track(curr_cue
, gwps
->state
->id3
->elapsed
);
451 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
453 gwps
->display
->stop_scroll();
454 if (gui_wps_display())
457 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
460 gui_wps_refresh(gwps
, 0, WPS_REFRESH_NON_STATIC
);
467 void display_keylock_text(bool locked
)
471 gui_wps
[i
].display
->stop_scroll();
473 splash(HZ
, locked
? ID2P(LANG_KEYLOCK_ON
) : ID2P(LANG_KEYLOCK_OFF
));
476 #ifdef HAVE_LCD_BITMAP
478 static void draw_progressbar(struct gui_wps
*gwps
,
479 struct wps_viewport
*wps_vp
)
481 struct screen
*display
= gwps
->display
;
482 struct wps_state
*state
= gwps
->state
;
483 struct progressbar
*pb
= wps_vp
->pb
;
488 int line_height
= font_get(wps_vp
->vp
.font
)->height
;
489 /* center the pb in the line, but only if the line is higher than the pb */
490 int center
= (line_height
-pb
->height
)/2;
491 /* if Y was not set calculate by font height,Y is -line_number-1 */
492 y
= (-y
-1)*line_height
+ (0 > center
? 0 : center
);
495 if (pb
->have_bitmap_pb
)
496 gui_bitmap_scrollbar_draw(display
, pb
->bm
,
497 pb
->x
, y
, pb
->width
, pb
->bm
.height
,
498 state
->id3
->length
? state
->id3
->length
: 1, 0,
499 state
->id3
->length
? state
->id3
->elapsed
500 + state
->ff_rewind_count
: 0,
503 gui_scrollbar_draw(display
, pb
->x
, y
, pb
->width
, pb
->height
,
504 state
->id3
->length
? state
->id3
->length
: 1, 0,
505 state
->id3
->length
? state
->id3
->elapsed
506 + state
->ff_rewind_count
: 0,
508 #ifdef AB_REPEAT_ENABLE
509 if ( ab_repeat_mode_enabled() && state
->id3
->length
!= 0 )
510 ab_draw_markers(display
, state
->id3
->length
,
511 pb
->x
, pb
->x
+ pb
->width
, y
, pb
->height
);
514 if ( cuesheet_is_enabled() && state
->id3
->cuesheet_type
)
515 cue_draw_markers(display
, state
->id3
->length
,
516 pb
->x
, pb
->x
+ pb
->width
, y
+1, pb
->height
-2);
519 /* clears the area where the image was shown */
520 static void clear_image_pos(struct gui_wps
*gwps
, int n
)
524 struct wps_data
*data
= gwps
->data
;
525 gwps
->display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
526 gwps
->display
->fillrect(data
->img
[n
].x
, data
->img
[n
].y
,
527 data
->img
[n
].bm
.width
, data
->img
[n
].subimage_height
);
528 gwps
->display
->set_drawmode(DRMODE_SOLID
);
531 static void wps_draw_image(struct gui_wps
*gwps
, int n
, int subimage
)
533 struct screen
*display
= gwps
->display
;
534 struct wps_data
*data
= gwps
->data
;
535 if(data
->img
[n
].always_display
)
536 display
->set_drawmode(DRMODE_FG
);
538 display
->set_drawmode(DRMODE_SOLID
);
541 if(data
->img
[n
].bm
.format
== FORMAT_MONO
) {
543 display
->mono_bitmap_part(data
->img
[n
].bm
.data
,
544 0, data
->img
[n
].subimage_height
* subimage
,
545 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
546 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
547 data
->img
[n
].subimage_height
);
550 display
->transparent_bitmap_part((fb_data
*)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
);
559 static void wps_display_images(struct gui_wps
*gwps
, struct viewport
* vp
)
561 if(!gwps
|| !gwps
->data
|| !gwps
->display
)
565 struct wps_data
*data
= gwps
->data
;
566 struct screen
*display
= gwps
->display
;
568 for (n
= 0; n
< MAX_IMAGES
; n
++)
570 if (data
->img
[n
].loaded
)
572 if (data
->img
[n
].display
>= 0)
574 wps_draw_image(gwps
, n
, data
->img
[n
].display
);
575 } else if (data
->img
[n
].always_display
&& data
->img
[n
].vp
== vp
)
577 wps_draw_image(gwps
, n
, 0);
581 display
->set_drawmode(DRMODE_SOLID
);
584 #else /* HAVE_LCD_CHARCELL */
586 static bool draw_player_progress(struct gui_wps
*gwps
)
588 struct wps_state
*state
= gwps
->state
;
589 struct screen
*display
= gwps
->display
;
590 unsigned char progress_pattern
[7];
597 if (state
->id3
->length
)
598 pos
= 36 * (state
->id3
->elapsed
+ state
->ff_rewind_count
)
599 / state
->id3
->length
;
601 for (i
= 0; i
< 7; i
++, pos
-= 5)
604 progress_pattern
[i
] = 0x1fu
;
606 progress_pattern
[i
] = 0x00u
;
608 progress_pattern
[i
] = 0x1fu
>> pos
;
611 display
->define_pattern(gwps
->data
->wps_progress_pat
[0], progress_pattern
);
615 static void draw_player_fullbar(struct gui_wps
*gwps
, char* buf
, int buf_size
)
617 static const unsigned char numbers
[10][4] = {
618 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
619 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
620 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
621 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
622 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
623 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
624 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
625 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
626 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
627 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
630 struct wps_state
*state
= gwps
->state
;
631 struct screen
*display
= gwps
->display
;
632 struct wps_data
*data
= gwps
->data
;
633 unsigned char progress_pattern
[7];
642 if (!state
->id3
|| buf_size
< 34) /* worst case: 11x UTF-8 char + \0 */
645 time
= state
->id3
->elapsed
+ state
->ff_rewind_count
;
646 if (state
->id3
->length
)
647 pos
= 55 * time
/ state
->id3
->length
;
649 memset(timestr
, 0, sizeof(timestr
));
650 format_time(timestr
, sizeof(timestr
)-2, time
);
651 timestr
[strlen(timestr
)] = ':'; /* always safe */
653 for (i
= 0; i
< 11; i
++, pos
-= 5)
656 memset(progress_pattern
, 0, sizeof(progress_pattern
));
658 if ((digit
= timestr
[time_idx
]))
663 if (timestr
[time_idx
+ 1] == ':') /* ones, left aligned */
665 memcpy(progress_pattern
, numbers
[digit
], 4);
668 else /* tens, shifted right */
670 for (j
= 0; j
< 4; j
++)
671 progress_pattern
[j
] = numbers
[digit
][j
] >> 1;
673 if (time_idx
> 0) /* not the first group, add colon in front */
675 progress_pattern
[1] |= 0x10u
;
676 progress_pattern
[3] |= 0x10u
;
682 progress_pattern
[5] = progress_pattern
[6] = 0x1fu
;
685 if (pos
> 0 && pos
< 5)
688 progress_pattern
[5] = progress_pattern
[6] = (~0x1fu
>> pos
) & 0x1fu
;
691 if (softchar
&& pat_idx
< 8)
693 display
->define_pattern(data
->wps_progress_pat
[pat_idx
],
695 buf
= utf8encode(data
->wps_progress_pat
[pat_idx
], buf
);
699 buf
= utf8encode(' ', buf
);
701 buf
= utf8encode(0xe115, buf
); /* 2/7 _ */
706 #endif /* HAVE_LCD_CHARCELL */
708 static char* get_codectype(const struct mp3entry
* id3
)
710 if (id3
->codectype
< AFMT_NUM_CODECS
) {
711 return (char*)audio_formats
[id3
->codectype
].label
;
717 /* Extract a part from a path.
719 * buf - buffer extract part to.
720 * buf_size - size of buffer.
721 * path - path to extract from.
722 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
723 * parent of parent, etc.
725 * Returns buf if the desired level was found, NULL otherwise.
727 static char* get_dir(char* buf
, int buf_size
, const char* path
, int level
)
730 const char* last_sep
;
733 sep
= path
+ strlen(path
);
748 if (level
|| (last_sep
<= sep
))
751 len
= MIN(last_sep
- sep
, buf_size
- 1);
752 strncpy(buf
, sep
+ 1, len
);
757 /* Return the tag found at index i and write its value in buf.
758 The return value is buf if the tag had a value, or NULL if not.
760 intval is used with conditionals/enums: when this function is called,
761 intval should contain the number of options in the conditional/enum.
762 When this function returns, intval is -1 if the tag is non numeric or,
763 if the tag is numeric, *intval is the enum case we want to go to (between 1
764 and the original value of *intval, inclusive).
765 When not treating a conditional/enum, intval should be NULL.
767 static const char *get_token_value(struct gui_wps
*gwps
,
768 struct wps_token
*token
,
769 char *buf
, int buf_size
,
775 struct wps_data
*data
= gwps
->data
;
776 struct wps_state
*state
= gwps
->state
;
781 struct mp3entry
*id3
;
792 struct tm
* tm
= NULL
;
794 /* if the token is an RTC one, update the time
795 and do the necessary checks */
796 if (token
->type
>= WPS_TOKENS_RTC_BEGIN
797 && token
->type
<= WPS_TOKENS_RTC_END
)
815 case WPS_TOKEN_CHARACTER
:
816 return &(token
->value
.c
);
818 case WPS_TOKEN_STRING
:
819 return data
->strings
[token
->value
.i
];
821 case WPS_TOKEN_TRACK_TIME_ELAPSED
:
822 format_time(buf
, buf_size
,
823 id3
->elapsed
+ state
->ff_rewind_count
);
826 case WPS_TOKEN_TRACK_TIME_REMAINING
:
827 format_time(buf
, buf_size
,
828 id3
->length
- id3
->elapsed
-
829 state
->ff_rewind_count
);
832 case WPS_TOKEN_TRACK_LENGTH
:
833 format_time(buf
, buf_size
, id3
->length
);
836 case WPS_TOKEN_PLAYLIST_ENTRIES
:
837 snprintf(buf
, buf_size
, "%d", playlist_amount());
840 case WPS_TOKEN_PLAYLIST_NAME
:
841 return playlist_name(NULL
, buf
, buf_size
);
843 case WPS_TOKEN_PLAYLIST_POSITION
:
844 snprintf(buf
, buf_size
, "%d", playlist_get_display_index());
847 case WPS_TOKEN_PLAYLIST_SHUFFLE
:
848 if ( global_settings
.playlist_shuffle
)
854 case WPS_TOKEN_VOLUME
:
855 snprintf(buf
, buf_size
, "%d", global_settings
.volume
);
858 if (global_settings
.volume
== sound_min(SOUND_VOLUME
))
862 else if (global_settings
.volume
== 0)
866 else if (global_settings
.volume
> 0)
872 *intval
= (limit
- 3) * (global_settings
.volume
873 - sound_min(SOUND_VOLUME
) - 1)
874 / (-1 - sound_min(SOUND_VOLUME
)) + 2;
879 case WPS_TOKEN_TRACK_ELAPSED_PERCENT
:
880 if (id3
->length
<= 0)
885 *intval
= limit
* (id3
->elapsed
+ state
->ff_rewind_count
)
888 snprintf(buf
, buf_size
, "%d",
889 100*(id3
->elapsed
+ state
->ff_rewind_count
) / id3
->length
);
892 case WPS_TOKEN_METADATA_ARTIST
:
895 case WPS_TOKEN_METADATA_COMPOSER
:
896 return id3
->composer
;
898 case WPS_TOKEN_METADATA_ALBUM
:
901 case WPS_TOKEN_METADATA_ALBUM_ARTIST
:
902 return id3
->albumartist
;
904 case WPS_TOKEN_METADATA_GROUPING
:
905 return id3
->grouping
;
907 case WPS_TOKEN_METADATA_GENRE
:
908 return id3
->genre_string
;
910 case WPS_TOKEN_METADATA_DISC_NUMBER
:
911 if (id3
->disc_string
)
912 return id3
->disc_string
;
914 snprintf(buf
, buf_size
, "%d", id3
->discnum
);
919 case WPS_TOKEN_METADATA_TRACK_NUMBER
:
920 if (id3
->track_string
)
921 return id3
->track_string
;
924 snprintf(buf
, buf_size
, "%d", id3
->tracknum
);
929 case WPS_TOKEN_METADATA_TRACK_TITLE
:
932 case WPS_TOKEN_METADATA_VERSION
:
933 switch (id3
->id3version
)
954 case WPS_TOKEN_METADATA_YEAR
:
955 if( id3
->year_string
)
956 return id3
->year_string
;
959 snprintf(buf
, buf_size
, "%d", id3
->year
);
964 case WPS_TOKEN_METADATA_COMMENT
:
968 case WPS_TOKEN_ALBUMART_DISPLAY
:
969 draw_album_art(gwps
, audio_current_aa_hid(), false);
972 case WPS_TOKEN_ALBUMART_FOUND
:
973 if (audio_current_aa_hid() >= 0) {
979 case WPS_TOKEN_FILE_BITRATE
:
981 snprintf(buf
, buf_size
, "%d", id3
->bitrate
);
986 case WPS_TOKEN_FILE_CODEC
:
989 if(id3
->codectype
== AFMT_UNKNOWN
)
990 *intval
= AFMT_NUM_CODECS
;
992 *intval
= id3
->codectype
;
994 return get_codectype(id3
);
996 case WPS_TOKEN_FILE_FREQUENCY
:
997 snprintf(buf
, buf_size
, "%ld", id3
->frequency
);
1000 case WPS_TOKEN_FILE_FREQUENCY_KHZ
:
1001 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
1002 if ((id3
->frequency
% 1000) < 100)
1003 snprintf(buf
, buf_size
, "%ld", id3
->frequency
/ 1000);
1005 snprintf(buf
, buf_size
, "%ld.%d",
1006 id3
->frequency
/ 1000,
1007 (id3
->frequency
% 1000) / 100);
1010 case WPS_TOKEN_FILE_NAME
:
1011 if (get_dir(buf
, buf_size
, id3
->path
, 0)) {
1012 /* Remove extension */
1013 char* sep
= strrchr(buf
, '.');
1023 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION
:
1024 return get_dir(buf
, buf_size
, id3
->path
, 0);
1026 case WPS_TOKEN_FILE_PATH
:
1029 case WPS_TOKEN_FILE_SIZE
:
1030 snprintf(buf
, buf_size
, "%ld", id3
->filesize
/ 1024);
1033 case WPS_TOKEN_FILE_VBR
:
1034 return id3
->vbr
? "(avg)" : NULL
;
1036 case WPS_TOKEN_FILE_DIRECTORY
:
1037 return get_dir(buf
, buf_size
, id3
->path
, token
->value
.i
);
1039 case WPS_TOKEN_BATTERY_PERCENT
:
1041 int l
= battery_level();
1045 limit
= MAX(limit
, 2);
1047 /* First enum is used for "unknown level". */
1048 *intval
= (limit
- 1) * l
/ 100 + 2;
1055 snprintf(buf
, buf_size
, "%d", l
);
1062 case WPS_TOKEN_BATTERY_VOLTS
:
1064 unsigned int v
= battery_voltage();
1065 snprintf(buf
, buf_size
, "%d.%02d", v
/ 1000, (v
% 1000) / 10);
1069 case WPS_TOKEN_BATTERY_TIME
:
1071 int t
= battery_time();
1073 snprintf(buf
, buf_size
, "%dh %dm", t
/ 60, t
% 60);
1080 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED
:
1082 if(charger_input_state
==CHARGER
)
1088 #if CONFIG_CHARGING >= CHARGING_MONITOR
1089 case WPS_TOKEN_BATTERY_CHARGING
:
1091 if (charge_state
== CHARGING
|| charge_state
== TOPOFF
) {
1098 case WPS_TOKEN_BATTERY_SLEEPTIME
:
1100 if (get_sleep_timer() == 0)
1104 format_time(buf
, buf_size
, get_sleep_timer() * 1000);
1109 case WPS_TOKEN_PLAYBACK_STATUS
:
1111 int status
= audio_status();
1113 if (status
== AUDIO_STATUS_PLAY
)
1115 if (wps_fading_out
||
1116 (status
& AUDIO_STATUS_PAUSE
&& !status_get_ffmode()))
1118 if (status_get_ffmode() == STATUS_FASTFORWARD
)
1120 if (status_get_ffmode() == STATUS_FASTBACKWARD
)
1127 snprintf(buf
, buf_size
, "%d", mode
-1);
1131 case WPS_TOKEN_REPEAT_MODE
:
1133 *intval
= global_settings
.repeat_mode
+ 1;
1134 snprintf(buf
, buf_size
, "%d", global_settings
.repeat_mode
);
1137 case WPS_TOKEN_RTC_12HOUR_CFG
:
1139 *intval
= global_settings
.timeformat
+ 1;
1140 snprintf(buf
, buf_size
, "%d", global_settings
.timeformat
);
1143 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1144 /* d: day of month (01..31) */
1145 snprintf(buf
, buf_size
, "%02d", tm
->tm_mday
);
1148 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1149 /* e: day of month, blank padded ( 1..31) */
1150 snprintf(buf
, buf_size
, "%2d", tm
->tm_mday
);
1153 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1154 /* H: hour (00..23) */
1155 snprintf(buf
, buf_size
, "%02d", tm
->tm_hour
);
1158 case WPS_TOKEN_RTC_HOUR_24
:
1159 /* k: hour ( 0..23) */
1160 snprintf(buf
, buf_size
, "%2d", tm
->tm_hour
);
1163 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1164 /* I: hour (01..12) */
1165 snprintf(buf
, buf_size
, "%02d",
1166 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1169 case WPS_TOKEN_RTC_HOUR_12
:
1170 /* l: hour ( 1..12) */
1171 snprintf(buf
, buf_size
, "%2d",
1172 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1175 case WPS_TOKEN_RTC_MONTH
:
1176 /* m: month (01..12) */
1178 *intval
= tm
->tm_mon
+ 1;
1179 snprintf(buf
, buf_size
, "%02d", tm
->tm_mon
+ 1);
1182 case WPS_TOKEN_RTC_MINUTE
:
1183 /* M: minute (00..59) */
1184 snprintf(buf
, buf_size
, "%02d", tm
->tm_min
);
1187 case WPS_TOKEN_RTC_SECOND
:
1188 /* S: second (00..59) */
1189 snprintf(buf
, buf_size
, "%02d", tm
->tm_sec
);
1192 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1193 /* y: last two digits of year (00..99) */
1194 snprintf(buf
, buf_size
, "%02d", tm
->tm_year
% 100);
1197 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1198 /* Y: year (1970...) */
1199 snprintf(buf
, buf_size
, "%04d", tm
->tm_year
+ 1900);
1202 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1203 /* p: upper case AM or PM indicator */
1204 return tm
->tm_hour
/12 == 0 ? "AM" : "PM";
1206 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1207 /* P: lower case am or pm indicator */
1208 return tm
->tm_hour
/12 == 0 ? "am" : "pm";
1210 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1211 /* a: abbreviated weekday name (Sun..Sat) */
1212 return str(LANG_WEEKDAY_SUNDAY
+ tm
->tm_wday
);
1214 case WPS_TOKEN_RTC_MONTH_NAME
:
1215 /* b: abbreviated month name (Jan..Dec) */
1216 return str(LANG_MONTH_JANUARY
+ tm
->tm_mon
);
1218 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1219 /* u: day of week (1..7); 1 is Monday */
1221 *intval
= (tm
->tm_wday
== 0) ? 7 : tm
->tm_wday
;
1222 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
+ 1);
1225 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1226 /* w: day of week (0..6); 0 is Sunday */
1228 *intval
= tm
->tm_wday
+ 1;
1229 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
);
1232 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1233 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1234 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1235 case WPS_TOKEN_RTC_HOUR_24
:
1236 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1237 case WPS_TOKEN_RTC_HOUR_12
:
1238 case WPS_TOKEN_RTC_MONTH
:
1239 case WPS_TOKEN_RTC_MINUTE
:
1240 case WPS_TOKEN_RTC_SECOND
:
1241 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1242 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1243 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1245 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1247 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1248 case WPS_TOKEN_RTC_MONTH_NAME
:
1250 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1251 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1255 #ifdef HAVE_LCD_CHARCELLS
1256 case WPS_TOKEN_PROGRESSBAR
:
1258 char *end
= utf8encode(data
->wps_progress_pat
[0], buf
);
1263 case WPS_TOKEN_PLAYER_PROGRESSBAR
:
1266 /* we need 11 characters (full line) for
1268 strncpy(buf
, " ", buf_size
);
1272 /* Tell the user if we have an OldPlayer */
1273 strncpy(buf
, " <Old LCD> ", buf_size
);
1278 #ifdef HAVE_TAGCACHE
1279 case WPS_TOKEN_DATABASE_PLAYCOUNT
:
1281 *intval
= id3
->playcount
+ 1;
1283 snprintf(buf
, buf_size
, "%ld", id3
->playcount
);
1286 case WPS_TOKEN_DATABASE_RATING
:
1288 *intval
= id3
->rating
+ 1;
1290 snprintf(buf
, buf_size
, "%d", id3
->rating
);
1293 case WPS_TOKEN_DATABASE_AUTOSCORE
:
1295 *intval
= id3
->score
+ 1;
1297 snprintf(buf
, buf_size
, "%d", id3
->score
);
1301 #if (CONFIG_CODEC == SWCODEC)
1302 case WPS_TOKEN_CROSSFADE
:
1304 *intval
= global_settings
.crossfade
+ 1;
1305 snprintf(buf
, buf_size
, "%d", global_settings
.crossfade
);
1308 case WPS_TOKEN_REPLAYGAIN
:
1312 if (global_settings
.replaygain
== 0)
1317 get_replaygain_mode(id3
->track_gain_string
!= NULL
,
1318 id3
->album_gain_string
!= NULL
);
1320 val
= 6; /* no tag */
1324 if (global_settings
.replaygain_type
== REPLAYGAIN_SHUFFLE
)
1339 strncpy(buf
, id3
->track_gain_string
, buf_size
);
1343 strncpy(buf
, id3
->album_gain_string
, buf_size
);
1348 #endif /* (CONFIG_CODEC == SWCODEC) */
1350 #if (CONFIG_CODEC != MAS3507D)
1351 case WPS_TOKEN_SOUND_PITCH
:
1353 int val
= sound_get_pitch();
1354 snprintf(buf
, buf_size
, "%d.%d",
1355 val
/ 10, val
% 10);
1360 case WPS_TOKEN_MAIN_HOLD
:
1361 #ifdef HAS_BUTTON_HOLD
1364 if (is_keys_locked())
1365 #endif /*hold switch or softlock*/
1370 #ifdef HAS_REMOTE_BUTTON_HOLD
1371 case WPS_TOKEN_REMOTE_HOLD
:
1372 if (remote_button_hold())
1378 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1379 case WPS_TOKEN_VLED_HDD
:
1385 case WPS_TOKEN_BUTTON_VOLUME
:
1386 if (data
->button_time_volume
&&
1387 TIME_BEFORE(current_tick
, data
->button_time_volume
+
1388 token
->value
.i
* TIMEOUT_UNIT
))
1392 case WPS_TOKEN_SETTING
:
1396 /* Handle contionals */
1397 const struct settings_list
*s
= settings
+token
->value
.i
;
1398 switch (s
->flags
&F_T_MASK
)
1403 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1404 /* shouldn't overflow since colors are stored
1406 * but this is pretty useless anyway */
1407 *intval
= *(int*)s
->setting
+ 1;
1408 else if (s
->cfg_vals
== NULL
)
1409 /* %?St|name|<1st choice|2nd choice|...> */
1410 *intval
= (*(int*)s
->setting
-s
->int_setting
->min
)
1411 /s
->int_setting
->step
+ 1;
1413 /* %?St|name|<1st choice|2nd choice|...> */
1414 /* Not sure about this one. cfg_name/vals are
1415 * indexed from 0 right? */
1416 *intval
= *(int*)s
->setting
+ 1;
1419 /* %?St|name|<if true|if false> */
1420 *intval
= *(bool*)s
->setting
?1:2;
1423 /* %?St|name|<if non empty string|if empty>
1424 * The string's emptyness discards the setting's
1425 * prefix and suffix */
1426 *intval
= ((char*)s
->setting
)[0]?1:2;
1429 /* This shouldn't happen ... but you never know */
1434 cfg_to_string(token
->value
.i
,buf
,buf_size
);
1443 /* Return the index to the end token for the conditional token at index.
1444 The conditional token can be either a start token or a separator
1445 (i.e. option) token.
1447 static int find_conditional_end(struct wps_data
*data
, int index
)
1450 while (data
->tokens
[ret
].type
!= WPS_TOKEN_CONDITIONAL_END
)
1451 ret
= data
->tokens
[ret
].value
.i
;
1453 /* ret now is the index to the end token for the conditional. */
1457 /* Evaluate the conditional that is at *token_index and return whether a skip
1458 has ocurred. *token_index is updated with the new position.
1460 static bool evaluate_conditional(struct gui_wps
*gwps
, int *token_index
)
1465 struct wps_data
*data
= gwps
->data
;
1468 int cond_index
= *token_index
;
1471 unsigned char num_options
= data
->tokens
[cond_index
].value
.i
& 0xFF;
1472 unsigned char prev_val
= (data
->tokens
[cond_index
].value
.i
& 0xFF00) >> 8;
1474 /* treat ?xx<true> constructs as if they had 2 options. */
1475 if (num_options
< 2)
1478 int intval
= num_options
;
1479 /* get_token_value needs to know the number of options in the enum */
1480 value
= get_token_value(gwps
, &data
->tokens
[cond_index
+ 1],
1481 result
, sizeof(result
), &intval
);
1483 /* intval is now the number of the enum option we want to read,
1484 starting from 1. If intval is -1, we check if value is empty. */
1486 intval
= (value
&& *value
) ? 1 : num_options
;
1487 else if (intval
> num_options
|| intval
< 1)
1488 intval
= num_options
;
1490 data
->tokens
[cond_index
].value
.i
= (intval
<< 8) + num_options
;
1492 /* skip to the appropriate enum case */
1493 int next
= cond_index
+ 2;
1494 for (i
= 1; i
< intval
; i
++)
1496 next
= data
->tokens
[next
].value
.i
;
1498 *token_index
= next
;
1500 if (prev_val
== intval
)
1502 /* Same conditional case as previously. Return without clearing the
1507 cond_end
= find_conditional_end(data
, cond_index
+ 2);
1508 for (i
= cond_index
+ 3; i
< cond_end
; i
++)
1510 #ifdef HAVE_LCD_BITMAP
1511 /* clear all pictures in the conditional and nested ones */
1512 if (data
->tokens
[i
].type
== WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
)
1513 clear_image_pos(gwps
, data
->tokens
[i
].value
.i
& 0xFF);
1515 #ifdef HAVE_ALBUMART
1516 if (data
->tokens
[i
].type
== WPS_TOKEN_ALBUMART_DISPLAY
)
1517 draw_album_art(gwps
, audio_current_aa_hid(), true);
1524 /* Read a (sub)line to the given alignment format buffer.
1525 linebuf is the buffer where the data is actually stored.
1526 align is the alignment format that'll be used to display the text.
1527 The return value indicates whether the line needs to be updated.
1529 static bool get_line(struct gui_wps
*gwps
,
1530 int line
, int subline
,
1531 struct align_pos
*align
,
1535 struct wps_data
*data
= gwps
->data
;
1538 char *buf
= linebuf
; /* will always point to the writing position */
1539 char *linebuf_end
= linebuf
+ linebuf_size
- 1;
1540 int i
, last_token_idx
;
1541 bool update
= false;
1543 /* alignment-related variables */
1545 char* cur_align_start
;
1546 cur_align_start
= buf
;
1547 cur_align
= WPS_ALIGN_LEFT
;
1549 align
->center
= NULL
;
1550 align
->right
= NULL
;
1552 /* Process all tokens of the desired subline */
1553 last_token_idx
= wps_last_token_index(data
, line
, subline
);
1554 for (i
= wps_first_token_index(data
, line
, subline
);
1555 i
<= last_token_idx
; i
++)
1557 switch(data
->tokens
[i
].type
)
1559 case WPS_TOKEN_CONDITIONAL
:
1560 /* place ourselves in the right conditional case */
1561 update
|= evaluate_conditional(gwps
, &i
);
1564 case WPS_TOKEN_CONDITIONAL_OPTION
:
1565 /* we've finished in the curent conditional case,
1566 skip to the end of the conditional structure */
1567 i
= find_conditional_end(data
, i
);
1570 #ifdef HAVE_LCD_BITMAP
1571 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
:
1573 struct gui_img
*img
= data
->img
;
1574 int n
= data
->tokens
[i
].value
.i
& 0xFF;
1575 int subimage
= data
->tokens
[i
].value
.i
>> 8;
1577 if (n
>= 0 && n
< MAX_IMAGES
&& img
[n
].loaded
)
1578 img
[n
].display
= subimage
;
1583 case WPS_TOKEN_ALIGN_LEFT
:
1584 case WPS_TOKEN_ALIGN_CENTER
:
1585 case WPS_TOKEN_ALIGN_RIGHT
:
1586 /* remember where the current aligned text started */
1589 case WPS_ALIGN_LEFT
:
1590 align
->left
= cur_align_start
;
1593 case WPS_ALIGN_CENTER
:
1594 align
->center
= cur_align_start
;
1597 case WPS_ALIGN_RIGHT
:
1598 align
->right
= cur_align_start
;
1601 /* start a new alignment */
1602 switch (data
->tokens
[i
].type
)
1604 case WPS_TOKEN_ALIGN_LEFT
:
1605 cur_align
= WPS_ALIGN_LEFT
;
1607 case WPS_TOKEN_ALIGN_CENTER
:
1608 cur_align
= WPS_ALIGN_CENTER
;
1610 case WPS_TOKEN_ALIGN_RIGHT
:
1611 cur_align
= WPS_ALIGN_RIGHT
;
1617 cur_align_start
= buf
;
1619 case WPS_VIEWPORT_ENABLE
:
1621 char label
= data
->tokens
[i
].value
.i
;
1623 char temp
= VP_DRAW_HIDEABLE
;
1624 for(j
=0;j
<data
->num_viewports
;j
++)
1626 temp
= VP_DRAW_HIDEABLE
;
1627 if ((data
->viewports
[j
].hidden_flags
&VP_DRAW_HIDEABLE
) &&
1628 (data
->viewports
[j
].label
== label
))
1630 if (data
->viewports
[j
].hidden_flags
&VP_DRAW_WASHIDDEN
)
1631 temp
|= VP_DRAW_WASHIDDEN
;
1632 data
->viewports
[j
].hidden_flags
= temp
;
1639 /* get the value of the tag and copy it to the buffer */
1640 const char *value
= get_token_value(gwps
, &data
->tokens
[i
],
1641 temp_buf
, sizeof(temp_buf
), NULL
);
1645 while (*value
&& (buf
< linebuf_end
))
1653 /* close the current alignment */
1656 case WPS_ALIGN_LEFT
:
1657 align
->left
= cur_align_start
;
1660 case WPS_ALIGN_CENTER
:
1661 align
->center
= cur_align_start
;
1664 case WPS_ALIGN_RIGHT
:
1665 align
->right
= cur_align_start
;
1672 static void get_subline_timeout(struct gui_wps
*gwps
, int line
, int subline
)
1674 struct wps_data
*data
= gwps
->data
;
1676 int subline_idx
= wps_subline_index(data
, line
, subline
);
1677 int last_token_idx
= wps_last_token_index(data
, line
, subline
);
1679 data
->sublines
[subline_idx
].time_mult
= DEFAULT_SUBLINE_TIME_MULTIPLIER
;
1681 for (i
= wps_first_token_index(data
, line
, subline
);
1682 i
<= last_token_idx
; i
++)
1684 switch(data
->tokens
[i
].type
)
1686 case WPS_TOKEN_CONDITIONAL
:
1687 /* place ourselves in the right conditional case */
1688 evaluate_conditional(gwps
, &i
);
1691 case WPS_TOKEN_CONDITIONAL_OPTION
:
1692 /* we've finished in the curent conditional case,
1693 skip to the end of the conditional structure */
1694 i
= find_conditional_end(data
, i
);
1697 case WPS_TOKEN_SUBLINE_TIMEOUT
:
1698 data
->sublines
[subline_idx
].time_mult
= data
->tokens
[i
].value
.i
;
1707 /* Calculates which subline should be displayed for the specified line
1708 Returns true iff the subline must be refreshed */
1709 static bool update_curr_subline(struct gui_wps
*gwps
, int line
)
1711 struct wps_data
*data
= gwps
->data
;
1713 int search
, search_start
, num_sublines
;
1715 bool new_subline_refresh
;
1716 bool only_one_subline
;
1718 num_sublines
= data
->lines
[line
].num_sublines
;
1719 reset_subline
= (data
->lines
[line
].curr_subline
== SUBLINE_RESET
);
1720 new_subline_refresh
= false;
1721 only_one_subline
= false;
1723 /* if time to advance to next sub-line */
1724 if (TIME_AFTER(current_tick
, data
->lines
[line
].subline_expire_time
- 1) ||
1727 /* search all sublines until the next subline with time > 0
1728 is found or we get back to the subline we started with */
1732 search_start
= data
->lines
[line
].curr_subline
;
1734 for (search
= 0; search
< num_sublines
; search
++)
1736 data
->lines
[line
].curr_subline
++;
1738 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1739 if (data
->lines
[line
].curr_subline
== num_sublines
)
1741 if (data
->lines
[line
].curr_subline
== 1)
1742 only_one_subline
= true;
1743 data
->lines
[line
].curr_subline
= 0;
1746 /* if back where we started after search or
1747 only one subline is defined on the line */
1748 if (((search
> 0) &&
1749 (data
->lines
[line
].curr_subline
== search_start
)) ||
1752 /* no other subline with a time > 0 exists */
1753 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1755 data
->lines
[line
].subline_expire_time
) + 100 * HZ
;
1760 /* get initial time multiplier for this subline */
1761 get_subline_timeout(gwps
, line
, data
->lines
[line
].curr_subline
);
1763 int subline_idx
= wps_subline_index(data
, line
,
1764 data
->lines
[line
].curr_subline
);
1766 /* only use this subline if subline time > 0 */
1767 if (data
->sublines
[subline_idx
].time_mult
> 0)
1769 new_subline_refresh
= true;
1770 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1771 current_tick
: data
->lines
[line
].subline_expire_time
) +
1772 TIMEOUT_UNIT
*data
->sublines
[subline_idx
].time_mult
;
1779 return new_subline_refresh
;
1782 /* Display a line appropriately according to its alignment format.
1783 format_align contains the text, separated between left, center and right.
1784 line is the index of the line on the screen.
1785 scroll indicates whether the line is a scrolling one or not.
1787 static void write_line(struct screen
*display
,
1788 struct align_pos
*format_align
,
1793 int left_width
= 0, left_xpos
;
1794 int center_width
= 0, center_xpos
;
1795 int right_width
= 0, right_xpos
;
1801 /* calculate different string sizes and positions */
1802 display
->getstringsize((unsigned char *)" ", &space_width
, &string_height
);
1803 if (format_align
->left
!= 0) {
1804 display
->getstringsize((unsigned char *)format_align
->left
,
1805 &left_width
, &string_height
);
1808 if (format_align
->right
!= 0) {
1809 display
->getstringsize((unsigned char *)format_align
->right
,
1810 &right_width
, &string_height
);
1813 if (format_align
->center
!= 0) {
1814 display
->getstringsize((unsigned char *)format_align
->center
,
1815 ¢er_width
, &string_height
);
1819 right_xpos
= (display
->getwidth() - right_width
);
1820 center_xpos
= (display
->getwidth() + left_xpos
- center_width
) / 2;
1822 scroll_width
= display
->getwidth() - left_xpos
;
1824 /* Checks for overlapping strings.
1825 If needed the overlapping strings will be merged, separated by a
1828 /* CASE 1: left and centered string overlap */
1829 /* there is a left string, need to merge left and center */
1830 if ((left_width
!= 0 && center_width
!= 0) &&
1831 (left_xpos
+ left_width
+ space_width
> center_xpos
)) {
1832 /* replace the former separator '\0' of left and
1833 center string with a space */
1834 *(--format_align
->center
) = ' ';
1835 /* calculate the new width and position of the merged string */
1836 left_width
= left_width
+ space_width
+ center_width
;
1837 /* there is no centered string anymore */
1840 /* there is no left string, move center to left */
1841 if ((left_width
== 0 && center_width
!= 0) &&
1842 (left_xpos
+ left_width
> center_xpos
)) {
1843 /* move the center string to the left string */
1844 format_align
->left
= format_align
->center
;
1845 /* calculate the new width and position of the string */
1846 left_width
= center_width
;
1847 /* there is no centered string anymore */
1851 /* CASE 2: centered and right string overlap */
1852 /* there is a right string, need to merge center and right */
1853 if ((center_width
!= 0 && right_width
!= 0) &&
1854 (center_xpos
+ center_width
+ space_width
> right_xpos
)) {
1855 /* replace the former separator '\0' of center and
1856 right string with a space */
1857 *(--format_align
->right
) = ' ';
1858 /* move the center string to the right after merge */
1859 format_align
->right
= format_align
->center
;
1860 /* calculate the new width and position of the merged string */
1861 right_width
= center_width
+ space_width
+ right_width
;
1862 right_xpos
= (display
->getwidth() - right_width
);
1863 /* there is no centered string anymore */
1866 /* there is no right string, move center to right */
1867 if ((center_width
!= 0 && right_width
== 0) &&
1868 (center_xpos
+ center_width
> right_xpos
)) {
1869 /* move the center string to the right string */
1870 format_align
->right
= format_align
->center
;
1871 /* calculate the new width and position of the string */
1872 right_width
= center_width
;
1873 right_xpos
= (display
->getwidth() - right_width
);
1874 /* there is no centered string anymore */
1878 /* CASE 3: left and right overlap
1879 There is no center string anymore, either there never
1880 was one or it has been merged in case 1 or 2 */
1881 /* there is a left string, need to merge left and right */
1882 if ((left_width
!= 0 && center_width
== 0 && right_width
!= 0) &&
1883 (left_xpos
+ left_width
+ space_width
> right_xpos
)) {
1884 /* replace the former separator '\0' of left and
1885 right string with a space */
1886 *(--format_align
->right
) = ' ';
1887 /* calculate the new width and position of the string */
1888 left_width
= left_width
+ space_width
+ right_width
;
1889 /* there is no right string anymore */
1892 /* there is no left string, move right to left */
1893 if ((left_width
== 0 && center_width
== 0 && right_width
!= 0) &&
1894 (left_width
> right_xpos
)) {
1895 /* move the right string to the left string */
1896 format_align
->left
= format_align
->right
;
1897 /* calculate the new width and position of the string */
1898 left_width
= right_width
;
1899 /* there is no right string anymore */
1903 ypos
= (line
* string_height
);
1906 if (scroll
&& ((left_width
> scroll_width
) ||
1907 (center_width
> scroll_width
) ||
1908 (right_width
> scroll_width
)))
1910 display
->puts_scroll(0, line
,
1911 (unsigned char *)format_align
->left
);
1915 #ifdef HAVE_LCD_BITMAP
1916 /* clear the line first */
1917 display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1918 display
->fillrect(left_xpos
, ypos
, display
->getwidth(), string_height
);
1919 display
->set_drawmode(DRMODE_SOLID
);
1922 /* Nasty hack: we output an empty scrolling string,
1923 which will reset the scroller for that line */
1924 display
->puts_scroll(0, line
, (unsigned char *)"");
1926 /* print aligned strings */
1927 if (left_width
!= 0)
1929 display
->putsxy(left_xpos
, ypos
,
1930 (unsigned char *)format_align
->left
);
1932 if (center_width
!= 0)
1934 display
->putsxy(center_xpos
, ypos
,
1935 (unsigned char *)format_align
->center
);
1937 if (right_width
!= 0)
1939 display
->putsxy(right_xpos
, ypos
,
1940 (unsigned char *)format_align
->right
);
1945 /* Refresh the WPS according to refresh_mode. */
1946 bool gui_wps_refresh(struct gui_wps
*gwps
,
1948 unsigned char refresh_mode
)
1950 struct wps_data
*data
= gwps
->data
;
1951 struct screen
*display
= gwps
->display
;
1952 struct wps_state
*state
= gwps
->state
;
1954 if(!gwps
|| !data
|| !state
|| !display
)
1957 int v
, line
, i
, subline_idx
;
1958 unsigned char flags
;
1959 char linebuf
[MAX_PATH
];
1960 unsigned char vp_refresh_mode
;
1962 struct align_pos align
;
1964 align
.center
= NULL
;
1967 bool update_line
, new_subline_refresh
;
1969 #ifdef HAVE_LCD_BITMAP
1971 /* to find out wether the peak meter is enabled we
1972 assume it wasn't until we find a line that contains
1973 the peak meter. We can't use peak_meter_enabled itself
1974 because that would mean to turn off the meter thread
1975 temporarily. (That shouldn't matter unless yield
1976 or sleep is called but who knows...)
1978 bool enable_pm
= false;
1982 /* reset to first subline if refresh all flag is set */
1983 if (refresh_mode
== WPS_REFRESH_ALL
)
1985 display
->set_viewport(&data
->viewports
[0].vp
);
1986 display
->clear_viewport();
1988 for (i
= 0; i
<= data
->num_lines
; i
++)
1990 data
->lines
[i
].curr_subline
= SUBLINE_RESET
;
1994 #ifdef HAVE_LCD_CHARCELLS
1995 for (i
= 0; i
< 8; i
++)
1997 if (data
->wps_progress_pat
[i
] == 0)
1998 data
->wps_progress_pat
[i
] = display
->get_locked_pattern();
2004 display
->stop_scroll();
2008 state
->ff_rewind_count
= ffwd_offset
;
2010 /* disable any viewports which are conditionally displayed */
2011 for (v
= 0; v
< data
->num_viewports
; v
++)
2013 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDEABLE
)
2015 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
)
2016 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
2018 data
->viewports
[v
].hidden_flags
|= VP_DRAW_HIDDEN
;
2021 for (v
= 0; v
< data
->num_viewports
; v
++)
2023 struct wps_viewport
*wps_vp
= &(data
->viewports
[v
]);
2024 display
->set_viewport(&wps_vp
->vp
);
2025 vp_refresh_mode
= refresh_mode
;
2027 #ifdef HAVE_LCD_BITMAP
2028 /* Set images to not to be displayed */
2029 for (i
= 0; i
< MAX_IMAGES
; i
++)
2031 data
->img
[i
].display
= -1;
2034 /* dont redraw the viewport if its disabled */
2035 if ((wps_vp
->hidden_flags
&VP_DRAW_HIDDEN
))
2037 if (!(wps_vp
->hidden_flags
&VP_DRAW_WASHIDDEN
))
2038 display
->scroll_stop(&wps_vp
->vp
);
2039 wps_vp
->hidden_flags
|= VP_DRAW_WASHIDDEN
;
2042 else if (((wps_vp
->hidden_flags
&
2043 (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
))
2044 == (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
)))
2046 vp_refresh_mode
= WPS_REFRESH_ALL
;
2047 wps_vp
->hidden_flags
= VP_DRAW_HIDEABLE
;
2049 if (vp_refresh_mode
== WPS_REFRESH_ALL
)
2051 display
->clear_viewport();
2054 for (line
= wps_vp
->first_line
;
2055 line
<= wps_vp
->last_line
; line
++)
2057 memset(linebuf
, 0, sizeof(linebuf
));
2058 update_line
= false;
2060 /* get current subline for the line */
2061 new_subline_refresh
= update_curr_subline(gwps
, line
);
2063 subline_idx
= wps_subline_index(data
, line
,
2064 data
->lines
[line
].curr_subline
);
2065 flags
= data
->sublines
[subline_idx
].line_type
;
2067 if (vp_refresh_mode
== WPS_REFRESH_ALL
|| (flags
& vp_refresh_mode
)
2068 || new_subline_refresh
)
2070 /* get_line tells us if we need to update the line */
2071 update_line
= get_line(gwps
, line
, data
->lines
[line
].curr_subline
,
2072 &align
, linebuf
, sizeof(linebuf
));
2074 #ifdef HAVE_LCD_BITMAP
2076 if (flags
& vp_refresh_mode
& WPS_REFRESH_PEAK_METER
)
2078 /* the peakmeter should be alone on its line */
2079 update_line
= false;
2081 int h
= font_get(wps_vp
->vp
.font
)->height
;
2082 int peak_meter_y
= (line
- wps_vp
->first_line
)* h
;
2084 /* The user might decide to have the peak meter in the last
2085 line so that it is only displayed if no status bar is
2086 visible. If so we neither want do draw nor enable the
2088 if (peak_meter_y
+ h
<= display
->getheight()) {
2089 /* found a line with a peak meter -> remember that we must
2092 peak_meter_enabled
= true;
2093 peak_meter_screen(gwps
->display
, 0, peak_meter_y
,
2094 MIN(h
, display
->getheight() - peak_meter_y
));
2098 peak_meter_enabled
= false;
2102 #else /* HAVE_LCD_CHARCELL */
2105 if (flags
& vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2107 if (data
->full_line_progressbar
)
2108 draw_player_fullbar(gwps
, linebuf
, sizeof(linebuf
));
2110 draw_player_progress(gwps
);
2115 /* conditionals clear the line which means if the %Vd is put into the default
2116 viewport there will be a blank line.
2117 To get around this we dont allow any actual drawing to happen in the
2118 deault vp if other vp's are defined */
2119 ((data
->num_viewports
>1 && v
!=0) || data
->num_viewports
== 1))
2121 if (flags
& WPS_REFRESH_SCROLL
)
2123 /* if the line is a scrolling one we don't want to update
2124 too often, so that it has the time to scroll */
2125 if ((vp_refresh_mode
& WPS_REFRESH_SCROLL
) || new_subline_refresh
)
2126 write_line(display
, &align
, line
- wps_vp
->first_line
, true);
2129 write_line(display
, &align
, line
- wps_vp
->first_line
, false);
2133 #ifdef HAVE_LCD_BITMAP
2135 if (vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2139 draw_progressbar(gwps
, wps_vp
);
2142 /* Now display any images in this viewport */
2143 wps_display_images(gwps
, &wps_vp
->vp
);
2147 #ifdef HAVE_LCD_BITMAP
2148 data
->peak_meter_enabled
= enable_pm
;
2151 /* Restore the default viewport */
2152 display
->set_viewport(NULL
);
2156 #ifdef HAVE_BACKLIGHT
2157 if (global_settings
.caption_backlight
&& state
->id3
)
2159 /* turn on backlight n seconds before track ends, and turn it off n
2160 seconds into the new track. n == backlight_timeout, or 5s */
2161 int n
= global_settings
.backlight_timeout
* 1000;
2164 n
= 5000; /* use 5s if backlight is always on or off */
2166 if (((state
->id3
->elapsed
< 1000) ||
2167 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2168 (state
->paused
== false))
2172 #ifdef HAVE_REMOTE_LCD
2173 if (global_settings
.remote_caption_backlight
&& state
->id3
)
2175 /* turn on remote backlight n seconds before track ends, and turn it
2176 off n seconds into the new track. n == remote_backlight_timeout,
2178 int n
= global_settings
.remote_backlight_timeout
* 1000;
2181 n
= 5000; /* use 5s if backlight is always on or off */
2183 if (((state
->id3
->elapsed
< 1000) ||
2184 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2185 (state
->paused
== false))
2186 remote_backlight_on();
2189 /* force a bars update if they are being displayed */
2190 viewportmanager_draw_statusbars(NULL
);