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 progressbar
*pb
)
481 struct screen
*display
= gwps
->display
;
482 struct wps_state
*state
= gwps
->state
;
483 if (pb
->have_bitmap_pb
)
484 gui_bitmap_scrollbar_draw(display
, pb
->bm
,
485 pb
->x
, pb
->y
, pb
->width
, pb
->bm
.height
,
486 state
->id3
->length
? state
->id3
->length
: 1, 0,
487 state
->id3
->length
? state
->id3
->elapsed
488 + state
->ff_rewind_count
: 0,
491 gui_scrollbar_draw(display
, pb
->x
, pb
->y
, pb
->width
, pb
->height
,
492 state
->id3
->length
? state
->id3
->length
: 1, 0,
493 state
->id3
->length
? state
->id3
->elapsed
494 + state
->ff_rewind_count
: 0,
496 #ifdef AB_REPEAT_ENABLE
497 if ( ab_repeat_mode_enabled() && state
->id3
->length
!= 0 )
498 ab_draw_markers(display
, state
->id3
->length
,
499 pb
->x
, pb
->x
+ pb
->width
, pb
->y
, pb
->height
);
502 if ( cuesheet_is_enabled() && state
->id3
->cuesheet_type
)
503 cue_draw_markers(display
, state
->id3
->length
,
504 pb
->x
, pb
->x
+ pb
->width
, pb
->y
+1, pb
->height
-2);
507 /* clears the area where the image was shown */
508 static void clear_image_pos(struct gui_wps
*gwps
, int n
)
512 struct wps_data
*data
= gwps
->data
;
513 gwps
->display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
514 gwps
->display
->fillrect(data
->img
[n
].x
, data
->img
[n
].y
,
515 data
->img
[n
].bm
.width
, data
->img
[n
].subimage_height
);
516 gwps
->display
->set_drawmode(DRMODE_SOLID
);
519 static void wps_draw_image(struct gui_wps
*gwps
, int n
, int subimage
)
521 struct screen
*display
= gwps
->display
;
522 struct wps_data
*data
= gwps
->data
;
523 if(data
->img
[n
].always_display
)
524 display
->set_drawmode(DRMODE_FG
);
526 display
->set_drawmode(DRMODE_SOLID
);
529 if(data
->img
[n
].bm
.format
== FORMAT_MONO
) {
531 display
->mono_bitmap_part(data
->img
[n
].bm
.data
,
532 0, data
->img
[n
].subimage_height
* subimage
,
533 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
534 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
535 data
->img
[n
].subimage_height
);
538 display
->transparent_bitmap_part((fb_data
*)data
->img
[n
].bm
.data
,
539 0, data
->img
[n
].subimage_height
* subimage
,
540 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
541 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
542 data
->img
[n
].subimage_height
);
547 static void wps_display_images(struct gui_wps
*gwps
, struct viewport
* vp
)
549 if(!gwps
|| !gwps
->data
|| !gwps
->display
)
553 struct wps_data
*data
= gwps
->data
;
554 struct screen
*display
= gwps
->display
;
556 for (n
= 0; n
< MAX_IMAGES
; n
++)
558 if (data
->img
[n
].loaded
)
560 if (data
->img
[n
].display
>= 0)
562 wps_draw_image(gwps
, n
, data
->img
[n
].display
);
563 } else if (data
->img
[n
].always_display
&& data
->img
[n
].vp
== vp
)
565 wps_draw_image(gwps
, n
, 0);
569 display
->set_drawmode(DRMODE_SOLID
);
572 #else /* HAVE_LCD_CHARCELL */
574 static bool draw_player_progress(struct gui_wps
*gwps
)
576 struct wps_state
*state
= gwps
->state
;
577 struct screen
*display
= gwps
->display
;
578 unsigned char progress_pattern
[7];
585 if (state
->id3
->length
)
586 pos
= 36 * (state
->id3
->elapsed
+ state
->ff_rewind_count
)
587 / state
->id3
->length
;
589 for (i
= 0; i
< 7; i
++, pos
-= 5)
592 progress_pattern
[i
] = 0x1fu
;
594 progress_pattern
[i
] = 0x00u
;
596 progress_pattern
[i
] = 0x1fu
>> pos
;
599 display
->define_pattern(gwps
->data
->wps_progress_pat
[0], progress_pattern
);
603 static void draw_player_fullbar(struct gui_wps
*gwps
, char* buf
, int buf_size
)
605 static const unsigned char numbers
[10][4] = {
606 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
607 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
608 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
609 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
610 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
611 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
612 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
613 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
614 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
615 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
618 struct wps_state
*state
= gwps
->state
;
619 struct screen
*display
= gwps
->display
;
620 struct wps_data
*data
= gwps
->data
;
621 unsigned char progress_pattern
[7];
630 if (!state
->id3
|| buf_size
< 34) /* worst case: 11x UTF-8 char + \0 */
633 time
= state
->id3
->elapsed
+ state
->ff_rewind_count
;
634 if (state
->id3
->length
)
635 pos
= 55 * time
/ state
->id3
->length
;
637 memset(timestr
, 0, sizeof(timestr
));
638 format_time(timestr
, sizeof(timestr
)-2, time
);
639 timestr
[strlen(timestr
)] = ':'; /* always safe */
641 for (i
= 0; i
< 11; i
++, pos
-= 5)
644 memset(progress_pattern
, 0, sizeof(progress_pattern
));
646 if ((digit
= timestr
[time_idx
]))
651 if (timestr
[time_idx
+ 1] == ':') /* ones, left aligned */
653 memcpy(progress_pattern
, numbers
[digit
], 4);
656 else /* tens, shifted right */
658 for (j
= 0; j
< 4; j
++)
659 progress_pattern
[j
] = numbers
[digit
][j
] >> 1;
661 if (time_idx
> 0) /* not the first group, add colon in front */
663 progress_pattern
[1] |= 0x10u
;
664 progress_pattern
[3] |= 0x10u
;
670 progress_pattern
[5] = progress_pattern
[6] = 0x1fu
;
673 if (pos
> 0 && pos
< 5)
676 progress_pattern
[5] = progress_pattern
[6] = (~0x1fu
>> pos
) & 0x1fu
;
679 if (softchar
&& pat_idx
< 8)
681 display
->define_pattern(data
->wps_progress_pat
[pat_idx
],
683 buf
= utf8encode(data
->wps_progress_pat
[pat_idx
], buf
);
687 buf
= utf8encode(' ', buf
);
689 buf
= utf8encode(0xe115, buf
); /* 2/7 _ */
694 #endif /* HAVE_LCD_CHARCELL */
696 static char* get_codectype(const struct mp3entry
* id3
)
698 if (id3
->codectype
< AFMT_NUM_CODECS
) {
699 return (char*)audio_formats
[id3
->codectype
].label
;
705 /* Extract a part from a path.
707 * buf - buffer extract part to.
708 * buf_size - size of buffer.
709 * path - path to extract from.
710 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
711 * parent of parent, etc.
713 * Returns buf if the desired level was found, NULL otherwise.
715 static char* get_dir(char* buf
, int buf_size
, const char* path
, int level
)
718 const char* last_sep
;
721 sep
= path
+ strlen(path
);
736 if (level
|| (last_sep
<= sep
))
739 len
= MIN(last_sep
- sep
, buf_size
- 1);
740 strncpy(buf
, sep
+ 1, len
);
745 /* Return the tag found at index i and write its value in buf.
746 The return value is buf if the tag had a value, or NULL if not.
748 intval is used with conditionals/enums: when this function is called,
749 intval should contain the number of options in the conditional/enum.
750 When this function returns, intval is -1 if the tag is non numeric or,
751 if the tag is numeric, *intval is the enum case we want to go to (between 1
752 and the original value of *intval, inclusive).
753 When not treating a conditional/enum, intval should be NULL.
755 static const char *get_token_value(struct gui_wps
*gwps
,
756 struct wps_token
*token
,
757 char *buf
, int buf_size
,
763 struct wps_data
*data
= gwps
->data
;
764 struct wps_state
*state
= gwps
->state
;
769 struct mp3entry
*id3
;
780 struct tm
* tm
= NULL
;
782 /* if the token is an RTC one, update the time
783 and do the necessary checks */
784 if (token
->type
>= WPS_TOKENS_RTC_BEGIN
785 && token
->type
<= WPS_TOKENS_RTC_END
)
803 case WPS_TOKEN_CHARACTER
:
804 return &(token
->value
.c
);
806 case WPS_TOKEN_STRING
:
807 return data
->strings
[token
->value
.i
];
809 case WPS_TOKEN_TRACK_TIME_ELAPSED
:
810 format_time(buf
, buf_size
,
811 id3
->elapsed
+ state
->ff_rewind_count
);
814 case WPS_TOKEN_TRACK_TIME_REMAINING
:
815 format_time(buf
, buf_size
,
816 id3
->length
- id3
->elapsed
-
817 state
->ff_rewind_count
);
820 case WPS_TOKEN_TRACK_LENGTH
:
821 format_time(buf
, buf_size
, id3
->length
);
824 case WPS_TOKEN_PLAYLIST_ENTRIES
:
825 snprintf(buf
, buf_size
, "%d", playlist_amount());
828 case WPS_TOKEN_PLAYLIST_NAME
:
829 return playlist_name(NULL
, buf
, buf_size
);
831 case WPS_TOKEN_PLAYLIST_POSITION
:
832 snprintf(buf
, buf_size
, "%d", playlist_get_display_index());
835 case WPS_TOKEN_PLAYLIST_SHUFFLE
:
836 if ( global_settings
.playlist_shuffle
)
842 case WPS_TOKEN_VOLUME
:
843 snprintf(buf
, buf_size
, "%d", global_settings
.volume
);
846 if (global_settings
.volume
== sound_min(SOUND_VOLUME
))
850 else if (global_settings
.volume
== 0)
854 else if (global_settings
.volume
> 0)
860 *intval
= (limit
- 3) * (global_settings
.volume
861 - sound_min(SOUND_VOLUME
) - 1)
862 / (-1 - sound_min(SOUND_VOLUME
)) + 2;
867 case WPS_TOKEN_TRACK_ELAPSED_PERCENT
:
868 if (id3
->length
<= 0)
873 *intval
= limit
* (id3
->elapsed
+ state
->ff_rewind_count
)
876 snprintf(buf
, buf_size
, "%d",
877 100*(id3
->elapsed
+ state
->ff_rewind_count
) / id3
->length
);
880 case WPS_TOKEN_METADATA_ARTIST
:
883 case WPS_TOKEN_METADATA_COMPOSER
:
884 return id3
->composer
;
886 case WPS_TOKEN_METADATA_ALBUM
:
889 case WPS_TOKEN_METADATA_ALBUM_ARTIST
:
890 return id3
->albumartist
;
892 case WPS_TOKEN_METADATA_GROUPING
:
893 return id3
->grouping
;
895 case WPS_TOKEN_METADATA_GENRE
:
896 return id3
->genre_string
;
898 case WPS_TOKEN_METADATA_DISC_NUMBER
:
899 if (id3
->disc_string
)
900 return id3
->disc_string
;
902 snprintf(buf
, buf_size
, "%d", id3
->discnum
);
907 case WPS_TOKEN_METADATA_TRACK_NUMBER
:
908 if (id3
->track_string
)
909 return id3
->track_string
;
912 snprintf(buf
, buf_size
, "%d", id3
->tracknum
);
917 case WPS_TOKEN_METADATA_TRACK_TITLE
:
920 case WPS_TOKEN_METADATA_VERSION
:
921 switch (id3
->id3version
)
942 case WPS_TOKEN_METADATA_YEAR
:
943 if( id3
->year_string
)
944 return id3
->year_string
;
947 snprintf(buf
, buf_size
, "%d", id3
->year
);
952 case WPS_TOKEN_METADATA_COMMENT
:
956 case WPS_TOKEN_ALBUMART_DISPLAY
:
957 draw_album_art(gwps
, audio_current_aa_hid(), false);
960 case WPS_TOKEN_ALBUMART_FOUND
:
961 if (audio_current_aa_hid() >= 0) {
967 case WPS_TOKEN_FILE_BITRATE
:
969 snprintf(buf
, buf_size
, "%d", id3
->bitrate
);
974 case WPS_TOKEN_FILE_CODEC
:
977 if(id3
->codectype
== AFMT_UNKNOWN
)
978 *intval
= AFMT_NUM_CODECS
;
980 *intval
= id3
->codectype
;
982 return get_codectype(id3
);
984 case WPS_TOKEN_FILE_FREQUENCY
:
985 snprintf(buf
, buf_size
, "%ld", id3
->frequency
);
988 case WPS_TOKEN_FILE_FREQUENCY_KHZ
:
989 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
990 if ((id3
->frequency
% 1000) < 100)
991 snprintf(buf
, buf_size
, "%ld", id3
->frequency
/ 1000);
993 snprintf(buf
, buf_size
, "%ld.%d",
994 id3
->frequency
/ 1000,
995 (id3
->frequency
% 1000) / 100);
998 case WPS_TOKEN_FILE_NAME
:
999 if (get_dir(buf
, buf_size
, id3
->path
, 0)) {
1000 /* Remove extension */
1001 char* sep
= strrchr(buf
, '.');
1011 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION
:
1012 return get_dir(buf
, buf_size
, id3
->path
, 0);
1014 case WPS_TOKEN_FILE_PATH
:
1017 case WPS_TOKEN_FILE_SIZE
:
1018 snprintf(buf
, buf_size
, "%ld", id3
->filesize
/ 1024);
1021 case WPS_TOKEN_FILE_VBR
:
1022 return id3
->vbr
? "(avg)" : NULL
;
1024 case WPS_TOKEN_FILE_DIRECTORY
:
1025 return get_dir(buf
, buf_size
, id3
->path
, token
->value
.i
);
1027 case WPS_TOKEN_BATTERY_PERCENT
:
1029 int l
= battery_level();
1033 limit
= MAX(limit
, 2);
1035 /* First enum is used for "unknown level". */
1036 *intval
= (limit
- 1) * l
/ 100 + 2;
1043 snprintf(buf
, buf_size
, "%d", l
);
1050 case WPS_TOKEN_BATTERY_VOLTS
:
1052 unsigned int v
= battery_voltage();
1053 snprintf(buf
, buf_size
, "%d.%02d", v
/ 1000, (v
% 1000) / 10);
1057 case WPS_TOKEN_BATTERY_TIME
:
1059 int t
= battery_time();
1061 snprintf(buf
, buf_size
, "%dh %dm", t
/ 60, t
% 60);
1068 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED
:
1070 if(charger_input_state
==CHARGER
)
1076 #if CONFIG_CHARGING >= CHARGING_MONITOR
1077 case WPS_TOKEN_BATTERY_CHARGING
:
1079 if (charge_state
== CHARGING
|| charge_state
== TOPOFF
) {
1086 case WPS_TOKEN_BATTERY_SLEEPTIME
:
1088 if (get_sleep_timer() == 0)
1092 format_time(buf
, buf_size
, get_sleep_timer() * 1000);
1097 case WPS_TOKEN_PLAYBACK_STATUS
:
1099 int status
= audio_status();
1101 if (status
== AUDIO_STATUS_PLAY
)
1103 if (wps_fading_out
||
1104 (status
& AUDIO_STATUS_PAUSE
&& !status_get_ffmode()))
1106 if (status_get_ffmode() == STATUS_FASTFORWARD
)
1108 if (status_get_ffmode() == STATUS_FASTBACKWARD
)
1115 snprintf(buf
, buf_size
, "%d", mode
-1);
1119 case WPS_TOKEN_REPEAT_MODE
:
1121 *intval
= global_settings
.repeat_mode
+ 1;
1122 snprintf(buf
, buf_size
, "%d", global_settings
.repeat_mode
);
1125 case WPS_TOKEN_RTC_12HOUR_CFG
:
1127 *intval
= global_settings
.timeformat
+ 1;
1128 snprintf(buf
, buf_size
, "%d", global_settings
.timeformat
);
1131 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1132 /* d: day of month (01..31) */
1133 snprintf(buf
, buf_size
, "%02d", tm
->tm_mday
);
1136 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1137 /* e: day of month, blank padded ( 1..31) */
1138 snprintf(buf
, buf_size
, "%2d", tm
->tm_mday
);
1141 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1142 /* H: hour (00..23) */
1143 snprintf(buf
, buf_size
, "%02d", tm
->tm_hour
);
1146 case WPS_TOKEN_RTC_HOUR_24
:
1147 /* k: hour ( 0..23) */
1148 snprintf(buf
, buf_size
, "%2d", tm
->tm_hour
);
1151 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1152 /* I: hour (01..12) */
1153 snprintf(buf
, buf_size
, "%02d",
1154 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1157 case WPS_TOKEN_RTC_HOUR_12
:
1158 /* l: hour ( 1..12) */
1159 snprintf(buf
, buf_size
, "%2d",
1160 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1163 case WPS_TOKEN_RTC_MONTH
:
1164 /* m: month (01..12) */
1166 *intval
= tm
->tm_mon
+ 1;
1167 snprintf(buf
, buf_size
, "%02d", tm
->tm_mon
+ 1);
1170 case WPS_TOKEN_RTC_MINUTE
:
1171 /* M: minute (00..59) */
1172 snprintf(buf
, buf_size
, "%02d", tm
->tm_min
);
1175 case WPS_TOKEN_RTC_SECOND
:
1176 /* S: second (00..59) */
1177 snprintf(buf
, buf_size
, "%02d", tm
->tm_sec
);
1180 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1181 /* y: last two digits of year (00..99) */
1182 snprintf(buf
, buf_size
, "%02d", tm
->tm_year
% 100);
1185 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1186 /* Y: year (1970...) */
1187 snprintf(buf
, buf_size
, "%04d", tm
->tm_year
+ 1900);
1190 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1191 /* p: upper case AM or PM indicator */
1192 return tm
->tm_hour
/12 == 0 ? "AM" : "PM";
1194 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1195 /* P: lower case am or pm indicator */
1196 return tm
->tm_hour
/12 == 0 ? "am" : "pm";
1198 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1199 /* a: abbreviated weekday name (Sun..Sat) */
1200 return str(LANG_WEEKDAY_SUNDAY
+ tm
->tm_wday
);
1202 case WPS_TOKEN_RTC_MONTH_NAME
:
1203 /* b: abbreviated month name (Jan..Dec) */
1204 return str(LANG_MONTH_JANUARY
+ tm
->tm_mon
);
1206 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1207 /* u: day of week (1..7); 1 is Monday */
1209 *intval
= (tm
->tm_wday
== 0) ? 7 : tm
->tm_wday
;
1210 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
+ 1);
1213 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1214 /* w: day of week (0..6); 0 is Sunday */
1216 *intval
= tm
->tm_wday
+ 1;
1217 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
);
1220 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1221 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1222 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1223 case WPS_TOKEN_RTC_HOUR_24
:
1224 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1225 case WPS_TOKEN_RTC_HOUR_12
:
1226 case WPS_TOKEN_RTC_MONTH
:
1227 case WPS_TOKEN_RTC_MINUTE
:
1228 case WPS_TOKEN_RTC_SECOND
:
1229 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1230 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1231 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1233 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1235 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1236 case WPS_TOKEN_RTC_MONTH_NAME
:
1238 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1239 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1243 #ifdef HAVE_LCD_CHARCELLS
1244 case WPS_TOKEN_PROGRESSBAR
:
1246 char *end
= utf8encode(data
->wps_progress_pat
[0], buf
);
1251 case WPS_TOKEN_PLAYER_PROGRESSBAR
:
1254 /* we need 11 characters (full line) for
1256 strncpy(buf
, " ", buf_size
);
1260 /* Tell the user if we have an OldPlayer */
1261 strncpy(buf
, " <Old LCD> ", buf_size
);
1266 #ifdef HAVE_TAGCACHE
1267 case WPS_TOKEN_DATABASE_PLAYCOUNT
:
1269 *intval
= id3
->playcount
+ 1;
1271 snprintf(buf
, buf_size
, "%ld", id3
->playcount
);
1274 case WPS_TOKEN_DATABASE_RATING
:
1276 *intval
= id3
->rating
+ 1;
1278 snprintf(buf
, buf_size
, "%d", id3
->rating
);
1281 case WPS_TOKEN_DATABASE_AUTOSCORE
:
1283 *intval
= id3
->score
+ 1;
1285 snprintf(buf
, buf_size
, "%d", id3
->score
);
1289 #if (CONFIG_CODEC == SWCODEC)
1290 case WPS_TOKEN_CROSSFADE
:
1292 *intval
= global_settings
.crossfade
+ 1;
1293 snprintf(buf
, buf_size
, "%d", global_settings
.crossfade
);
1296 case WPS_TOKEN_REPLAYGAIN
:
1300 if (global_settings
.replaygain
== 0)
1305 get_replaygain_mode(id3
->track_gain_string
!= NULL
,
1306 id3
->album_gain_string
!= NULL
);
1308 val
= 6; /* no tag */
1312 if (global_settings
.replaygain_type
== REPLAYGAIN_SHUFFLE
)
1327 strncpy(buf
, id3
->track_gain_string
, buf_size
);
1331 strncpy(buf
, id3
->album_gain_string
, buf_size
);
1336 #endif /* (CONFIG_CODEC == SWCODEC) */
1338 #if (CONFIG_CODEC != MAS3507D)
1339 case WPS_TOKEN_SOUND_PITCH
:
1341 int val
= sound_get_pitch();
1342 snprintf(buf
, buf_size
, "%d.%d",
1343 val
/ 10, val
% 10);
1348 case WPS_TOKEN_MAIN_HOLD
:
1349 #ifdef HAS_BUTTON_HOLD
1352 if (is_keys_locked())
1353 #endif /*hold switch or softlock*/
1358 #ifdef HAS_REMOTE_BUTTON_HOLD
1359 case WPS_TOKEN_REMOTE_HOLD
:
1360 if (remote_button_hold())
1366 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1367 case WPS_TOKEN_VLED_HDD
:
1373 case WPS_TOKEN_BUTTON_VOLUME
:
1374 if (data
->button_time_volume
&&
1375 TIME_BEFORE(current_tick
, data
->button_time_volume
+
1376 token
->value
.i
* TIMEOUT_UNIT
))
1380 case WPS_TOKEN_SETTING
:
1384 /* Handle contionals */
1385 const struct settings_list
*s
= settings
+token
->value
.i
;
1386 switch (s
->flags
&F_T_MASK
)
1391 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1392 /* shouldn't overflow since colors are stored
1394 * but this is pretty useless anyway */
1395 *intval
= *(int*)s
->setting
+ 1;
1396 else if (s
->cfg_vals
== NULL
)
1397 /* %?St|name|<1st choice|2nd choice|...> */
1398 *intval
= (*(int*)s
->setting
-s
->int_setting
->min
)
1399 /s
->int_setting
->step
+ 1;
1401 /* %?St|name|<1st choice|2nd choice|...> */
1402 /* Not sure about this one. cfg_name/vals are
1403 * indexed from 0 right? */
1404 *intval
= *(int*)s
->setting
+ 1;
1407 /* %?St|name|<if true|if false> */
1408 *intval
= *(bool*)s
->setting
?1:2;
1411 /* %?St|name|<if non empty string|if empty>
1412 * The string's emptyness discards the setting's
1413 * prefix and suffix */
1414 *intval
= ((char*)s
->setting
)[0]?1:2;
1417 /* This shouldn't happen ... but you never know */
1422 cfg_to_string(token
->value
.i
,buf
,buf_size
);
1431 /* Return the index to the end token for the conditional token at index.
1432 The conditional token can be either a start token or a separator
1433 (i.e. option) token.
1435 static int find_conditional_end(struct wps_data
*data
, int index
)
1438 while (data
->tokens
[ret
].type
!= WPS_TOKEN_CONDITIONAL_END
)
1439 ret
= data
->tokens
[ret
].value
.i
;
1441 /* ret now is the index to the end token for the conditional. */
1445 /* Evaluate the conditional that is at *token_index and return whether a skip
1446 has ocurred. *token_index is updated with the new position.
1448 static bool evaluate_conditional(struct gui_wps
*gwps
, int *token_index
)
1453 struct wps_data
*data
= gwps
->data
;
1456 int cond_index
= *token_index
;
1459 unsigned char num_options
= data
->tokens
[cond_index
].value
.i
& 0xFF;
1460 unsigned char prev_val
= (data
->tokens
[cond_index
].value
.i
& 0xFF00) >> 8;
1462 /* treat ?xx<true> constructs as if they had 2 options. */
1463 if (num_options
< 2)
1466 int intval
= num_options
;
1467 /* get_token_value needs to know the number of options in the enum */
1468 value
= get_token_value(gwps
, &data
->tokens
[cond_index
+ 1],
1469 result
, sizeof(result
), &intval
);
1471 /* intval is now the number of the enum option we want to read,
1472 starting from 1. If intval is -1, we check if value is empty. */
1474 intval
= (value
&& *value
) ? 1 : num_options
;
1475 else if (intval
> num_options
|| intval
< 1)
1476 intval
= num_options
;
1478 data
->tokens
[cond_index
].value
.i
= (intval
<< 8) + num_options
;
1480 /* skip to the appropriate enum case */
1481 int next
= cond_index
+ 2;
1482 for (i
= 1; i
< intval
; i
++)
1484 next
= data
->tokens
[next
].value
.i
;
1486 *token_index
= next
;
1488 if (prev_val
== intval
)
1490 /* Same conditional case as previously. Return without clearing the
1495 cond_end
= find_conditional_end(data
, cond_index
+ 2);
1496 for (i
= cond_index
+ 3; i
< cond_end
; i
++)
1498 #ifdef HAVE_LCD_BITMAP
1499 /* clear all pictures in the conditional and nested ones */
1500 if (data
->tokens
[i
].type
== WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
)
1501 clear_image_pos(gwps
, data
->tokens
[i
].value
.i
& 0xFF);
1503 #ifdef HAVE_ALBUMART
1504 if (data
->tokens
[i
].type
== WPS_TOKEN_ALBUMART_DISPLAY
)
1505 draw_album_art(gwps
, audio_current_aa_hid(), true);
1512 /* Read a (sub)line to the given alignment format buffer.
1513 linebuf is the buffer where the data is actually stored.
1514 align is the alignment format that'll be used to display the text.
1515 The return value indicates whether the line needs to be updated.
1517 static bool get_line(struct gui_wps
*gwps
,
1518 int line
, int subline
,
1519 struct align_pos
*align
,
1523 struct wps_data
*data
= gwps
->data
;
1526 char *buf
= linebuf
; /* will always point to the writing position */
1527 char *linebuf_end
= linebuf
+ linebuf_size
- 1;
1528 int i
, last_token_idx
;
1529 bool update
= false;
1531 /* alignment-related variables */
1533 char* cur_align_start
;
1534 cur_align_start
= buf
;
1535 cur_align
= WPS_ALIGN_LEFT
;
1537 align
->center
= NULL
;
1538 align
->right
= NULL
;
1540 /* Process all tokens of the desired subline */
1541 last_token_idx
= wps_last_token_index(data
, line
, subline
);
1542 for (i
= wps_first_token_index(data
, line
, subline
);
1543 i
<= last_token_idx
; i
++)
1545 switch(data
->tokens
[i
].type
)
1547 case WPS_TOKEN_CONDITIONAL
:
1548 /* place ourselves in the right conditional case */
1549 update
|= evaluate_conditional(gwps
, &i
);
1552 case WPS_TOKEN_CONDITIONAL_OPTION
:
1553 /* we've finished in the curent conditional case,
1554 skip to the end of the conditional structure */
1555 i
= find_conditional_end(data
, i
);
1558 #ifdef HAVE_LCD_BITMAP
1559 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
:
1561 struct gui_img
*img
= data
->img
;
1562 int n
= data
->tokens
[i
].value
.i
& 0xFF;
1563 int subimage
= data
->tokens
[i
].value
.i
>> 8;
1565 if (n
>= 0 && n
< MAX_IMAGES
&& img
[n
].loaded
)
1566 img
[n
].display
= subimage
;
1571 case WPS_TOKEN_ALIGN_LEFT
:
1572 case WPS_TOKEN_ALIGN_CENTER
:
1573 case WPS_TOKEN_ALIGN_RIGHT
:
1574 /* remember where the current aligned text started */
1577 case WPS_ALIGN_LEFT
:
1578 align
->left
= cur_align_start
;
1581 case WPS_ALIGN_CENTER
:
1582 align
->center
= cur_align_start
;
1585 case WPS_ALIGN_RIGHT
:
1586 align
->right
= cur_align_start
;
1589 /* start a new alignment */
1590 switch (data
->tokens
[i
].type
)
1592 case WPS_TOKEN_ALIGN_LEFT
:
1593 cur_align
= WPS_ALIGN_LEFT
;
1595 case WPS_TOKEN_ALIGN_CENTER
:
1596 cur_align
= WPS_ALIGN_CENTER
;
1598 case WPS_TOKEN_ALIGN_RIGHT
:
1599 cur_align
= WPS_ALIGN_RIGHT
;
1605 cur_align_start
= buf
;
1607 case WPS_VIEWPORT_ENABLE
:
1609 char label
= data
->tokens
[i
].value
.i
;
1611 char temp
= VP_DRAW_HIDEABLE
;
1612 for(j
=0;j
<data
->num_viewports
;j
++)
1614 temp
= VP_DRAW_HIDEABLE
;
1615 if ((data
->viewports
[j
].hidden_flags
&VP_DRAW_HIDEABLE
) &&
1616 (data
->viewports
[j
].label
== label
))
1618 if (data
->viewports
[j
].hidden_flags
&VP_DRAW_WASHIDDEN
)
1619 temp
|= VP_DRAW_WASHIDDEN
;
1620 data
->viewports
[j
].hidden_flags
= temp
;
1627 /* get the value of the tag and copy it to the buffer */
1628 const char *value
= get_token_value(gwps
, &data
->tokens
[i
],
1629 temp_buf
, sizeof(temp_buf
), NULL
);
1633 while (*value
&& (buf
< linebuf_end
))
1641 /* close the current alignment */
1644 case WPS_ALIGN_LEFT
:
1645 align
->left
= cur_align_start
;
1648 case WPS_ALIGN_CENTER
:
1649 align
->center
= cur_align_start
;
1652 case WPS_ALIGN_RIGHT
:
1653 align
->right
= cur_align_start
;
1660 static void get_subline_timeout(struct gui_wps
*gwps
, int line
, int subline
)
1662 struct wps_data
*data
= gwps
->data
;
1664 int subline_idx
= wps_subline_index(data
, line
, subline
);
1665 int last_token_idx
= wps_last_token_index(data
, line
, subline
);
1667 data
->sublines
[subline_idx
].time_mult
= DEFAULT_SUBLINE_TIME_MULTIPLIER
;
1669 for (i
= wps_first_token_index(data
, line
, subline
);
1670 i
<= last_token_idx
; i
++)
1672 switch(data
->tokens
[i
].type
)
1674 case WPS_TOKEN_CONDITIONAL
:
1675 /* place ourselves in the right conditional case */
1676 evaluate_conditional(gwps
, &i
);
1679 case WPS_TOKEN_CONDITIONAL_OPTION
:
1680 /* we've finished in the curent conditional case,
1681 skip to the end of the conditional structure */
1682 i
= find_conditional_end(data
, i
);
1685 case WPS_TOKEN_SUBLINE_TIMEOUT
:
1686 data
->sublines
[subline_idx
].time_mult
= data
->tokens
[i
].value
.i
;
1695 /* Calculates which subline should be displayed for the specified line
1696 Returns true iff the subline must be refreshed */
1697 static bool update_curr_subline(struct gui_wps
*gwps
, int line
)
1699 struct wps_data
*data
= gwps
->data
;
1701 int search
, search_start
, num_sublines
;
1703 bool new_subline_refresh
;
1704 bool only_one_subline
;
1706 num_sublines
= data
->lines
[line
].num_sublines
;
1707 reset_subline
= (data
->lines
[line
].curr_subline
== SUBLINE_RESET
);
1708 new_subline_refresh
= false;
1709 only_one_subline
= false;
1711 /* if time to advance to next sub-line */
1712 if (TIME_AFTER(current_tick
, data
->lines
[line
].subline_expire_time
- 1) ||
1715 /* search all sublines until the next subline with time > 0
1716 is found or we get back to the subline we started with */
1720 search_start
= data
->lines
[line
].curr_subline
;
1722 for (search
= 0; search
< num_sublines
; search
++)
1724 data
->lines
[line
].curr_subline
++;
1726 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1727 if (data
->lines
[line
].curr_subline
== num_sublines
)
1729 if (data
->lines
[line
].curr_subline
== 1)
1730 only_one_subline
= true;
1731 data
->lines
[line
].curr_subline
= 0;
1734 /* if back where we started after search or
1735 only one subline is defined on the line */
1736 if (((search
> 0) &&
1737 (data
->lines
[line
].curr_subline
== search_start
)) ||
1740 /* no other subline with a time > 0 exists */
1741 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1743 data
->lines
[line
].subline_expire_time
) + 100 * HZ
;
1748 /* get initial time multiplier for this subline */
1749 get_subline_timeout(gwps
, line
, data
->lines
[line
].curr_subline
);
1751 int subline_idx
= wps_subline_index(data
, line
,
1752 data
->lines
[line
].curr_subline
);
1754 /* only use this subline if subline time > 0 */
1755 if (data
->sublines
[subline_idx
].time_mult
> 0)
1757 new_subline_refresh
= true;
1758 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1759 current_tick
: data
->lines
[line
].subline_expire_time
) +
1760 TIMEOUT_UNIT
*data
->sublines
[subline_idx
].time_mult
;
1767 return new_subline_refresh
;
1770 /* Display a line appropriately according to its alignment format.
1771 format_align contains the text, separated between left, center and right.
1772 line is the index of the line on the screen.
1773 scroll indicates whether the line is a scrolling one or not.
1775 static void write_line(struct screen
*display
,
1776 struct align_pos
*format_align
,
1781 int left_width
= 0, left_xpos
;
1782 int center_width
= 0, center_xpos
;
1783 int right_width
= 0, right_xpos
;
1789 /* calculate different string sizes and positions */
1790 display
->getstringsize((unsigned char *)" ", &space_width
, &string_height
);
1791 if (format_align
->left
!= 0) {
1792 display
->getstringsize((unsigned char *)format_align
->left
,
1793 &left_width
, &string_height
);
1796 if (format_align
->right
!= 0) {
1797 display
->getstringsize((unsigned char *)format_align
->right
,
1798 &right_width
, &string_height
);
1801 if (format_align
->center
!= 0) {
1802 display
->getstringsize((unsigned char *)format_align
->center
,
1803 ¢er_width
, &string_height
);
1807 right_xpos
= (display
->getwidth() - right_width
);
1808 center_xpos
= (display
->getwidth() + left_xpos
- center_width
) / 2;
1810 scroll_width
= display
->getwidth() - left_xpos
;
1812 /* Checks for overlapping strings.
1813 If needed the overlapping strings will be merged, separated by a
1816 /* CASE 1: left and centered string overlap */
1817 /* there is a left string, need to merge left and center */
1818 if ((left_width
!= 0 && center_width
!= 0) &&
1819 (left_xpos
+ left_width
+ space_width
> center_xpos
)) {
1820 /* replace the former separator '\0' of left and
1821 center string with a space */
1822 *(--format_align
->center
) = ' ';
1823 /* calculate the new width and position of the merged string */
1824 left_width
= left_width
+ space_width
+ center_width
;
1825 /* there is no centered string anymore */
1828 /* there is no left string, move center to left */
1829 if ((left_width
== 0 && center_width
!= 0) &&
1830 (left_xpos
+ left_width
> center_xpos
)) {
1831 /* move the center string to the left string */
1832 format_align
->left
= format_align
->center
;
1833 /* calculate the new width and position of the string */
1834 left_width
= center_width
;
1835 /* there is no centered string anymore */
1839 /* CASE 2: centered and right string overlap */
1840 /* there is a right string, need to merge center and right */
1841 if ((center_width
!= 0 && right_width
!= 0) &&
1842 (center_xpos
+ center_width
+ space_width
> right_xpos
)) {
1843 /* replace the former separator '\0' of center and
1844 right string with a space */
1845 *(--format_align
->right
) = ' ';
1846 /* move the center string to the right after merge */
1847 format_align
->right
= format_align
->center
;
1848 /* calculate the new width and position of the merged string */
1849 right_width
= center_width
+ space_width
+ right_width
;
1850 right_xpos
= (display
->getwidth() - right_width
);
1851 /* there is no centered string anymore */
1854 /* there is no right string, move center to right */
1855 if ((center_width
!= 0 && right_width
== 0) &&
1856 (center_xpos
+ center_width
> right_xpos
)) {
1857 /* move the center string to the right string */
1858 format_align
->right
= format_align
->center
;
1859 /* calculate the new width and position of the string */
1860 right_width
= center_width
;
1861 right_xpos
= (display
->getwidth() - right_width
);
1862 /* there is no centered string anymore */
1866 /* CASE 3: left and right overlap
1867 There is no center string anymore, either there never
1868 was one or it has been merged in case 1 or 2 */
1869 /* there is a left string, need to merge left and right */
1870 if ((left_width
!= 0 && center_width
== 0 && right_width
!= 0) &&
1871 (left_xpos
+ left_width
+ space_width
> right_xpos
)) {
1872 /* replace the former separator '\0' of left and
1873 right string with a space */
1874 *(--format_align
->right
) = ' ';
1875 /* calculate the new width and position of the string */
1876 left_width
= left_width
+ space_width
+ right_width
;
1877 /* there is no right string anymore */
1880 /* there is no left string, move right to left */
1881 if ((left_width
== 0 && center_width
== 0 && right_width
!= 0) &&
1882 (left_width
> right_xpos
)) {
1883 /* move the right string to the left string */
1884 format_align
->left
= format_align
->right
;
1885 /* calculate the new width and position of the string */
1886 left_width
= right_width
;
1887 /* there is no right string anymore */
1891 ypos
= (line
* string_height
);
1894 if (scroll
&& ((left_width
> scroll_width
) ||
1895 (center_width
> scroll_width
) ||
1896 (right_width
> scroll_width
)))
1898 display
->puts_scroll(0, line
,
1899 (unsigned char *)format_align
->left
);
1903 #ifdef HAVE_LCD_BITMAP
1904 /* clear the line first */
1905 display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1906 display
->fillrect(left_xpos
, ypos
, display
->getwidth(), string_height
);
1907 display
->set_drawmode(DRMODE_SOLID
);
1910 /* Nasty hack: we output an empty scrolling string,
1911 which will reset the scroller for that line */
1912 display
->puts_scroll(0, line
, (unsigned char *)"");
1914 /* print aligned strings */
1915 if (left_width
!= 0)
1917 display
->putsxy(left_xpos
, ypos
,
1918 (unsigned char *)format_align
->left
);
1920 if (center_width
!= 0)
1922 display
->putsxy(center_xpos
, ypos
,
1923 (unsigned char *)format_align
->center
);
1925 if (right_width
!= 0)
1927 display
->putsxy(right_xpos
, ypos
,
1928 (unsigned char *)format_align
->right
);
1933 /* Refresh the WPS according to refresh_mode. */
1934 bool gui_wps_refresh(struct gui_wps
*gwps
,
1936 unsigned char refresh_mode
)
1938 struct wps_data
*data
= gwps
->data
;
1939 struct screen
*display
= gwps
->display
;
1940 struct wps_state
*state
= gwps
->state
;
1942 if(!gwps
|| !data
|| !state
|| !display
)
1945 int v
, line
, i
, subline_idx
;
1946 unsigned char flags
;
1947 char linebuf
[MAX_PATH
];
1948 unsigned char vp_refresh_mode
;
1950 struct align_pos align
;
1952 align
.center
= NULL
;
1955 bool update_line
, new_subline_refresh
;
1957 #ifdef HAVE_LCD_BITMAP
1959 /* to find out wether the peak meter is enabled we
1960 assume it wasn't until we find a line that contains
1961 the peak meter. We can't use peak_meter_enabled itself
1962 because that would mean to turn off the meter thread
1963 temporarily. (That shouldn't matter unless yield
1964 or sleep is called but who knows...)
1966 bool enable_pm
= false;
1970 /* reset to first subline if refresh all flag is set */
1971 if (refresh_mode
== WPS_REFRESH_ALL
)
1973 display
->set_viewport(&data
->viewports
[0].vp
);
1974 display
->clear_viewport();
1976 for (i
= 0; i
<= data
->num_lines
; i
++)
1978 data
->lines
[i
].curr_subline
= SUBLINE_RESET
;
1982 #ifdef HAVE_LCD_CHARCELLS
1983 for (i
= 0; i
< 8; i
++)
1985 if (data
->wps_progress_pat
[i
] == 0)
1986 data
->wps_progress_pat
[i
] = display
->get_locked_pattern();
1992 display
->stop_scroll();
1996 state
->ff_rewind_count
= ffwd_offset
;
1998 /* disable any viewports which are conditionally displayed */
1999 for (v
= 0; v
< data
->num_viewports
; v
++)
2001 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDEABLE
)
2003 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
)
2004 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
2006 data
->viewports
[v
].hidden_flags
|= VP_DRAW_HIDDEN
;
2009 for (v
= 0; v
< data
->num_viewports
; v
++)
2011 display
->set_viewport(&data
->viewports
[v
].vp
);
2012 vp_refresh_mode
= refresh_mode
;
2014 #ifdef HAVE_LCD_BITMAP
2015 /* Set images to not to be displayed */
2016 for (i
= 0; i
< MAX_IMAGES
; i
++)
2018 data
->img
[i
].display
= -1;
2021 /* dont redraw the viewport if its disabled */
2022 if ((data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
))
2024 if (!(data
->viewports
[v
].hidden_flags
&VP_DRAW_WASHIDDEN
))
2025 display
->scroll_stop(&data
->viewports
[v
].vp
);
2026 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
2029 else if (((data
->viewports
[v
].hidden_flags
&
2030 (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
))
2031 == (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
)))
2033 vp_refresh_mode
= WPS_REFRESH_ALL
;
2034 data
->viewports
[v
].hidden_flags
= VP_DRAW_HIDEABLE
;
2036 if (vp_refresh_mode
== WPS_REFRESH_ALL
)
2038 display
->clear_viewport();
2041 for (line
= data
->viewports
[v
].first_line
;
2042 line
<= data
->viewports
[v
].last_line
; line
++)
2044 memset(linebuf
, 0, sizeof(linebuf
));
2045 update_line
= false;
2047 /* get current subline for the line */
2048 new_subline_refresh
= update_curr_subline(gwps
, line
);
2050 subline_idx
= wps_subline_index(data
, line
,
2051 data
->lines
[line
].curr_subline
);
2052 flags
= data
->sublines
[subline_idx
].line_type
;
2054 if (vp_refresh_mode
== WPS_REFRESH_ALL
|| (flags
& vp_refresh_mode
)
2055 || new_subline_refresh
)
2057 /* get_line tells us if we need to update the line */
2058 update_line
= get_line(gwps
, line
, data
->lines
[line
].curr_subline
,
2059 &align
, linebuf
, sizeof(linebuf
));
2061 #ifdef HAVE_LCD_BITMAP
2063 if (flags
& vp_refresh_mode
& WPS_REFRESH_PEAK_METER
)
2065 /* the peakmeter should be alone on its line */
2066 update_line
= false;
2068 int h
= font_get(data
->viewports
[v
].vp
.font
)->height
;
2069 int peak_meter_y
= (line
- data
->viewports
[v
].first_line
)* h
;
2071 /* The user might decide to have the peak meter in the last
2072 line so that it is only displayed if no status bar is
2073 visible. If so we neither want do draw nor enable the
2075 if (peak_meter_y
+ h
<= display
->getheight()) {
2076 /* found a line with a peak meter -> remember that we must
2079 peak_meter_enabled
= true;
2080 peak_meter_screen(gwps
->display
, 0, peak_meter_y
,
2081 MIN(h
, display
->getheight() - peak_meter_y
));
2085 peak_meter_enabled
= false;
2089 #else /* HAVE_LCD_CHARCELL */
2092 if (flags
& vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2094 if (data
->full_line_progressbar
)
2095 draw_player_fullbar(gwps
, linebuf
, sizeof(linebuf
));
2097 draw_player_progress(gwps
);
2102 /* conditionals clear the line which means if the %Vd is put into the default
2103 viewport there will be a blank line.
2104 To get around this we dont allow any actual drawing to happen in the
2105 deault vp if other vp's are defined */
2106 ((data
->num_viewports
>1 && v
!=0) || data
->num_viewports
== 1))
2108 if (flags
& WPS_REFRESH_SCROLL
)
2110 /* if the line is a scrolling one we don't want to update
2111 too often, so that it has the time to scroll */
2112 if ((vp_refresh_mode
& WPS_REFRESH_SCROLL
) || new_subline_refresh
)
2113 write_line(display
, &align
, line
- data
->viewports
[v
].first_line
, true);
2116 write_line(display
, &align
, line
- data
->viewports
[v
].first_line
, false);
2120 #ifdef HAVE_LCD_BITMAP
2122 if (vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2124 if (data
->viewports
[v
].pb
)
2125 draw_progressbar(gwps
, data
->viewports
[v
].pb
);
2127 /* Now display any images in this viewport */
2128 wps_display_images(gwps
, &data
->viewports
[v
].vp
);
2132 #ifdef HAVE_LCD_BITMAP
2133 data
->peak_meter_enabled
= enable_pm
;
2136 /* Restore the default viewport */
2137 display
->set_viewport(NULL
);
2141 #ifdef HAVE_BACKLIGHT
2142 if (global_settings
.caption_backlight
&& state
->id3
)
2144 /* turn on backlight n seconds before track ends, and turn it off n
2145 seconds into the new track. n == backlight_timeout, or 5s */
2146 int n
= global_settings
.backlight_timeout
* 1000;
2149 n
= 5000; /* use 5s if backlight is always on or off */
2151 if (((state
->id3
->elapsed
< 1000) ||
2152 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2153 (state
->paused
== false))
2157 #ifdef HAVE_REMOTE_LCD
2158 if (global_settings
.remote_caption_backlight
&& state
->id3
)
2160 /* turn on remote backlight n seconds before track ends, and turn it
2161 off n seconds into the new track. n == remote_backlight_timeout,
2163 int n
= global_settings
.remote_backlight_timeout
* 1000;
2166 n
= 5000; /* use 5s if backlight is always on or off */
2168 if (((state
->id3
->elapsed
< 1000) ||
2169 ((state
->id3
->length
- state
->id3
->elapsed
) < (unsigned)n
)) &&
2170 (state
->paused
== false))
2171 remote_backlight_on();
2174 /* force a bars update if they are being displayed */
2175 viewportmanager_draw_statusbars(NULL
);