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 unsigned step
= ((unsigned)global_settings
.skip_length
*1000);
157 unsigned long *elapsed
= &(wps_state
.id3
->elapsed
);
159 if (direction
== 1 && wps_state
.id3
->length
- *elapsed
< step
+1000) {
160 #if CONFIG_CODEC == SWCODEC
161 if(global_settings
.beep
)
162 pcmbuf_beep(1000, 150, 1500*global_settings
.beep
);
165 } else if ((direction
== -1 && *elapsed
< step
)) {
168 *elapsed
+= step
* direction
;
170 if((audio_status() & AUDIO_STATUS_PLAY
) && !wps_state
.paused
) {
171 #if (CONFIG_CODEC == SWCODEC)
172 audio_pre_ff_rewind();
177 audio_ff_rewind(*elapsed
);
178 #if (CONFIG_CODEC != SWCODEC)
179 if (!wps_state
.paused
)
184 bool ffwd_rew(int button
)
186 unsigned int step
= 0; /* current ff/rewind step */
187 unsigned int max_step
= 0; /* maximum ff/rewind step */
188 int ff_rewind_count
= 0; /* current ff/rewind count (in ticks) */
189 int direction
= -1; /* forward=1 or backward=-1 */
193 const long ff_rw_accel
= (global_settings
.ff_rewind_accel
+ 3);
195 if (button
== ACTION_NONE
)
197 status_set_ffmode(0);
204 case ACTION_WPS_SEEKFWD
:
206 case ACTION_WPS_SEEKBACK
:
207 if (wps_state
.ff_rewind
)
211 /* fast forwarding, calc max step relative to end */
212 max_step
= (wps_state
.id3
->length
-
213 (wps_state
.id3
->elapsed
+
215 FF_REWIND_MAX_PERCENT
/ 100;
219 /* rewinding, calc max step relative to start */
220 max_step
= (wps_state
.id3
->elapsed
+ ff_rewind_count
) *
221 FF_REWIND_MAX_PERCENT
/ 100;
224 max_step
= MAX(max_step
, MIN_FF_REWIND_STEP
);
229 ff_rewind_count
+= step
* direction
;
231 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
232 step
+= step
>> ff_rw_accel
;
236 if ( (audio_status() & AUDIO_STATUS_PLAY
) &&
237 wps_state
.id3
&& wps_state
.id3
->length
)
239 if (!wps_state
.paused
)
240 #if (CONFIG_CODEC == SWCODEC)
241 audio_pre_ff_rewind();
245 #if CONFIG_KEYPAD == PLAYER_PAD
247 gui_wps
[i
].display
->stop_scroll();
250 status_set_ffmode(STATUS_FASTFORWARD
);
252 status_set_ffmode(STATUS_FASTBACKWARD
);
254 wps_state
.ff_rewind
= true;
256 step
= 1000 * global_settings
.ff_rewind_min_step
;
263 if ((wps_state
.id3
->elapsed
+ ff_rewind_count
) >
264 wps_state
.id3
->length
)
265 ff_rewind_count
= wps_state
.id3
->length
-
266 wps_state
.id3
->elapsed
;
269 if ((int)(wps_state
.id3
->elapsed
+ ff_rewind_count
) < 0)
270 ff_rewind_count
= -wps_state
.id3
->elapsed
;
274 gui_wps_refresh(&gui_wps
[i
],
275 (wps_state
.wps_time_countup
== false)?
276 ff_rewind_count
:-ff_rewind_count
,
277 WPS_REFRESH_PLAYER_PROGRESS
|
278 WPS_REFRESH_DYNAMIC
);
282 case ACTION_WPS_STOPSEEK
:
283 wps_state
.id3
->elapsed
= wps_state
.id3
->elapsed
+ff_rewind_count
;
284 audio_ff_rewind(wps_state
.id3
->elapsed
);
286 wps_state
.ff_rewind
= false;
287 status_set_ffmode(0);
288 #if (CONFIG_CODEC != SWCODEC)
289 if (!wps_state
.paused
)
292 #ifdef HAVE_LCD_CHARCELLS
299 if(default_event_handler(button
) == SYS_USB_CONNECTED
) {
300 status_set_ffmode(0);
307 button
= get_action(CONTEXT_WPS
|ALLOW_SOFTLOCK
,TIMEOUT_BLOCK
);
312 bool gui_wps_display(void)
315 if (!wps_state
.id3
&& !(audio_status() & AUDIO_STATUS_PLAY
))
317 global_status
.resume_index
= -1;
318 splash(HZ
, ID2P(LANG_END_PLAYLIST
));
325 /* Update the values in the first (default) viewport - in case the user
326 has modified the statusbar or colour settings */
327 #ifdef HAVE_LCD_BITMAP
329 if (gui_wps
[i
].display
->depth
> 1)
331 gui_wps
[i
].data
->viewports
[0].vp
.fg_pattern
= gui_wps
[i
].display
->get_foreground();
332 gui_wps
[i
].data
->viewports
[0].vp
.bg_pattern
= gui_wps
[i
].display
->get_background();
336 gui_wps
[i
].display
->clear_display();
337 if (!gui_wps
[i
].data
->wps_loaded
) {
338 if ( !gui_wps
[i
].data
->num_tokens
) {
339 /* set the default wps for the main-screen */
342 #ifdef HAVE_LCD_BITMAP
344 unload_wps_backdrop();
346 wps_data_load(gui_wps
[i
].data
,
348 "%s%?it<%?in<%in. |>%it|%fn>\n"
349 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
350 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
352 "%al%pc/%pt%ar[%pp:%pe]\n"
353 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
357 wps_data_load(gui_wps
[i
].data
,
359 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
360 "%pc%?ps<*|/>%pt\n", false);
364 /* set the default wps for the remote-screen */
367 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
368 unload_remote_wps_backdrop();
370 wps_data_load(gui_wps
[i
].data
,
372 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
373 "%s%?it<%?in<%in. |>%it|%fn>\n"
374 "%al%pc/%pt%ar[%pp:%pe]\n"
375 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
386 gui_wps_refresh(&gui_wps
[i
], 0, WPS_REFRESH_ALL
);
387 #ifdef HAVE_LCD_BITMAP
388 /* temporary work around so the statusbar doesnt disappear when the WPS
389 * is first entered. This should be removed when the
390 * WPS-statusbar handling is fixed up a bit more */
391 bool draw
= global_settings
.statusbar
;
392 if (gui_wps
[i
].data
->wps_sb_tag
)
393 draw
= gui_wps
[i
].data
->show_sb_on_wps
;
395 gui_statusbar_draw(&statusbars
.statusbars
[i
], true);
401 bool update(struct gui_wps
*gwps
)
403 bool track_changed
= audio_has_changed_track();
404 bool retcode
= false;
406 gwps
->state
->nid3
= audio_next_track();
409 gwps
->display
->stop_scroll();
410 gwps
->state
->id3
= audio_current_track();
412 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
413 && strcmp(gwps
->state
->id3
->path
, curr_cue
->audio_filename
))
415 /* the current cuesheet isn't the right one any more */
416 /* We need to parse the new cuesheet */
418 char cuepath
[MAX_PATH
];
420 if (look_for_cuesheet_file(gwps
->state
->id3
->path
, cuepath
) &&
421 parse_cuesheet(cuepath
, curr_cue
))
423 gwps
->state
->id3
->cuesheet_type
= 1;
424 strcpy(curr_cue
->audio_filename
, gwps
->state
->id3
->path
);
427 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
430 if (gui_wps_display())
433 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
437 if (gwps
->state
->id3
)
439 if (cuesheet_is_enabled() && gwps
->state
->id3
->cuesheet_type
440 && (gwps
->state
->id3
->elapsed
< curr_cue
->curr_track
->offset
441 || (curr_cue
->curr_track_idx
< curr_cue
->track_count
- 1
442 && gwps
->state
->id3
->elapsed
>= (curr_cue
->curr_track
+1)->offset
)))
444 /* We've changed tracks within the cuesheet :
445 we need to update the ID3 info and refresh the WPS */
447 cue_find_current_track(curr_cue
, gwps
->state
->id3
->elapsed
);
448 cue_spoof_id3(curr_cue
, gwps
->state
->id3
);
450 gwps
->display
->stop_scroll();
451 if (gui_wps_display())
454 gui_wps_refresh(gwps
, 0, WPS_REFRESH_ALL
);
457 gui_wps_refresh(gwps
, 0, WPS_REFRESH_NON_STATIC
);
464 void display_keylock_text(bool locked
)
468 gui_wps
[i
].display
->stop_scroll();
470 splash(HZ
, locked
? ID2P(LANG_KEYLOCK_ON
) : ID2P(LANG_KEYLOCK_OFF
));
473 #ifdef HAVE_LCD_BITMAP
475 static void draw_progressbar(struct gui_wps
*gwps
,
476 struct wps_viewport
*wps_vp
)
478 struct screen
*display
= gwps
->display
;
479 struct wps_state
*state
= gwps
->state
;
480 struct progressbar
*pb
= wps_vp
->pb
;
485 int line_height
= font_get(wps_vp
->vp
.font
)->height
;
486 /* center the pb in the line, but only if the line is higher than the pb */
487 int center
= (line_height
-pb
->height
)/2;
488 /* if Y was not set calculate by font height,Y is -line_number-1 */
489 y
= (-y
-1)*line_height
+ (0 > center
? 0 : center
);
492 if (pb
->have_bitmap_pb
)
493 gui_bitmap_scrollbar_draw(display
, pb
->bm
,
494 pb
->x
, y
, pb
->width
, pb
->bm
.height
,
495 state
->id3
->length
? state
->id3
->length
: 1, 0,
496 state
->id3
->length
? state
->id3
->elapsed
497 + state
->ff_rewind_count
: 0,
500 gui_scrollbar_draw(display
, pb
->x
, y
, pb
->width
, pb
->height
,
501 state
->id3
->length
? state
->id3
->length
: 1, 0,
502 state
->id3
->length
? state
->id3
->elapsed
503 + state
->ff_rewind_count
: 0,
505 #ifdef AB_REPEAT_ENABLE
506 if ( ab_repeat_mode_enabled() && state
->id3
->length
!= 0 )
507 ab_draw_markers(display
, state
->id3
->length
,
508 pb
->x
, pb
->x
+ pb
->width
, y
, pb
->height
);
511 if ( cuesheet_is_enabled() && state
->id3
->cuesheet_type
)
512 cue_draw_markers(display
, state
->id3
->length
,
513 pb
->x
, pb
->x
+ pb
->width
, y
+1, pb
->height
-2);
516 /* clears the area where the image was shown */
517 static void clear_image_pos(struct gui_wps
*gwps
, int n
)
521 struct wps_data
*data
= gwps
->data
;
522 gwps
->display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
523 gwps
->display
->fillrect(data
->img
[n
].x
, data
->img
[n
].y
,
524 data
->img
[n
].bm
.width
, data
->img
[n
].subimage_height
);
525 gwps
->display
->set_drawmode(DRMODE_SOLID
);
528 static void wps_draw_image(struct gui_wps
*gwps
, int n
, int subimage
)
530 struct screen
*display
= gwps
->display
;
531 struct wps_data
*data
= gwps
->data
;
532 if(data
->img
[n
].always_display
)
533 display
->set_drawmode(DRMODE_FG
);
535 display
->set_drawmode(DRMODE_SOLID
);
538 if(data
->img
[n
].bm
.format
== FORMAT_MONO
) {
540 display
->mono_bitmap_part(data
->img
[n
].bm
.data
,
541 0, data
->img
[n
].subimage_height
* subimage
,
542 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
543 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
544 data
->img
[n
].subimage_height
);
547 display
->transparent_bitmap_part((fb_data
*)data
->img
[n
].bm
.data
,
548 0, data
->img
[n
].subimage_height
* subimage
,
549 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
550 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
551 data
->img
[n
].subimage_height
);
556 static void wps_display_images(struct gui_wps
*gwps
, struct viewport
* vp
)
558 if(!gwps
|| !gwps
->data
|| !gwps
->display
)
562 struct wps_data
*data
= gwps
->data
;
563 struct screen
*display
= gwps
->display
;
565 for (n
= 0; n
< MAX_IMAGES
; n
++)
567 if (data
->img
[n
].loaded
)
569 if (data
->img
[n
].display
>= 0)
571 wps_draw_image(gwps
, n
, data
->img
[n
].display
);
572 } else if (data
->img
[n
].always_display
&& data
->img
[n
].vp
== vp
)
574 wps_draw_image(gwps
, n
, 0);
578 display
->set_drawmode(DRMODE_SOLID
);
581 #else /* HAVE_LCD_CHARCELL */
583 static bool draw_player_progress(struct gui_wps
*gwps
)
585 struct wps_state
*state
= gwps
->state
;
586 struct screen
*display
= gwps
->display
;
587 unsigned char progress_pattern
[7];
594 if (state
->id3
->length
)
595 pos
= 36 * (state
->id3
->elapsed
+ state
->ff_rewind_count
)
596 / state
->id3
->length
;
598 for (i
= 0; i
< 7; i
++, pos
-= 5)
601 progress_pattern
[i
] = 0x1fu
;
603 progress_pattern
[i
] = 0x00u
;
605 progress_pattern
[i
] = 0x1fu
>> pos
;
608 display
->define_pattern(gwps
->data
->wps_progress_pat
[0], progress_pattern
);
612 static void draw_player_fullbar(struct gui_wps
*gwps
, char* buf
, int buf_size
)
614 static const unsigned char numbers
[10][4] = {
615 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
616 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
617 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
618 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
619 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
620 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
621 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
622 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
623 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
624 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
627 struct wps_state
*state
= gwps
->state
;
628 struct screen
*display
= gwps
->display
;
629 struct wps_data
*data
= gwps
->data
;
630 unsigned char progress_pattern
[7];
639 if (!state
->id3
|| buf_size
< 34) /* worst case: 11x UTF-8 char + \0 */
642 time
= state
->id3
->elapsed
+ state
->ff_rewind_count
;
643 if (state
->id3
->length
)
644 pos
= 55 * time
/ state
->id3
->length
;
646 memset(timestr
, 0, sizeof(timestr
));
647 format_time(timestr
, sizeof(timestr
)-2, time
);
648 timestr
[strlen(timestr
)] = ':'; /* always safe */
650 for (i
= 0; i
< 11; i
++, pos
-= 5)
653 memset(progress_pattern
, 0, sizeof(progress_pattern
));
655 if ((digit
= timestr
[time_idx
]))
660 if (timestr
[time_idx
+ 1] == ':') /* ones, left aligned */
662 memcpy(progress_pattern
, numbers
[digit
], 4);
665 else /* tens, shifted right */
667 for (j
= 0; j
< 4; j
++)
668 progress_pattern
[j
] = numbers
[digit
][j
] >> 1;
670 if (time_idx
> 0) /* not the first group, add colon in front */
672 progress_pattern
[1] |= 0x10u
;
673 progress_pattern
[3] |= 0x10u
;
679 progress_pattern
[5] = progress_pattern
[6] = 0x1fu
;
682 if (pos
> 0 && pos
< 5)
685 progress_pattern
[5] = progress_pattern
[6] = (~0x1fu
>> pos
) & 0x1fu
;
688 if (softchar
&& pat_idx
< 8)
690 display
->define_pattern(data
->wps_progress_pat
[pat_idx
],
692 buf
= utf8encode(data
->wps_progress_pat
[pat_idx
], buf
);
696 buf
= utf8encode(' ', buf
);
698 buf
= utf8encode(0xe115, buf
); /* 2/7 _ */
703 #endif /* HAVE_LCD_CHARCELL */
705 static char* get_codectype(const struct mp3entry
* id3
)
707 if (id3
->codectype
< AFMT_NUM_CODECS
) {
708 return (char*)audio_formats
[id3
->codectype
].label
;
714 /* Extract a part from a path.
716 * buf - buffer extract part to.
717 * buf_size - size of buffer.
718 * path - path to extract from.
719 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
720 * parent of parent, etc.
722 * Returns buf if the desired level was found, NULL otherwise.
724 static char* get_dir(char* buf
, int buf_size
, const char* path
, int level
)
727 const char* last_sep
;
730 sep
= path
+ strlen(path
);
745 if (level
|| (last_sep
<= sep
))
748 len
= MIN(last_sep
- sep
, buf_size
- 1);
749 strncpy(buf
, sep
+ 1, len
);
754 /* Return the tag found at index i and write its value in buf.
755 The return value is buf if the tag had a value, or NULL if not.
757 intval is used with conditionals/enums: when this function is called,
758 intval should contain the number of options in the conditional/enum.
759 When this function returns, intval is -1 if the tag is non numeric or,
760 if the tag is numeric, *intval is the enum case we want to go to (between 1
761 and the original value of *intval, inclusive).
762 When not treating a conditional/enum, intval should be NULL.
764 static const char *get_token_value(struct gui_wps
*gwps
,
765 struct wps_token
*token
,
766 char *buf
, int buf_size
,
772 struct wps_data
*data
= gwps
->data
;
773 struct wps_state
*state
= gwps
->state
;
778 struct mp3entry
*id3
;
789 struct tm
* tm
= NULL
;
791 /* if the token is an RTC one, update the time
792 and do the necessary checks */
793 if (token
->type
>= WPS_TOKENS_RTC_BEGIN
794 && token
->type
<= WPS_TOKENS_RTC_END
)
812 case WPS_TOKEN_CHARACTER
:
813 return &(token
->value
.c
);
815 case WPS_TOKEN_STRING
:
816 return data
->strings
[token
->value
.i
];
818 case WPS_TOKEN_TRACK_TIME_ELAPSED
:
819 format_time(buf
, buf_size
,
820 id3
->elapsed
+ state
->ff_rewind_count
);
823 case WPS_TOKEN_TRACK_TIME_REMAINING
:
824 format_time(buf
, buf_size
,
825 id3
->length
- id3
->elapsed
-
826 state
->ff_rewind_count
);
829 case WPS_TOKEN_TRACK_LENGTH
:
830 format_time(buf
, buf_size
, id3
->length
);
833 case WPS_TOKEN_PLAYLIST_ENTRIES
:
834 snprintf(buf
, buf_size
, "%d", playlist_amount());
837 case WPS_TOKEN_PLAYLIST_NAME
:
838 return playlist_name(NULL
, buf
, buf_size
);
840 case WPS_TOKEN_PLAYLIST_POSITION
:
841 snprintf(buf
, buf_size
, "%d", playlist_get_display_index());
844 case WPS_TOKEN_PLAYLIST_SHUFFLE
:
845 if ( global_settings
.playlist_shuffle
)
851 case WPS_TOKEN_VOLUME
:
852 snprintf(buf
, buf_size
, "%d", global_settings
.volume
);
855 if (global_settings
.volume
== sound_min(SOUND_VOLUME
))
859 else if (global_settings
.volume
== 0)
863 else if (global_settings
.volume
> 0)
869 *intval
= (limit
- 3) * (global_settings
.volume
870 - sound_min(SOUND_VOLUME
) - 1)
871 / (-1 - sound_min(SOUND_VOLUME
)) + 2;
876 case WPS_TOKEN_TRACK_ELAPSED_PERCENT
:
877 if (id3
->length
<= 0)
882 *intval
= limit
* (id3
->elapsed
+ state
->ff_rewind_count
)
885 snprintf(buf
, buf_size
, "%d",
886 100*(id3
->elapsed
+ state
->ff_rewind_count
) / id3
->length
);
889 case WPS_TOKEN_METADATA_ARTIST
:
892 case WPS_TOKEN_METADATA_COMPOSER
:
893 return id3
->composer
;
895 case WPS_TOKEN_METADATA_ALBUM
:
898 case WPS_TOKEN_METADATA_ALBUM_ARTIST
:
899 return id3
->albumartist
;
901 case WPS_TOKEN_METADATA_GROUPING
:
902 return id3
->grouping
;
904 case WPS_TOKEN_METADATA_GENRE
:
905 return id3
->genre_string
;
907 case WPS_TOKEN_METADATA_DISC_NUMBER
:
908 if (id3
->disc_string
)
909 return id3
->disc_string
;
911 snprintf(buf
, buf_size
, "%d", id3
->discnum
);
916 case WPS_TOKEN_METADATA_TRACK_NUMBER
:
917 if (id3
->track_string
)
918 return id3
->track_string
;
921 snprintf(buf
, buf_size
, "%d", id3
->tracknum
);
926 case WPS_TOKEN_METADATA_TRACK_TITLE
:
929 case WPS_TOKEN_METADATA_VERSION
:
930 switch (id3
->id3version
)
951 case WPS_TOKEN_METADATA_YEAR
:
952 if( id3
->year_string
)
953 return id3
->year_string
;
956 snprintf(buf
, buf_size
, "%d", id3
->year
);
961 case WPS_TOKEN_METADATA_COMMENT
:
965 case WPS_TOKEN_ALBUMART_DISPLAY
:
966 draw_album_art(gwps
, audio_current_aa_hid(), false);
969 case WPS_TOKEN_ALBUMART_FOUND
:
970 if (audio_current_aa_hid() >= 0) {
976 case WPS_TOKEN_FILE_BITRATE
:
978 snprintf(buf
, buf_size
, "%d", id3
->bitrate
);
983 case WPS_TOKEN_FILE_CODEC
:
986 if(id3
->codectype
== AFMT_UNKNOWN
)
987 *intval
= AFMT_NUM_CODECS
;
989 *intval
= id3
->codectype
;
991 return get_codectype(id3
);
993 case WPS_TOKEN_FILE_FREQUENCY
:
994 snprintf(buf
, buf_size
, "%ld", id3
->frequency
);
997 case WPS_TOKEN_FILE_FREQUENCY_KHZ
:
998 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
999 if ((id3
->frequency
% 1000) < 100)
1000 snprintf(buf
, buf_size
, "%ld", id3
->frequency
/ 1000);
1002 snprintf(buf
, buf_size
, "%ld.%d",
1003 id3
->frequency
/ 1000,
1004 (id3
->frequency
% 1000) / 100);
1007 case WPS_TOKEN_FILE_NAME
:
1008 if (get_dir(buf
, buf_size
, id3
->path
, 0)) {
1009 /* Remove extension */
1010 char* sep
= strrchr(buf
, '.');
1020 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION
:
1021 return get_dir(buf
, buf_size
, id3
->path
, 0);
1023 case WPS_TOKEN_FILE_PATH
:
1026 case WPS_TOKEN_FILE_SIZE
:
1027 snprintf(buf
, buf_size
, "%ld", id3
->filesize
/ 1024);
1030 case WPS_TOKEN_FILE_VBR
:
1031 return id3
->vbr
? "(avg)" : NULL
;
1033 case WPS_TOKEN_FILE_DIRECTORY
:
1034 return get_dir(buf
, buf_size
, id3
->path
, token
->value
.i
);
1036 case WPS_TOKEN_BATTERY_PERCENT
:
1038 int l
= battery_level();
1042 limit
= MAX(limit
, 2);
1044 /* First enum is used for "unknown level". */
1045 *intval
= (limit
- 1) * l
/ 100 + 2;
1052 snprintf(buf
, buf_size
, "%d", l
);
1059 case WPS_TOKEN_BATTERY_VOLTS
:
1061 unsigned int v
= battery_voltage();
1062 snprintf(buf
, buf_size
, "%d.%02d", v
/ 1000, (v
% 1000) / 10);
1066 case WPS_TOKEN_BATTERY_TIME
:
1068 int t
= battery_time();
1070 snprintf(buf
, buf_size
, "%dh %dm", t
/ 60, t
% 60);
1077 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED
:
1079 if(charger_input_state
==CHARGER
)
1085 #if CONFIG_CHARGING >= CHARGING_MONITOR
1086 case WPS_TOKEN_BATTERY_CHARGING
:
1088 if (charge_state
== CHARGING
|| charge_state
== TOPOFF
) {
1095 case WPS_TOKEN_BATTERY_SLEEPTIME
:
1097 if (get_sleep_timer() == 0)
1101 format_time(buf
, buf_size
, get_sleep_timer() * 1000);
1106 case WPS_TOKEN_PLAYBACK_STATUS
:
1108 int status
= audio_status();
1110 if (status
== AUDIO_STATUS_PLAY
)
1112 if (wps_fading_out
||
1113 (status
& AUDIO_STATUS_PAUSE
&& !status_get_ffmode()))
1115 if (status_get_ffmode() == STATUS_FASTFORWARD
)
1117 if (status_get_ffmode() == STATUS_FASTBACKWARD
)
1124 snprintf(buf
, buf_size
, "%d", mode
-1);
1128 case WPS_TOKEN_REPEAT_MODE
:
1130 *intval
= global_settings
.repeat_mode
+ 1;
1131 snprintf(buf
, buf_size
, "%d", global_settings
.repeat_mode
);
1134 case WPS_TOKEN_RTC_12HOUR_CFG
:
1136 *intval
= global_settings
.timeformat
+ 1;
1137 snprintf(buf
, buf_size
, "%d", global_settings
.timeformat
);
1140 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1141 /* d: day of month (01..31) */
1142 snprintf(buf
, buf_size
, "%02d", tm
->tm_mday
);
1145 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1146 /* e: day of month, blank padded ( 1..31) */
1147 snprintf(buf
, buf_size
, "%2d", tm
->tm_mday
);
1150 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1151 /* H: hour (00..23) */
1152 snprintf(buf
, buf_size
, "%02d", tm
->tm_hour
);
1155 case WPS_TOKEN_RTC_HOUR_24
:
1156 /* k: hour ( 0..23) */
1157 snprintf(buf
, buf_size
, "%2d", tm
->tm_hour
);
1160 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1161 /* I: hour (01..12) */
1162 snprintf(buf
, buf_size
, "%02d",
1163 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1166 case WPS_TOKEN_RTC_HOUR_12
:
1167 /* l: hour ( 1..12) */
1168 snprintf(buf
, buf_size
, "%2d",
1169 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1172 case WPS_TOKEN_RTC_MONTH
:
1173 /* m: month (01..12) */
1175 *intval
= tm
->tm_mon
+ 1;
1176 snprintf(buf
, buf_size
, "%02d", tm
->tm_mon
+ 1);
1179 case WPS_TOKEN_RTC_MINUTE
:
1180 /* M: minute (00..59) */
1181 snprintf(buf
, buf_size
, "%02d", tm
->tm_min
);
1184 case WPS_TOKEN_RTC_SECOND
:
1185 /* S: second (00..59) */
1186 snprintf(buf
, buf_size
, "%02d", tm
->tm_sec
);
1189 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1190 /* y: last two digits of year (00..99) */
1191 snprintf(buf
, buf_size
, "%02d", tm
->tm_year
% 100);
1194 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1195 /* Y: year (1970...) */
1196 snprintf(buf
, buf_size
, "%04d", tm
->tm_year
+ 1900);
1199 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1200 /* p: upper case AM or PM indicator */
1201 return tm
->tm_hour
/12 == 0 ? "AM" : "PM";
1203 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1204 /* P: lower case am or pm indicator */
1205 return tm
->tm_hour
/12 == 0 ? "am" : "pm";
1207 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1208 /* a: abbreviated weekday name (Sun..Sat) */
1209 return str(LANG_WEEKDAY_SUNDAY
+ tm
->tm_wday
);
1211 case WPS_TOKEN_RTC_MONTH_NAME
:
1212 /* b: abbreviated month name (Jan..Dec) */
1213 return str(LANG_MONTH_JANUARY
+ tm
->tm_mon
);
1215 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1216 /* u: day of week (1..7); 1 is Monday */
1218 *intval
= (tm
->tm_wday
== 0) ? 7 : tm
->tm_wday
;
1219 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
+ 1);
1222 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1223 /* w: day of week (0..6); 0 is Sunday */
1225 *intval
= tm
->tm_wday
+ 1;
1226 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
);
1229 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1230 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1231 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1232 case WPS_TOKEN_RTC_HOUR_24
:
1233 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1234 case WPS_TOKEN_RTC_HOUR_12
:
1235 case WPS_TOKEN_RTC_MONTH
:
1236 case WPS_TOKEN_RTC_MINUTE
:
1237 case WPS_TOKEN_RTC_SECOND
:
1238 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1239 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1240 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1242 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1244 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1245 case WPS_TOKEN_RTC_MONTH_NAME
:
1247 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1248 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1252 #ifdef HAVE_LCD_CHARCELLS
1253 case WPS_TOKEN_PROGRESSBAR
:
1255 char *end
= utf8encode(data
->wps_progress_pat
[0], buf
);
1260 case WPS_TOKEN_PLAYER_PROGRESSBAR
:
1263 /* we need 11 characters (full line) for
1265 strncpy(buf
, " ", buf_size
);
1269 /* Tell the user if we have an OldPlayer */
1270 strncpy(buf
, " <Old LCD> ", buf_size
);
1275 #ifdef HAVE_TAGCACHE
1276 case WPS_TOKEN_DATABASE_PLAYCOUNT
:
1278 *intval
= id3
->playcount
+ 1;
1280 snprintf(buf
, buf_size
, "%ld", id3
->playcount
);
1283 case WPS_TOKEN_DATABASE_RATING
:
1285 *intval
= id3
->rating
+ 1;
1287 snprintf(buf
, buf_size
, "%d", id3
->rating
);
1290 case WPS_TOKEN_DATABASE_AUTOSCORE
:
1292 *intval
= id3
->score
+ 1;
1294 snprintf(buf
, buf_size
, "%d", id3
->score
);
1298 #if (CONFIG_CODEC == SWCODEC)
1299 case WPS_TOKEN_CROSSFADE
:
1301 *intval
= global_settings
.crossfade
+ 1;
1302 snprintf(buf
, buf_size
, "%d", global_settings
.crossfade
);
1305 case WPS_TOKEN_REPLAYGAIN
:
1309 if (global_settings
.replaygain
== 0)
1314 get_replaygain_mode(id3
->track_gain_string
!= NULL
,
1315 id3
->album_gain_string
!= NULL
);
1317 val
= 6; /* no tag */
1321 if (global_settings
.replaygain_type
== REPLAYGAIN_SHUFFLE
)
1336 strncpy(buf
, id3
->track_gain_string
, buf_size
);
1340 strncpy(buf
, id3
->album_gain_string
, buf_size
);
1345 #endif /* (CONFIG_CODEC == SWCODEC) */
1347 #if (CONFIG_CODEC != MAS3507D)
1348 case WPS_TOKEN_SOUND_PITCH
:
1350 int val
= sound_get_pitch();
1351 snprintf(buf
, buf_size
, "%d.%d",
1352 val
/ 10, val
% 10);
1357 case WPS_TOKEN_MAIN_HOLD
:
1358 #ifdef HAS_BUTTON_HOLD
1361 if (is_keys_locked())
1362 #endif /*hold switch or softlock*/
1367 #ifdef HAS_REMOTE_BUTTON_HOLD
1368 case WPS_TOKEN_REMOTE_HOLD
:
1369 if (remote_button_hold())
1375 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1376 case WPS_TOKEN_VLED_HDD
:
1382 case WPS_TOKEN_BUTTON_VOLUME
:
1383 if (data
->button_time_volume
&&
1384 TIME_BEFORE(current_tick
, data
->button_time_volume
+
1385 token
->value
.i
* TIMEOUT_UNIT
))
1389 case WPS_TOKEN_SETTING
:
1393 /* Handle contionals */
1394 const struct settings_list
*s
= settings
+token
->value
.i
;
1395 switch (s
->flags
&F_T_MASK
)
1400 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1401 /* shouldn't overflow since colors are stored
1403 * but this is pretty useless anyway */
1404 *intval
= *(int*)s
->setting
+ 1;
1405 else if (s
->cfg_vals
== NULL
)
1406 /* %?St|name|<1st choice|2nd choice|...> */
1407 *intval
= (*(int*)s
->setting
-s
->int_setting
->min
)
1408 /s
->int_setting
->step
+ 1;
1410 /* %?St|name|<1st choice|2nd choice|...> */
1411 /* Not sure about this one. cfg_name/vals are
1412 * indexed from 0 right? */
1413 *intval
= *(int*)s
->setting
+ 1;
1416 /* %?St|name|<if true|if false> */
1417 *intval
= *(bool*)s
->setting
?1:2;
1420 /* %?St|name|<if non empty string|if empty>
1421 * The string's emptyness discards the setting's
1422 * prefix and suffix */
1423 *intval
= ((char*)s
->setting
)[0]?1:2;
1426 /* This shouldn't happen ... but you never know */
1431 cfg_to_string(token
->value
.i
,buf
,buf_size
);
1440 /* Return the index to the end token for the conditional token at index.
1441 The conditional token can be either a start token or a separator
1442 (i.e. option) token.
1444 static int find_conditional_end(struct wps_data
*data
, int index
)
1447 while (data
->tokens
[ret
].type
!= WPS_TOKEN_CONDITIONAL_END
)
1448 ret
= data
->tokens
[ret
].value
.i
;
1450 /* ret now is the index to the end token for the conditional. */
1454 /* Evaluate the conditional that is at *token_index and return whether a skip
1455 has ocurred. *token_index is updated with the new position.
1457 static bool evaluate_conditional(struct gui_wps
*gwps
, int *token_index
)
1462 struct wps_data
*data
= gwps
->data
;
1465 int cond_index
= *token_index
;
1468 unsigned char num_options
= data
->tokens
[cond_index
].value
.i
& 0xFF;
1469 unsigned char prev_val
= (data
->tokens
[cond_index
].value
.i
& 0xFF00) >> 8;
1471 /* treat ?xx<true> constructs as if they had 2 options. */
1472 if (num_options
< 2)
1475 int intval
= num_options
;
1476 /* get_token_value needs to know the number of options in the enum */
1477 value
= get_token_value(gwps
, &data
->tokens
[cond_index
+ 1],
1478 result
, sizeof(result
), &intval
);
1480 /* intval is now the number of the enum option we want to read,
1481 starting from 1. If intval is -1, we check if value is empty. */
1483 intval
= (value
&& *value
) ? 1 : num_options
;
1484 else if (intval
> num_options
|| intval
< 1)
1485 intval
= num_options
;
1487 data
->tokens
[cond_index
].value
.i
= (intval
<< 8) + num_options
;
1489 /* skip to the appropriate enum case */
1490 int next
= cond_index
+ 2;
1491 for (i
= 1; i
< intval
; i
++)
1493 next
= data
->tokens
[next
].value
.i
;
1495 *token_index
= next
;
1497 if (prev_val
== intval
)
1499 /* Same conditional case as previously. Return without clearing the
1504 cond_end
= find_conditional_end(data
, cond_index
+ 2);
1505 for (i
= cond_index
+ 3; i
< cond_end
; i
++)
1507 #ifdef HAVE_LCD_BITMAP
1508 /* clear all pictures in the conditional and nested ones */
1509 if (data
->tokens
[i
].type
== WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
)
1510 clear_image_pos(gwps
, data
->tokens
[i
].value
.i
& 0xFF);
1512 #ifdef HAVE_ALBUMART
1513 if (data
->tokens
[i
].type
== WPS_TOKEN_ALBUMART_DISPLAY
)
1514 draw_album_art(gwps
, audio_current_aa_hid(), true);
1521 /* Read a (sub)line to the given alignment format buffer.
1522 linebuf is the buffer where the data is actually stored.
1523 align is the alignment format that'll be used to display the text.
1524 The return value indicates whether the line needs to be updated.
1526 static bool get_line(struct gui_wps
*gwps
,
1527 int line
, int subline
,
1528 struct align_pos
*align
,
1532 struct wps_data
*data
= gwps
->data
;
1535 char *buf
= linebuf
; /* will always point to the writing position */
1536 char *linebuf_end
= linebuf
+ linebuf_size
- 1;
1537 int i
, last_token_idx
;
1538 bool update
= false;
1540 /* alignment-related variables */
1542 char* cur_align_start
;
1543 cur_align_start
= buf
;
1544 cur_align
= WPS_ALIGN_LEFT
;
1546 align
->center
= NULL
;
1547 align
->right
= NULL
;
1549 /* Process all tokens of the desired subline */
1550 last_token_idx
= wps_last_token_index(data
, line
, subline
);
1551 for (i
= wps_first_token_index(data
, line
, subline
);
1552 i
<= last_token_idx
; i
++)
1554 switch(data
->tokens
[i
].type
)
1556 case WPS_TOKEN_CONDITIONAL
:
1557 /* place ourselves in the right conditional case */
1558 update
|= evaluate_conditional(gwps
, &i
);
1561 case WPS_TOKEN_CONDITIONAL_OPTION
:
1562 /* we've finished in the curent conditional case,
1563 skip to the end of the conditional structure */
1564 i
= find_conditional_end(data
, i
);
1567 #ifdef HAVE_LCD_BITMAP
1568 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
:
1570 struct gui_img
*img
= data
->img
;
1571 int n
= data
->tokens
[i
].value
.i
& 0xFF;
1572 int subimage
= data
->tokens
[i
].value
.i
>> 8;
1574 if (n
>= 0 && n
< MAX_IMAGES
&& img
[n
].loaded
)
1575 img
[n
].display
= subimage
;
1580 case WPS_TOKEN_ALIGN_LEFT
:
1581 case WPS_TOKEN_ALIGN_CENTER
:
1582 case WPS_TOKEN_ALIGN_RIGHT
:
1583 /* remember where the current aligned text started */
1586 case WPS_ALIGN_LEFT
:
1587 align
->left
= cur_align_start
;
1590 case WPS_ALIGN_CENTER
:
1591 align
->center
= cur_align_start
;
1594 case WPS_ALIGN_RIGHT
:
1595 align
->right
= cur_align_start
;
1598 /* start a new alignment */
1599 switch (data
->tokens
[i
].type
)
1601 case WPS_TOKEN_ALIGN_LEFT
:
1602 cur_align
= WPS_ALIGN_LEFT
;
1604 case WPS_TOKEN_ALIGN_CENTER
:
1605 cur_align
= WPS_ALIGN_CENTER
;
1607 case WPS_TOKEN_ALIGN_RIGHT
:
1608 cur_align
= WPS_ALIGN_RIGHT
;
1614 cur_align_start
= buf
;
1616 case WPS_VIEWPORT_ENABLE
:
1618 char label
= data
->tokens
[i
].value
.i
;
1620 char temp
= VP_DRAW_HIDEABLE
;
1621 for(j
=0;j
<data
->num_viewports
;j
++)
1623 temp
= VP_DRAW_HIDEABLE
;
1624 if ((data
->viewports
[j
].hidden_flags
&VP_DRAW_HIDEABLE
) &&
1625 (data
->viewports
[j
].label
== label
))
1627 if (data
->viewports
[j
].hidden_flags
&VP_DRAW_WASHIDDEN
)
1628 temp
|= VP_DRAW_WASHIDDEN
;
1629 data
->viewports
[j
].hidden_flags
= temp
;
1636 /* get the value of the tag and copy it to the buffer */
1637 const char *value
= get_token_value(gwps
, &data
->tokens
[i
],
1638 temp_buf
, sizeof(temp_buf
), NULL
);
1642 while (*value
&& (buf
< linebuf_end
))
1650 /* close the current alignment */
1653 case WPS_ALIGN_LEFT
:
1654 align
->left
= cur_align_start
;
1657 case WPS_ALIGN_CENTER
:
1658 align
->center
= cur_align_start
;
1661 case WPS_ALIGN_RIGHT
:
1662 align
->right
= cur_align_start
;
1669 static void get_subline_timeout(struct gui_wps
*gwps
, int line
, int subline
)
1671 struct wps_data
*data
= gwps
->data
;
1673 int subline_idx
= wps_subline_index(data
, line
, subline
);
1674 int last_token_idx
= wps_last_token_index(data
, line
, subline
);
1676 data
->sublines
[subline_idx
].time_mult
= DEFAULT_SUBLINE_TIME_MULTIPLIER
;
1678 for (i
= wps_first_token_index(data
, line
, subline
);
1679 i
<= last_token_idx
; i
++)
1681 switch(data
->tokens
[i
].type
)
1683 case WPS_TOKEN_CONDITIONAL
:
1684 /* place ourselves in the right conditional case */
1685 evaluate_conditional(gwps
, &i
);
1688 case WPS_TOKEN_CONDITIONAL_OPTION
:
1689 /* we've finished in the curent conditional case,
1690 skip to the end of the conditional structure */
1691 i
= find_conditional_end(data
, i
);
1694 case WPS_TOKEN_SUBLINE_TIMEOUT
:
1695 data
->sublines
[subline_idx
].time_mult
= data
->tokens
[i
].value
.i
;
1704 /* Calculates which subline should be displayed for the specified line
1705 Returns true iff the subline must be refreshed */
1706 static bool update_curr_subline(struct gui_wps
*gwps
, int line
)
1708 struct wps_data
*data
= gwps
->data
;
1710 int search
, search_start
, num_sublines
;
1712 bool new_subline_refresh
;
1713 bool only_one_subline
;
1715 num_sublines
= data
->lines
[line
].num_sublines
;
1716 reset_subline
= (data
->lines
[line
].curr_subline
== SUBLINE_RESET
);
1717 new_subline_refresh
= false;
1718 only_one_subline
= false;
1720 /* if time to advance to next sub-line */
1721 if (TIME_AFTER(current_tick
, data
->lines
[line
].subline_expire_time
- 1) ||
1724 /* search all sublines until the next subline with time > 0
1725 is found or we get back to the subline we started with */
1729 search_start
= data
->lines
[line
].curr_subline
;
1731 for (search
= 0; search
< num_sublines
; search
++)
1733 data
->lines
[line
].curr_subline
++;
1735 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1736 if (data
->lines
[line
].curr_subline
== num_sublines
)
1738 if (data
->lines
[line
].curr_subline
== 1)
1739 only_one_subline
= true;
1740 data
->lines
[line
].curr_subline
= 0;
1743 /* if back where we started after search or
1744 only one subline is defined on the line */
1745 if (((search
> 0) &&
1746 (data
->lines
[line
].curr_subline
== search_start
)) ||
1749 /* no other subline with a time > 0 exists */
1750 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1752 data
->lines
[line
].subline_expire_time
) + 100 * HZ
;
1757 /* get initial time multiplier for this subline */
1758 get_subline_timeout(gwps
, line
, data
->lines
[line
].curr_subline
);
1760 int subline_idx
= wps_subline_index(data
, line
,
1761 data
->lines
[line
].curr_subline
);
1763 /* only use this subline if subline time > 0 */
1764 if (data
->sublines
[subline_idx
].time_mult
> 0)
1766 new_subline_refresh
= true;
1767 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1768 current_tick
: data
->lines
[line
].subline_expire_time
) +
1769 TIMEOUT_UNIT
*data
->sublines
[subline_idx
].time_mult
;
1776 return new_subline_refresh
;
1779 /* Display a line appropriately according to its alignment format.
1780 format_align contains the text, separated between left, center and right.
1781 line is the index of the line on the screen.
1782 scroll indicates whether the line is a scrolling one or not.
1784 static void write_line(struct screen
*display
,
1785 struct align_pos
*format_align
,
1790 int left_width
= 0, left_xpos
;
1791 int center_width
= 0, center_xpos
;
1792 int right_width
= 0, right_xpos
;
1798 /* calculate different string sizes and positions */
1799 display
->getstringsize((unsigned char *)" ", &space_width
, &string_height
);
1800 if (format_align
->left
!= 0) {
1801 display
->getstringsize((unsigned char *)format_align
->left
,
1802 &left_width
, &string_height
);
1805 if (format_align
->right
!= 0) {
1806 display
->getstringsize((unsigned char *)format_align
->right
,
1807 &right_width
, &string_height
);
1810 if (format_align
->center
!= 0) {
1811 display
->getstringsize((unsigned char *)format_align
->center
,
1812 ¢er_width
, &string_height
);
1816 right_xpos
= (display
->getwidth() - right_width
);
1817 center_xpos
= (display
->getwidth() + left_xpos
- center_width
) / 2;
1819 scroll_width
= display
->getwidth() - left_xpos
;
1821 /* Checks for overlapping strings.
1822 If needed the overlapping strings will be merged, separated by a
1825 /* CASE 1: left and centered string overlap */
1826 /* there is a left string, need to merge left and center */
1827 if ((left_width
!= 0 && center_width
!= 0) &&
1828 (left_xpos
+ left_width
+ space_width
> center_xpos
)) {
1829 /* replace the former separator '\0' of left and
1830 center string with a space */
1831 *(--format_align
->center
) = ' ';
1832 /* calculate the new width and position of the merged string */
1833 left_width
= left_width
+ space_width
+ center_width
;
1834 /* there is no centered string anymore */
1837 /* there is no left string, move center to left */
1838 if ((left_width
== 0 && center_width
!= 0) &&
1839 (left_xpos
+ left_width
> center_xpos
)) {
1840 /* move the center string to the left string */
1841 format_align
->left
= format_align
->center
;
1842 /* calculate the new width and position of the string */
1843 left_width
= center_width
;
1844 /* there is no centered string anymore */
1848 /* CASE 2: centered and right string overlap */
1849 /* there is a right string, need to merge center and right */
1850 if ((center_width
!= 0 && right_width
!= 0) &&
1851 (center_xpos
+ center_width
+ space_width
> right_xpos
)) {
1852 /* replace the former separator '\0' of center and
1853 right string with a space */
1854 *(--format_align
->right
) = ' ';
1855 /* move the center string to the right after merge */
1856 format_align
->right
= format_align
->center
;
1857 /* calculate the new width and position of the merged string */
1858 right_width
= center_width
+ space_width
+ right_width
;
1859 right_xpos
= (display
->getwidth() - right_width
);
1860 /* there is no centered string anymore */
1863 /* there is no right string, move center to right */
1864 if ((center_width
!= 0 && right_width
== 0) &&
1865 (center_xpos
+ center_width
> right_xpos
)) {
1866 /* move the center string to the right string */
1867 format_align
->right
= format_align
->center
;
1868 /* calculate the new width and position of the string */
1869 right_width
= center_width
;
1870 right_xpos
= (display
->getwidth() - right_width
);
1871 /* there is no centered string anymore */
1875 /* CASE 3: left and right overlap
1876 There is no center string anymore, either there never
1877 was one or it has been merged in case 1 or 2 */
1878 /* there is a left string, need to merge left and right */
1879 if ((left_width
!= 0 && center_width
== 0 && right_width
!= 0) &&
1880 (left_xpos
+ left_width
+ space_width
> right_xpos
)) {
1881 /* replace the former separator '\0' of left and
1882 right string with a space */
1883 *(--format_align
->right
) = ' ';
1884 /* calculate the new width and position of the string */
1885 left_width
= left_width
+ space_width
+ right_width
;
1886 /* there is no right string anymore */
1889 /* there is no left string, move right to left */
1890 if ((left_width
== 0 && center_width
== 0 && right_width
!= 0) &&
1891 (left_width
> right_xpos
)) {
1892 /* move the right string to the left string */
1893 format_align
->left
= format_align
->right
;
1894 /* calculate the new width and position of the string */
1895 left_width
= right_width
;
1896 /* there is no right string anymore */
1900 ypos
= (line
* string_height
);
1903 if (scroll
&& ((left_width
> scroll_width
) ||
1904 (center_width
> scroll_width
) ||
1905 (right_width
> scroll_width
)))
1907 display
->puts_scroll(0, line
,
1908 (unsigned char *)format_align
->left
);
1912 #ifdef HAVE_LCD_BITMAP
1913 /* clear the line first */
1914 display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1915 display
->fillrect(left_xpos
, ypos
, display
->getwidth(), string_height
);
1916 display
->set_drawmode(DRMODE_SOLID
);
1919 /* Nasty hack: we output an empty scrolling string,
1920 which will reset the scroller for that line */
1921 display
->puts_scroll(0, line
, (unsigned char *)"");
1923 /* print aligned strings */
1924 if (left_width
!= 0)
1926 display
->putsxy(left_xpos
, ypos
,
1927 (unsigned char *)format_align
->left
);
1929 if (center_width
!= 0)
1931 display
->putsxy(center_xpos
, ypos
,
1932 (unsigned char *)format_align
->center
);
1934 if (right_width
!= 0)
1936 display
->putsxy(right_xpos
, ypos
,
1937 (unsigned char *)format_align
->right
);
1942 /* Refresh the WPS according to refresh_mode. */
1943 bool gui_wps_refresh(struct gui_wps
*gwps
,
1945 unsigned char refresh_mode
)
1947 struct wps_data
*data
= gwps
->data
;
1948 struct screen
*display
= gwps
->display
;
1949 struct wps_state
*state
= gwps
->state
;
1951 if(!gwps
|| !data
|| !state
|| !display
)
1954 int v
, line
, i
, subline_idx
;
1955 unsigned char flags
;
1956 char linebuf
[MAX_PATH
];
1957 unsigned char vp_refresh_mode
;
1959 struct align_pos align
;
1961 align
.center
= NULL
;
1964 bool update_line
, new_subline_refresh
;
1966 #ifdef HAVE_LCD_BITMAP
1968 /* to find out wether the peak meter is enabled we
1969 assume it wasn't until we find a line that contains
1970 the peak meter. We can't use peak_meter_enabled itself
1971 because that would mean to turn off the meter thread
1972 temporarily. (That shouldn't matter unless yield
1973 or sleep is called but who knows...)
1975 bool enable_pm
= false;
1979 /* reset to first subline if refresh all flag is set */
1980 if (refresh_mode
== WPS_REFRESH_ALL
)
1982 display
->set_viewport(&data
->viewports
[0].vp
);
1983 display
->clear_viewport();
1985 for (i
= 0; i
<= data
->num_lines
; i
++)
1987 data
->lines
[i
].curr_subline
= SUBLINE_RESET
;
1991 #ifdef HAVE_LCD_CHARCELLS
1992 for (i
= 0; i
< 8; i
++)
1994 if (data
->wps_progress_pat
[i
] == 0)
1995 data
->wps_progress_pat
[i
] = display
->get_locked_pattern();
2001 display
->stop_scroll();
2005 state
->ff_rewind_count
= ffwd_offset
;
2007 /* disable any viewports which are conditionally displayed */
2008 for (v
= 0; v
< data
->num_viewports
; v
++)
2010 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDEABLE
)
2012 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
)
2013 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
2015 data
->viewports
[v
].hidden_flags
|= VP_DRAW_HIDDEN
;
2018 for (v
= 0; v
< data
->num_viewports
; v
++)
2020 struct wps_viewport
*wps_vp
= &(data
->viewports
[v
]);
2021 display
->set_viewport(&wps_vp
->vp
);
2022 vp_refresh_mode
= refresh_mode
;
2024 #ifdef HAVE_LCD_BITMAP
2025 /* Set images to not to be displayed */
2026 for (i
= 0; i
< MAX_IMAGES
; i
++)
2028 data
->img
[i
].display
= -1;
2031 /* dont redraw the viewport if its disabled */
2032 if ((wps_vp
->hidden_flags
&VP_DRAW_HIDDEN
))
2034 if (!(wps_vp
->hidden_flags
&VP_DRAW_WASHIDDEN
))
2035 display
->scroll_stop(&wps_vp
->vp
);
2036 wps_vp
->hidden_flags
|= VP_DRAW_WASHIDDEN
;
2039 else if (((wps_vp
->hidden_flags
&
2040 (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
))
2041 == (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
)))
2043 vp_refresh_mode
= WPS_REFRESH_ALL
;
2044 wps_vp
->hidden_flags
= VP_DRAW_HIDEABLE
;
2046 if (vp_refresh_mode
== WPS_REFRESH_ALL
)
2048 display
->clear_viewport();
2051 for (line
= wps_vp
->first_line
;
2052 line
<= wps_vp
->last_line
; line
++)
2054 memset(linebuf
, 0, sizeof(linebuf
));
2055 update_line
= false;
2057 /* get current subline for the line */
2058 new_subline_refresh
= update_curr_subline(gwps
, line
);
2060 subline_idx
= wps_subline_index(data
, line
,
2061 data
->lines
[line
].curr_subline
);
2062 flags
= data
->sublines
[subline_idx
].line_type
;
2064 if (vp_refresh_mode
== WPS_REFRESH_ALL
|| (flags
& vp_refresh_mode
)
2065 || new_subline_refresh
)
2067 /* get_line tells us if we need to update the line */
2068 update_line
= get_line(gwps
, line
, data
->lines
[line
].curr_subline
,
2069 &align
, linebuf
, sizeof(linebuf
));
2071 #ifdef HAVE_LCD_BITMAP
2073 if (flags
& vp_refresh_mode
& WPS_REFRESH_PEAK_METER
)
2075 /* the peakmeter should be alone on its line */
2076 update_line
= false;
2078 int h
= font_get(wps_vp
->vp
.font
)->height
;
2079 int peak_meter_y
= (line
- wps_vp
->first_line
)* h
;
2081 /* The user might decide to have the peak meter in the last
2082 line so that it is only displayed if no status bar is
2083 visible. If so we neither want do draw nor enable the
2085 if (peak_meter_y
+ h
<= display
->getheight()) {
2086 /* found a line with a peak meter -> remember that we must
2089 peak_meter_enabled
= true;
2090 peak_meter_screen(gwps
->display
, 0, peak_meter_y
,
2091 MIN(h
, display
->getheight() - peak_meter_y
));
2095 peak_meter_enabled
= false;
2099 #else /* HAVE_LCD_CHARCELL */
2102 if (flags
& vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2104 if (data
->full_line_progressbar
)
2105 draw_player_fullbar(gwps
, linebuf
, sizeof(linebuf
));
2107 draw_player_progress(gwps
);
2112 /* conditionals clear the line which means if the %Vd is put into the default
2113 viewport there will be a blank line.
2114 To get around this we dont allow any actual drawing to happen in the
2115 deault vp if other vp's are defined */
2116 ((data
->num_viewports
>1 && v
!=0) || data
->num_viewports
== 1))
2118 if (flags
& WPS_REFRESH_SCROLL
)
2120 /* if the line is a scrolling one we don't want to update
2121 too often, so that it has the time to scroll */
2122 if ((vp_refresh_mode
& WPS_REFRESH_SCROLL
) || new_subline_refresh
)
2123 write_line(display
, &align
, line
- wps_vp
->first_line
, true);
2126 write_line(display
, &align
, line
- wps_vp
->first_line
, false);
2130 #ifdef HAVE_LCD_BITMAP
2132 if (vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2136 draw_progressbar(gwps
, wps_vp
);
2139 /* Now display any images in this viewport */
2140 wps_display_images(gwps
, &wps_vp
->vp
);
2144 #ifdef HAVE_LCD_BITMAP
2145 data
->peak_meter_enabled
= enable_pm
;
2148 /* Restore the default viewport */
2149 display
->set_viewport(NULL
);
2153 #ifdef HAVE_BACKLIGHT
2154 if (global_settings
.caption_backlight
&& state
->id3
)
2156 /* turn on backlight n seconds before track ends, and turn it off n
2157 seconds into the new track. n == backlight_timeout, or 5s */
2158 int n
= global_settings
.backlight_timeout
* 1000;
2161 n
= 5000; /* use 5s if backlight is always on or off */
2163 if (((state
->id3
->elapsed
< 1000) ||
2164 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2165 (state
->paused
== false))
2169 #ifdef HAVE_REMOTE_LCD
2170 if (global_settings
.remote_caption_backlight
&& state
->id3
)
2172 /* turn on remote backlight n seconds before track ends, and turn it
2173 off n seconds into the new track. n == remote_backlight_timeout,
2175 int n
= global_settings
.remote_backlight_timeout
* 1000;
2178 n
= 5000; /* use 5s if backlight is always on or off */
2180 if (((state
->id3
->elapsed
< 1000) ||
2181 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2182 (state
->paused
== false))
2183 remote_backlight_on();
2186 /* force a bars update if they are being displayed */
2187 viewportmanager_draw_statusbars(NULL
);