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"
46 #include "scrollbar.h"
49 #ifdef HAVE_LCD_BITMAP
50 #include "peakmeter.h"
59 #if CONFIG_CODEC == SWCODEC
65 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
66 /* 3% of 30min file == 54s step size */
67 #define MIN_FF_REWIND_STEP 500
69 /* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds
70 (possibly with a decimal fraction) but stored as integer values.
71 E.g. 2.5 is stored as 25. This means 25 tenth of a second, i.e. 25 units.
73 #define TIMEOUT_UNIT (HZ/10) /* I.e. 0.1 sec */
74 #define DEFAULT_SUBLINE_TIME_MULTIPLIER 20 /* In TIMEOUT_UNIT's */
76 bool wps_fading_out
= false;
77 void fade(bool fade_in
, bool updatewps
)
79 int fp_global_vol
= global_settings
.volume
<< 8;
80 int fp_min_vol
= sound_min(SOUND_VOLUME
) << 8;
81 int fp_step
= (fp_global_vol
- fp_min_vol
) / 30;
83 wps_fading_out
= !fade_in
;
86 int fp_volume
= fp_min_vol
;
88 /* zero out the sound */
89 sound_set_volume(fp_min_vol
>> 8);
91 sleep(HZ
/10); /* let audio thread run */
94 while (fp_volume
< fp_global_vol
- fp_step
) {
96 sound_set_volume(fp_volume
>> 8);
100 gui_wps_redraw(&gui_wps
[i
], 0, WPS_REFRESH_NON_STATIC
);
104 sound_set_volume(global_settings
.volume
);
108 int fp_volume
= fp_global_vol
;
110 while (fp_volume
> fp_min_vol
+ fp_step
) {
111 fp_volume
-= fp_step
;
112 sound_set_volume(fp_volume
>> 8);
116 gui_wps_redraw(&gui_wps
[i
], 0, WPS_REFRESH_NON_STATIC
);
121 wps_fading_out
= false;
122 #if CONFIG_CODEC != SWCODEC
124 /* let audio thread run and wait for the mas to run out of data */
125 while (!mp3_pause_done())
130 /* reset volume to what it was before the fade */
131 sound_set_volume(global_settings
.volume
);
135 bool update_onvol_change(struct gui_wps
* gwps
)
137 gui_wps_redraw(gwps
, 0, WPS_REFRESH_NON_STATIC
);
139 #ifdef HAVE_LCD_CHARCELLS
140 splashf(0, "Vol: %3d dB",
141 sound_val2phys(SOUND_VOLUME
, global_settings
.volume
));
147 bool ffwd_rew(int button
)
149 unsigned int step
= 0; /* current ff/rewind step */
150 unsigned int max_step
= 0; /* maximum ff/rewind step */
151 int ff_rewind_count
= 0; /* current ff/rewind count (in ticks) */
152 int direction
= -1; /* forward=1 or backward=-1 */
156 const long ff_rw_accel
= (global_settings
.ff_rewind_accel
+ 3);
158 if (button
== ACTION_NONE
)
160 status_set_ffmode(0);
167 case ACTION_WPS_SEEKFWD
:
169 case ACTION_WPS_SEEKBACK
:
170 if (wps_state
.ff_rewind
)
174 /* fast forwarding, calc max step relative to end */
175 max_step
= (wps_state
.id3
->length
-
176 (wps_state
.id3
->elapsed
+
178 FF_REWIND_MAX_PERCENT
/ 100;
182 /* rewinding, calc max step relative to start */
183 max_step
= (wps_state
.id3
->elapsed
+ ff_rewind_count
) *
184 FF_REWIND_MAX_PERCENT
/ 100;
187 max_step
= MAX(max_step
, MIN_FF_REWIND_STEP
);
192 ff_rewind_count
+= step
* direction
;
194 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
195 step
+= step
>> ff_rw_accel
;
199 if ( (audio_status() & AUDIO_STATUS_PLAY
) &&
200 wps_state
.id3
&& wps_state
.id3
->length
)
202 if (!wps_state
.paused
)
203 #if (CONFIG_CODEC == SWCODEC)
204 audio_pre_ff_rewind();
208 #if CONFIG_KEYPAD == PLAYER_PAD
210 gui_wps
[i
].display
->stop_scroll();
213 status_set_ffmode(STATUS_FASTFORWARD
);
215 status_set_ffmode(STATUS_FASTBACKWARD
);
217 wps_state
.ff_rewind
= true;
219 step
= 1000 * global_settings
.ff_rewind_min_step
;
226 if ((wps_state
.id3
->elapsed
+ ff_rewind_count
) >
227 wps_state
.id3
->length
)
228 ff_rewind_count
= wps_state
.id3
->length
-
229 wps_state
.id3
->elapsed
;
232 if ((int)(wps_state
.id3
->elapsed
+ ff_rewind_count
) < 0)
233 ff_rewind_count
= -wps_state
.id3
->elapsed
;
237 gui_wps_redraw(&gui_wps
[i
],
238 (wps_state
.wps_time_countup
== false)?
239 ff_rewind_count
:-ff_rewind_count
,
240 WPS_REFRESH_PLAYER_PROGRESS
|
241 WPS_REFRESH_DYNAMIC
);
245 case ACTION_WPS_STOPSEEK
:
246 wps_state
.id3
->elapsed
= wps_state
.id3
->elapsed
+ff_rewind_count
;
247 audio_ff_rewind(wps_state
.id3
->elapsed
);
249 wps_state
.ff_rewind
= false;
250 status_set_ffmode(0);
251 #if (CONFIG_CODEC != SWCODEC)
252 if (!wps_state
.paused
)
255 #ifdef HAVE_LCD_CHARCELLS
257 gui_wps_redraw(&gui_wps
[i
],0, WPS_REFRESH_ALL
);
263 if(default_event_handler(button
) == SYS_USB_CONNECTED
) {
264 status_set_ffmode(0);
271 button
= get_action(CONTEXT_WPS
|ALLOW_SOFTLOCK
,TIMEOUT_BLOCK
);
276 bool gui_wps_display(struct gui_wps
*gwps
)
278 struct screen
*display
= gwps
->display
;
279 struct wps_data
*data
= gwps
->data
;
280 int screen
= display
->screen_type
;
282 /* Update the values in the first (default) viewport - in case the user
283 has modified the statusbar or colour settings */
285 if (display
->depth
> 1)
287 data
->viewports
[0].vp
.fg_pattern
= display
->get_foreground();
288 data
->viewports
[0].vp
.bg_pattern
= display
->get_background();
291 display
->clear_display();
292 if (!data
->wps_loaded
) {
293 if ( !data
->num_tokens
) {
294 /* set the default wps for the main-screen */
295 if(screen
== SCREEN_MAIN
)
298 unload_wps_backdrop();
302 #ifdef HAVE_LCD_BITMAP
303 "%s%?it<%?in<%in. |>%it|%fn>\n"
304 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
305 "%s%?id<%id|%?d1<%d1|(root)>> %?iy<(%iy)|>\n"
307 "%al%pc/%pt%ar[%pp:%pe]\n"
308 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
312 "%s%pp/%pe: %?it<%it|%fn> - %?ia<%ia|%d2> - %?id<%id|%d1>\n"
313 "%pc%?ps<*|/>%pt\n", false);
316 #ifdef HAVE_REMOTE_LCD
317 /* set the default wps for the remote-screen */
318 else if(screen
== SCREEN_REMOTE
)
320 #if LCD_REMOTE_DEPTH > 1
321 unload_remote_wps_backdrop();
325 "%s%?ia<%ia|%?d2<%d2|(root)>>\n"
326 "%s%?it<%?in<%in. |>%it|%fn>\n"
327 "%al%pc/%pt%ar[%pp:%pe]\n"
328 "%fbkBit %?fv<avg|> %?iv<(id3v%iv)|(no id3)>\n"
336 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
337 if (screen
== SCREEN_REMOTE
)
338 show_remote_wps_backdrop();
339 else if (screen
== SCREEN_MAIN
)
345 return gui_wps_redraw(gwps
, 0, WPS_REFRESH_ALL
);
348 bool gui_wps_update(struct gui_wps
*gwps
)
350 struct mp3entry
*id3
= gwps
->state
->id3
;
352 if (cuesheet_is_enabled() && id3
->cuesheet_type
353 && (id3
->elapsed
< curr_cue
->curr_track
->offset
354 || (curr_cue
->curr_track_idx
< curr_cue
->track_count
- 1
355 && id3
->elapsed
>= (curr_cue
->curr_track
+1)->offset
)))
357 /* We've changed tracks within the cuesheet :
358 we need to update the ID3 info and refresh the WPS */
359 gwps
->state
->do_full_update
= true;
360 cue_find_current_track(curr_cue
, id3
->elapsed
);
361 cue_spoof_id3(curr_cue
, id3
);
364 retval
= gui_wps_redraw(gwps
, 0,
365 gwps
->state
->do_full_update
?
366 WPS_REFRESH_ALL
: WPS_REFRESH_NON_STATIC
);
371 void display_keylock_text(bool locked
)
375 gui_wps
[i
].display
->stop_scroll();
377 splash(HZ
, locked
? ID2P(LANG_KEYLOCK_ON
) : ID2P(LANG_KEYLOCK_OFF
));
380 #ifdef HAVE_LCD_BITMAP
382 static void draw_progressbar(struct gui_wps
*gwps
,
383 struct wps_viewport
*wps_vp
)
385 struct screen
*display
= gwps
->display
;
386 struct wps_state
*state
= gwps
->state
;
387 struct progressbar
*pb
= wps_vp
->pb
;
392 int line_height
= font_get(wps_vp
->vp
.font
)->height
;
393 /* center the pb in the line, but only if the line is higher than the pb */
394 int center
= (line_height
-pb
->height
)/2;
395 /* if Y was not set calculate by font height,Y is -line_number-1 */
396 y
= (-y
-1)*line_height
+ (0 > center
? 0 : center
);
399 if (pb
->have_bitmap_pb
)
400 gui_bitmap_scrollbar_draw(display
, pb
->bm
,
401 pb
->x
, y
, pb
->width
, pb
->bm
.height
,
402 state
->id3
->length
? state
->id3
->length
: 1, 0,
403 state
->id3
->length
? state
->id3
->elapsed
404 + state
->ff_rewind_count
: 0,
407 gui_scrollbar_draw(display
, pb
->x
, y
, pb
->width
, pb
->height
,
408 state
->id3
->length
? state
->id3
->length
: 1, 0,
409 state
->id3
->length
? state
->id3
->elapsed
410 + state
->ff_rewind_count
: 0,
412 #ifdef AB_REPEAT_ENABLE
413 if ( ab_repeat_mode_enabled() && state
->id3
->length
!= 0 )
414 ab_draw_markers(display
, state
->id3
->length
,
415 pb
->x
, pb
->x
+ pb
->width
, y
, pb
->height
);
418 if ( cuesheet_is_enabled() && state
->id3
->cuesheet_type
)
419 cue_draw_markers(display
, state
->id3
->length
,
420 pb
->x
, pb
->x
+ pb
->width
, y
+1, pb
->height
-2);
423 /* clears the area where the image was shown */
424 static void clear_image_pos(struct gui_wps
*gwps
, int n
)
428 struct wps_data
*data
= gwps
->data
;
429 gwps
->display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
430 gwps
->display
->fillrect(data
->img
[n
].x
, data
->img
[n
].y
,
431 data
->img
[n
].bm
.width
, data
->img
[n
].subimage_height
);
432 gwps
->display
->set_drawmode(DRMODE_SOLID
);
435 static void wps_draw_image(struct gui_wps
*gwps
, int n
, int subimage
)
437 struct screen
*display
= gwps
->display
;
438 struct wps_data
*data
= gwps
->data
;
439 if(data
->img
[n
].always_display
)
440 display
->set_drawmode(DRMODE_FG
);
442 display
->set_drawmode(DRMODE_SOLID
);
445 if(data
->img
[n
].bm
.format
== FORMAT_MONO
) {
447 display
->mono_bitmap_part(data
->img
[n
].bm
.data
,
448 0, data
->img
[n
].subimage_height
* subimage
,
449 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
450 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
451 data
->img
[n
].subimage_height
);
454 display
->transparent_bitmap_part((fb_data
*)data
->img
[n
].bm
.data
,
455 0, data
->img
[n
].subimage_height
* subimage
,
456 data
->img
[n
].bm
.width
, data
->img
[n
].x
,
457 data
->img
[n
].y
, data
->img
[n
].bm
.width
,
458 data
->img
[n
].subimage_height
);
463 static void wps_display_images(struct gui_wps
*gwps
, struct viewport
* vp
)
465 if(!gwps
|| !gwps
->data
|| !gwps
->display
)
469 struct wps_data
*data
= gwps
->data
;
470 struct screen
*display
= gwps
->display
;
472 for (n
= 0; n
< MAX_IMAGES
; n
++)
474 if (data
->img
[n
].loaded
)
476 if (data
->img
[n
].display
>= 0)
478 wps_draw_image(gwps
, n
, data
->img
[n
].display
);
479 } else if (data
->img
[n
].always_display
&& data
->img
[n
].vp
== vp
)
481 wps_draw_image(gwps
, n
, 0);
485 display
->set_drawmode(DRMODE_SOLID
);
488 #else /* HAVE_LCD_CHARCELL */
490 static bool draw_player_progress(struct gui_wps
*gwps
)
492 struct wps_state
*state
= gwps
->state
;
493 struct screen
*display
= gwps
->display
;
494 unsigned char progress_pattern
[7];
501 if (state
->id3
->length
)
502 pos
= 36 * (state
->id3
->elapsed
+ state
->ff_rewind_count
)
503 / state
->id3
->length
;
505 for (i
= 0; i
< 7; i
++, pos
-= 5)
508 progress_pattern
[i
] = 0x1fu
;
510 progress_pattern
[i
] = 0x00u
;
512 progress_pattern
[i
] = 0x1fu
>> pos
;
515 display
->define_pattern(gwps
->data
->wps_progress_pat
[0], progress_pattern
);
519 static void draw_player_fullbar(struct gui_wps
*gwps
, char* buf
, int buf_size
)
521 static const unsigned char numbers
[10][4] = {
522 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
523 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
524 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
525 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
526 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
527 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
528 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
529 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
530 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
531 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
534 struct wps_state
*state
= gwps
->state
;
535 struct screen
*display
= gwps
->display
;
536 struct wps_data
*data
= gwps
->data
;
537 unsigned char progress_pattern
[7];
546 if (!state
->id3
|| buf_size
< 34) /* worst case: 11x UTF-8 char + \0 */
549 time
= state
->id3
->elapsed
+ state
->ff_rewind_count
;
550 if (state
->id3
->length
)
551 pos
= 55 * time
/ state
->id3
->length
;
553 memset(timestr
, 0, sizeof(timestr
));
554 format_time(timestr
, sizeof(timestr
)-2, time
);
555 timestr
[strlen(timestr
)] = ':'; /* always safe */
557 for (i
= 0; i
< 11; i
++, pos
-= 5)
560 memset(progress_pattern
, 0, sizeof(progress_pattern
));
562 if ((digit
= timestr
[time_idx
]))
567 if (timestr
[time_idx
+ 1] == ':') /* ones, left aligned */
569 memcpy(progress_pattern
, numbers
[digit
], 4);
572 else /* tens, shifted right */
574 for (j
= 0; j
< 4; j
++)
575 progress_pattern
[j
] = numbers
[digit
][j
] >> 1;
577 if (time_idx
> 0) /* not the first group, add colon in front */
579 progress_pattern
[1] |= 0x10u
;
580 progress_pattern
[3] |= 0x10u
;
586 progress_pattern
[5] = progress_pattern
[6] = 0x1fu
;
589 if (pos
> 0 && pos
< 5)
592 progress_pattern
[5] = progress_pattern
[6] = (~0x1fu
>> pos
) & 0x1fu
;
595 if (softchar
&& pat_idx
< 8)
597 display
->define_pattern(data
->wps_progress_pat
[pat_idx
],
599 buf
= utf8encode(data
->wps_progress_pat
[pat_idx
], buf
);
603 buf
= utf8encode(' ', buf
);
605 buf
= utf8encode(0xe115, buf
); /* 2/7 _ */
610 #endif /* HAVE_LCD_CHARCELL */
612 static char* get_codectype(const struct mp3entry
* id3
)
614 if (id3
->codectype
< AFMT_NUM_CODECS
) {
615 return (char*)audio_formats
[id3
->codectype
].label
;
621 /* Extract a part from a path.
623 * buf - buffer extract part to.
624 * buf_size - size of buffer.
625 * path - path to extract from.
626 * level - what to extract. 0 is file name, 1 is parent of file, 2 is
627 * parent of parent, etc.
629 * Returns buf if the desired level was found, NULL otherwise.
631 static char* get_dir(char* buf
, int buf_size
, const char* path
, int level
)
634 const char* last_sep
;
637 sep
= path
+ strlen(path
);
652 if (level
|| (last_sep
<= sep
))
655 len
= MIN(last_sep
- sep
, buf_size
- 1);
656 strncpy(buf
, sep
+ 1, len
);
661 /* Return the tag found at index i and write its value in buf.
662 The return value is buf if the tag had a value, or NULL if not.
664 intval is used with conditionals/enums: when this function is called,
665 intval should contain the number of options in the conditional/enum.
666 When this function returns, intval is -1 if the tag is non numeric or,
667 if the tag is numeric, *intval is the enum case we want to go to (between 1
668 and the original value of *intval, inclusive).
669 When not treating a conditional/enum, intval should be NULL.
671 static const char *get_token_value(struct gui_wps
*gwps
,
672 struct wps_token
*token
,
673 char *buf
, int buf_size
,
679 struct wps_data
*data
= gwps
->data
;
680 struct wps_state
*state
= gwps
->state
;
685 struct mp3entry
*id3
;
696 struct tm
* tm
= NULL
;
698 /* if the token is an RTC one, update the time
699 and do the necessary checks */
700 if (token
->type
>= WPS_TOKENS_RTC_BEGIN
701 && token
->type
<= WPS_TOKENS_RTC_END
)
719 case WPS_TOKEN_CHARACTER
:
720 return &(token
->value
.c
);
722 case WPS_TOKEN_STRING
:
723 return data
->strings
[token
->value
.i
];
725 case WPS_TOKEN_TRACK_TIME_ELAPSED
:
726 format_time(buf
, buf_size
,
727 id3
->elapsed
+ state
->ff_rewind_count
);
730 case WPS_TOKEN_TRACK_TIME_REMAINING
:
731 format_time(buf
, buf_size
,
732 id3
->length
- id3
->elapsed
-
733 state
->ff_rewind_count
);
736 case WPS_TOKEN_TRACK_LENGTH
:
737 format_time(buf
, buf_size
, id3
->length
);
740 case WPS_TOKEN_PLAYLIST_ENTRIES
:
741 snprintf(buf
, buf_size
, "%d", playlist_amount());
744 case WPS_TOKEN_PLAYLIST_NAME
:
745 return playlist_name(NULL
, buf
, buf_size
);
747 case WPS_TOKEN_PLAYLIST_POSITION
:
748 snprintf(buf
, buf_size
, "%d", playlist_get_display_index());
751 case WPS_TOKEN_PLAYLIST_SHUFFLE
:
752 if ( global_settings
.playlist_shuffle
)
758 case WPS_TOKEN_VOLUME
:
759 snprintf(buf
, buf_size
, "%d", global_settings
.volume
);
762 if (global_settings
.volume
== sound_min(SOUND_VOLUME
))
766 else if (global_settings
.volume
== 0)
770 else if (global_settings
.volume
> 0)
776 *intval
= (limit
- 3) * (global_settings
.volume
777 - sound_min(SOUND_VOLUME
) - 1)
778 / (-1 - sound_min(SOUND_VOLUME
)) + 2;
783 case WPS_TOKEN_TRACK_ELAPSED_PERCENT
:
784 if (id3
->length
<= 0)
789 *intval
= limit
* (id3
->elapsed
+ state
->ff_rewind_count
)
792 snprintf(buf
, buf_size
, "%d",
793 100*(id3
->elapsed
+ state
->ff_rewind_count
) / id3
->length
);
796 case WPS_TOKEN_METADATA_ARTIST
:
799 case WPS_TOKEN_METADATA_COMPOSER
:
800 return id3
->composer
;
802 case WPS_TOKEN_METADATA_ALBUM
:
805 case WPS_TOKEN_METADATA_ALBUM_ARTIST
:
806 return id3
->albumartist
;
808 case WPS_TOKEN_METADATA_GROUPING
:
809 return id3
->grouping
;
811 case WPS_TOKEN_METADATA_GENRE
:
812 return id3
->genre_string
;
814 case WPS_TOKEN_METADATA_DISC_NUMBER
:
815 if (id3
->disc_string
)
816 return id3
->disc_string
;
818 snprintf(buf
, buf_size
, "%d", id3
->discnum
);
823 case WPS_TOKEN_METADATA_TRACK_NUMBER
:
824 if (id3
->track_string
)
825 return id3
->track_string
;
828 snprintf(buf
, buf_size
, "%d", id3
->tracknum
);
833 case WPS_TOKEN_METADATA_TRACK_TITLE
:
836 case WPS_TOKEN_METADATA_VERSION
:
837 switch (id3
->id3version
)
858 case WPS_TOKEN_METADATA_YEAR
:
859 if( id3
->year_string
)
860 return id3
->year_string
;
863 snprintf(buf
, buf_size
, "%d", id3
->year
);
868 case WPS_TOKEN_METADATA_COMMENT
:
872 case WPS_TOKEN_ALBUMART_DISPLAY
:
873 draw_album_art(gwps
, audio_current_aa_hid(), false);
876 case WPS_TOKEN_ALBUMART_FOUND
:
877 if (audio_current_aa_hid() >= 0) {
883 case WPS_TOKEN_FILE_BITRATE
:
885 snprintf(buf
, buf_size
, "%d", id3
->bitrate
);
890 case WPS_TOKEN_FILE_CODEC
:
893 if(id3
->codectype
== AFMT_UNKNOWN
)
894 *intval
= AFMT_NUM_CODECS
;
896 *intval
= id3
->codectype
;
898 return get_codectype(id3
);
900 case WPS_TOKEN_FILE_FREQUENCY
:
901 snprintf(buf
, buf_size
, "%ld", id3
->frequency
);
904 case WPS_TOKEN_FILE_FREQUENCY_KHZ
:
905 /* ignore remainders < 100, so 22050 Hz becomes just 22k */
906 if ((id3
->frequency
% 1000) < 100)
907 snprintf(buf
, buf_size
, "%ld", id3
->frequency
/ 1000);
909 snprintf(buf
, buf_size
, "%ld.%d",
910 id3
->frequency
/ 1000,
911 (id3
->frequency
% 1000) / 100);
914 case WPS_TOKEN_FILE_NAME
:
915 if (get_dir(buf
, buf_size
, id3
->path
, 0)) {
916 /* Remove extension */
917 char* sep
= strrchr(buf
, '.');
927 case WPS_TOKEN_FILE_NAME_WITH_EXTENSION
:
928 return get_dir(buf
, buf_size
, id3
->path
, 0);
930 case WPS_TOKEN_FILE_PATH
:
933 case WPS_TOKEN_FILE_SIZE
:
934 snprintf(buf
, buf_size
, "%ld", id3
->filesize
/ 1024);
937 case WPS_TOKEN_FILE_VBR
:
938 return id3
->vbr
? "(avg)" : NULL
;
940 case WPS_TOKEN_FILE_DIRECTORY
:
941 return get_dir(buf
, buf_size
, id3
->path
, token
->value
.i
);
943 case WPS_TOKEN_BATTERY_PERCENT
:
945 int l
= battery_level();
949 limit
= MAX(limit
, 2);
951 /* First enum is used for "unknown level". */
952 *intval
= (limit
- 1) * l
/ 100 + 2;
959 snprintf(buf
, buf_size
, "%d", l
);
966 case WPS_TOKEN_BATTERY_VOLTS
:
968 unsigned int v
= battery_voltage();
969 snprintf(buf
, buf_size
, "%d.%02d", v
/ 1000, (v
% 1000) / 10);
973 case WPS_TOKEN_BATTERY_TIME
:
975 int t
= battery_time();
977 snprintf(buf
, buf_size
, "%dh %dm", t
/ 60, t
% 60);
984 case WPS_TOKEN_BATTERY_CHARGER_CONNECTED
:
986 if(charger_input_state
==CHARGER
)
992 #if CONFIG_CHARGING >= CHARGING_MONITOR
993 case WPS_TOKEN_BATTERY_CHARGING
:
995 if (charge_state
== CHARGING
|| charge_state
== TOPOFF
) {
1002 case WPS_TOKEN_BATTERY_SLEEPTIME
:
1004 if (get_sleep_timer() == 0)
1008 format_time(buf
, buf_size
, get_sleep_timer() * 1000);
1013 case WPS_TOKEN_PLAYBACK_STATUS
:
1015 int status
= audio_status();
1017 if (status
== AUDIO_STATUS_PLAY
)
1019 if (wps_fading_out
||
1020 (status
& AUDIO_STATUS_PAUSE
&& !status_get_ffmode()))
1022 if (status_get_ffmode() == STATUS_FASTFORWARD
)
1024 if (status_get_ffmode() == STATUS_FASTBACKWARD
)
1031 snprintf(buf
, buf_size
, "%d", mode
-1);
1035 case WPS_TOKEN_REPEAT_MODE
:
1037 *intval
= global_settings
.repeat_mode
+ 1;
1038 snprintf(buf
, buf_size
, "%d", global_settings
.repeat_mode
);
1041 case WPS_TOKEN_RTC_PRESENT
:
1049 case WPS_TOKEN_RTC_12HOUR_CFG
:
1051 *intval
= global_settings
.timeformat
+ 1;
1052 snprintf(buf
, buf_size
, "%d", global_settings
.timeformat
);
1055 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1056 /* d: day of month (01..31) */
1057 snprintf(buf
, buf_size
, "%02d", tm
->tm_mday
);
1060 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1061 /* e: day of month, blank padded ( 1..31) */
1062 snprintf(buf
, buf_size
, "%2d", tm
->tm_mday
);
1065 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1066 /* H: hour (00..23) */
1067 snprintf(buf
, buf_size
, "%02d", tm
->tm_hour
);
1070 case WPS_TOKEN_RTC_HOUR_24
:
1071 /* k: hour ( 0..23) */
1072 snprintf(buf
, buf_size
, "%2d", tm
->tm_hour
);
1075 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1076 /* I: hour (01..12) */
1077 snprintf(buf
, buf_size
, "%02d",
1078 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1081 case WPS_TOKEN_RTC_HOUR_12
:
1082 /* l: hour ( 1..12) */
1083 snprintf(buf
, buf_size
, "%2d",
1084 (tm
->tm_hour
% 12 == 0) ? 12 : tm
->tm_hour
% 12);
1087 case WPS_TOKEN_RTC_MONTH
:
1088 /* m: month (01..12) */
1090 *intval
= tm
->tm_mon
+ 1;
1091 snprintf(buf
, buf_size
, "%02d", tm
->tm_mon
+ 1);
1094 case WPS_TOKEN_RTC_MINUTE
:
1095 /* M: minute (00..59) */
1096 snprintf(buf
, buf_size
, "%02d", tm
->tm_min
);
1099 case WPS_TOKEN_RTC_SECOND
:
1100 /* S: second (00..59) */
1101 snprintf(buf
, buf_size
, "%02d", tm
->tm_sec
);
1104 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1105 /* y: last two digits of year (00..99) */
1106 snprintf(buf
, buf_size
, "%02d", tm
->tm_year
% 100);
1109 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1110 /* Y: year (1970...) */
1111 snprintf(buf
, buf_size
, "%04d", tm
->tm_year
+ 1900);
1114 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1115 /* p: upper case AM or PM indicator */
1116 return tm
->tm_hour
/12 == 0 ? "AM" : "PM";
1118 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1119 /* P: lower case am or pm indicator */
1120 return tm
->tm_hour
/12 == 0 ? "am" : "pm";
1122 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1123 /* a: abbreviated weekday name (Sun..Sat) */
1124 return str(LANG_WEEKDAY_SUNDAY
+ tm
->tm_wday
);
1126 case WPS_TOKEN_RTC_MONTH_NAME
:
1127 /* b: abbreviated month name (Jan..Dec) */
1128 return str(LANG_MONTH_JANUARY
+ tm
->tm_mon
);
1130 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1131 /* u: day of week (1..7); 1 is Monday */
1133 *intval
= (tm
->tm_wday
== 0) ? 7 : tm
->tm_wday
;
1134 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
+ 1);
1137 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1138 /* w: day of week (0..6); 0 is Sunday */
1140 *intval
= tm
->tm_wday
+ 1;
1141 snprintf(buf
, buf_size
, "%1d", tm
->tm_wday
);
1144 case WPS_TOKEN_RTC_DAY_OF_MONTH
:
1145 case WPS_TOKEN_RTC_DAY_OF_MONTH_BLANK_PADDED
:
1146 case WPS_TOKEN_RTC_HOUR_24_ZERO_PADDED
:
1147 case WPS_TOKEN_RTC_HOUR_24
:
1148 case WPS_TOKEN_RTC_HOUR_12_ZERO_PADDED
:
1149 case WPS_TOKEN_RTC_HOUR_12
:
1150 case WPS_TOKEN_RTC_MONTH
:
1151 case WPS_TOKEN_RTC_MINUTE
:
1152 case WPS_TOKEN_RTC_SECOND
:
1153 case WPS_TOKEN_RTC_AM_PM_UPPER
:
1154 case WPS_TOKEN_RTC_AM_PM_LOWER
:
1155 case WPS_TOKEN_RTC_YEAR_2_DIGITS
:
1157 case WPS_TOKEN_RTC_YEAR_4_DIGITS
:
1159 case WPS_TOKEN_RTC_WEEKDAY_NAME
:
1160 case WPS_TOKEN_RTC_MONTH_NAME
:
1162 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_MON
:
1163 case WPS_TOKEN_RTC_DAY_OF_WEEK_START_SUN
:
1167 #ifdef HAVE_LCD_CHARCELLS
1168 case WPS_TOKEN_PROGRESSBAR
:
1170 char *end
= utf8encode(data
->wps_progress_pat
[0], buf
);
1175 case WPS_TOKEN_PLAYER_PROGRESSBAR
:
1178 /* we need 11 characters (full line) for
1180 strncpy(buf
, " ", buf_size
);
1184 /* Tell the user if we have an OldPlayer */
1185 strncpy(buf
, " <Old LCD> ", buf_size
);
1190 #ifdef HAVE_TAGCACHE
1191 case WPS_TOKEN_DATABASE_PLAYCOUNT
:
1193 *intval
= id3
->playcount
+ 1;
1195 snprintf(buf
, buf_size
, "%ld", id3
->playcount
);
1198 case WPS_TOKEN_DATABASE_RATING
:
1200 *intval
= id3
->rating
+ 1;
1202 snprintf(buf
, buf_size
, "%d", id3
->rating
);
1205 case WPS_TOKEN_DATABASE_AUTOSCORE
:
1207 *intval
= id3
->score
+ 1;
1209 snprintf(buf
, buf_size
, "%d", id3
->score
);
1213 #if (CONFIG_CODEC == SWCODEC)
1214 case WPS_TOKEN_CROSSFADE
:
1216 *intval
= global_settings
.crossfade
+ 1;
1217 snprintf(buf
, buf_size
, "%d", global_settings
.crossfade
);
1220 case WPS_TOKEN_REPLAYGAIN
:
1224 if (global_settings
.replaygain
== 0)
1229 get_replaygain_mode(id3
->track_gain_string
!= NULL
,
1230 id3
->album_gain_string
!= NULL
);
1232 val
= 6; /* no tag */
1236 if (global_settings
.replaygain_type
== REPLAYGAIN_SHUFFLE
)
1251 strncpy(buf
, id3
->track_gain_string
, buf_size
);
1255 strncpy(buf
, id3
->album_gain_string
, buf_size
);
1260 #endif /* (CONFIG_CODEC == SWCODEC) */
1262 #if (CONFIG_CODEC != MAS3507D)
1263 case WPS_TOKEN_SOUND_PITCH
:
1265 int val
= sound_get_pitch();
1266 snprintf(buf
, buf_size
, "%d.%d",
1267 val
/ 10, val
% 10);
1272 case WPS_TOKEN_MAIN_HOLD
:
1273 #ifdef HAS_BUTTON_HOLD
1276 if (is_keys_locked())
1277 #endif /*hold switch or softlock*/
1282 #ifdef HAS_REMOTE_BUTTON_HOLD
1283 case WPS_TOKEN_REMOTE_HOLD
:
1284 if (remote_button_hold())
1290 #if (CONFIG_LED == LED_VIRTUAL) || defined(HAVE_REMOTE_LCD)
1291 case WPS_TOKEN_VLED_HDD
:
1297 case WPS_TOKEN_BUTTON_VOLUME
:
1298 if (data
->button_time_volume
&&
1299 TIME_BEFORE(current_tick
, data
->button_time_volume
+
1300 token
->value
.i
* TIMEOUT_UNIT
))
1303 case WPS_TOKEN_LASTTOUCH
:
1304 #ifdef HAVE_TOUCHSCREEN
1305 if (TIME_BEFORE(current_tick
, token
->value
.i
* TIMEOUT_UNIT
+
1306 touchscreen_last_touch()))
1311 case WPS_TOKEN_SETTING
:
1315 /* Handle contionals */
1316 const struct settings_list
*s
= settings
+token
->value
.i
;
1317 switch (s
->flags
&F_T_MASK
)
1322 /* %?St|name|<#000000|#000001|...|#FFFFFF> */
1323 /* shouldn't overflow since colors are stored
1325 * but this is pretty useless anyway */
1326 *intval
= *(int*)s
->setting
+ 1;
1327 else if (s
->cfg_vals
== NULL
)
1328 /* %?St|name|<1st choice|2nd choice|...> */
1329 *intval
= (*(int*)s
->setting
-s
->int_setting
->min
)
1330 /s
->int_setting
->step
+ 1;
1332 /* %?St|name|<1st choice|2nd choice|...> */
1333 /* Not sure about this one. cfg_name/vals are
1334 * indexed from 0 right? */
1335 *intval
= *(int*)s
->setting
+ 1;
1338 /* %?St|name|<if true|if false> */
1339 *intval
= *(bool*)s
->setting
?1:2;
1342 /* %?St|name|<if non empty string|if empty>
1343 * The string's emptyness discards the setting's
1344 * prefix and suffix */
1345 *intval
= ((char*)s
->setting
)[0]?1:2;
1348 /* This shouldn't happen ... but you never know */
1353 cfg_to_string(token
->value
.i
,buf
,buf_size
);
1362 /* Return the index to the end token for the conditional token at index.
1363 The conditional token can be either a start token or a separator
1364 (i.e. option) token.
1366 static int find_conditional_end(struct wps_data
*data
, int index
)
1369 while (data
->tokens
[ret
].type
!= WPS_TOKEN_CONDITIONAL_END
)
1370 ret
= data
->tokens
[ret
].value
.i
;
1372 /* ret now is the index to the end token for the conditional. */
1376 /* Evaluate the conditional that is at *token_index and return whether a skip
1377 has ocurred. *token_index is updated with the new position.
1379 static bool evaluate_conditional(struct gui_wps
*gwps
, int *token_index
)
1384 struct wps_data
*data
= gwps
->data
;
1387 int cond_index
= *token_index
;
1390 unsigned char num_options
= data
->tokens
[cond_index
].value
.i
& 0xFF;
1391 unsigned char prev_val
= (data
->tokens
[cond_index
].value
.i
& 0xFF00) >> 8;
1393 /* treat ?xx<true> constructs as if they had 2 options. */
1394 if (num_options
< 2)
1397 int intval
= num_options
;
1398 /* get_token_value needs to know the number of options in the enum */
1399 value
= get_token_value(gwps
, &data
->tokens
[cond_index
+ 1],
1400 result
, sizeof(result
), &intval
);
1402 /* intval is now the number of the enum option we want to read,
1403 starting from 1. If intval is -1, we check if value is empty. */
1405 intval
= (value
&& *value
) ? 1 : num_options
;
1406 else if (intval
> num_options
|| intval
< 1)
1407 intval
= num_options
;
1409 data
->tokens
[cond_index
].value
.i
= (intval
<< 8) + num_options
;
1411 /* skip to the appropriate enum case */
1412 int next
= cond_index
+ 2;
1413 for (i
= 1; i
< intval
; i
++)
1415 next
= data
->tokens
[next
].value
.i
;
1417 *token_index
= next
;
1419 if (prev_val
== intval
)
1421 /* Same conditional case as previously. Return without clearing the
1426 cond_end
= find_conditional_end(data
, cond_index
+ 2);
1427 for (i
= cond_index
+ 3; i
< cond_end
; i
++)
1429 #ifdef HAVE_LCD_BITMAP
1430 /* clear all pictures in the conditional and nested ones */
1431 if (data
->tokens
[i
].type
== WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
)
1432 clear_image_pos(gwps
, data
->tokens
[i
].value
.i
& 0xFF);
1434 #ifdef HAVE_ALBUMART
1435 if (data
->tokens
[i
].type
== WPS_TOKEN_ALBUMART_DISPLAY
)
1436 draw_album_art(gwps
, audio_current_aa_hid(), true);
1443 /* Read a (sub)line to the given alignment format buffer.
1444 linebuf is the buffer where the data is actually stored.
1445 align is the alignment format that'll be used to display the text.
1446 The return value indicates whether the line needs to be updated.
1448 static bool get_line(struct gui_wps
*gwps
,
1449 int line
, int subline
,
1450 struct align_pos
*align
,
1454 struct wps_data
*data
= gwps
->data
;
1457 char *buf
= linebuf
; /* will always point to the writing position */
1458 char *linebuf_end
= linebuf
+ linebuf_size
- 1;
1459 int i
, last_token_idx
;
1460 bool update
= false;
1462 /* alignment-related variables */
1464 char* cur_align_start
;
1465 cur_align_start
= buf
;
1466 cur_align
= WPS_ALIGN_LEFT
;
1468 align
->center
= NULL
;
1469 align
->right
= NULL
;
1471 /* Process all tokens of the desired subline */
1472 last_token_idx
= wps_last_token_index(data
, line
, subline
);
1473 for (i
= wps_first_token_index(data
, line
, subline
);
1474 i
<= last_token_idx
; i
++)
1476 switch(data
->tokens
[i
].type
)
1478 case WPS_TOKEN_CONDITIONAL
:
1479 /* place ourselves in the right conditional case */
1480 update
|= evaluate_conditional(gwps
, &i
);
1483 case WPS_TOKEN_CONDITIONAL_OPTION
:
1484 /* we've finished in the curent conditional case,
1485 skip to the end of the conditional structure */
1486 i
= find_conditional_end(data
, i
);
1489 #ifdef HAVE_LCD_BITMAP
1490 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY
:
1492 struct gui_img
*img
= data
->img
;
1493 int n
= data
->tokens
[i
].value
.i
& 0xFF;
1494 int subimage
= data
->tokens
[i
].value
.i
>> 8;
1496 if (n
>= 0 && n
< MAX_IMAGES
&& img
[n
].loaded
)
1497 img
[n
].display
= subimage
;
1502 case WPS_TOKEN_ALIGN_LEFT
:
1503 case WPS_TOKEN_ALIGN_CENTER
:
1504 case WPS_TOKEN_ALIGN_RIGHT
:
1505 /* remember where the current aligned text started */
1508 case WPS_ALIGN_LEFT
:
1509 align
->left
= cur_align_start
;
1512 case WPS_ALIGN_CENTER
:
1513 align
->center
= cur_align_start
;
1516 case WPS_ALIGN_RIGHT
:
1517 align
->right
= cur_align_start
;
1520 /* start a new alignment */
1521 switch (data
->tokens
[i
].type
)
1523 case WPS_TOKEN_ALIGN_LEFT
:
1524 cur_align
= WPS_ALIGN_LEFT
;
1526 case WPS_TOKEN_ALIGN_CENTER
:
1527 cur_align
= WPS_ALIGN_CENTER
;
1529 case WPS_TOKEN_ALIGN_RIGHT
:
1530 cur_align
= WPS_ALIGN_RIGHT
;
1536 cur_align_start
= buf
;
1538 case WPS_VIEWPORT_ENABLE
:
1540 char label
= data
->tokens
[i
].value
.i
;
1542 char temp
= VP_DRAW_HIDEABLE
;
1543 for(j
=0;j
<data
->num_viewports
;j
++)
1545 temp
= VP_DRAW_HIDEABLE
;
1546 if ((data
->viewports
[j
].hidden_flags
&VP_DRAW_HIDEABLE
) &&
1547 (data
->viewports
[j
].label
== label
))
1549 if (data
->viewports
[j
].hidden_flags
&VP_DRAW_WASHIDDEN
)
1550 temp
|= VP_DRAW_WASHIDDEN
;
1551 data
->viewports
[j
].hidden_flags
= temp
;
1558 /* get the value of the tag and copy it to the buffer */
1559 const char *value
= get_token_value(gwps
, &data
->tokens
[i
],
1560 temp_buf
, sizeof(temp_buf
), NULL
);
1564 while (*value
&& (buf
< linebuf_end
))
1572 /* close the current alignment */
1575 case WPS_ALIGN_LEFT
:
1576 align
->left
= cur_align_start
;
1579 case WPS_ALIGN_CENTER
:
1580 align
->center
= cur_align_start
;
1583 case WPS_ALIGN_RIGHT
:
1584 align
->right
= cur_align_start
;
1591 static void get_subline_timeout(struct gui_wps
*gwps
, int line
, int subline
)
1593 struct wps_data
*data
= gwps
->data
;
1595 int subline_idx
= wps_subline_index(data
, line
, subline
);
1596 int last_token_idx
= wps_last_token_index(data
, line
, subline
);
1598 data
->sublines
[subline_idx
].time_mult
= DEFAULT_SUBLINE_TIME_MULTIPLIER
;
1600 for (i
= wps_first_token_index(data
, line
, subline
);
1601 i
<= last_token_idx
; i
++)
1603 switch(data
->tokens
[i
].type
)
1605 case WPS_TOKEN_CONDITIONAL
:
1606 /* place ourselves in the right conditional case */
1607 evaluate_conditional(gwps
, &i
);
1610 case WPS_TOKEN_CONDITIONAL_OPTION
:
1611 /* we've finished in the curent conditional case,
1612 skip to the end of the conditional structure */
1613 i
= find_conditional_end(data
, i
);
1616 case WPS_TOKEN_SUBLINE_TIMEOUT
:
1617 data
->sublines
[subline_idx
].time_mult
= data
->tokens
[i
].value
.i
;
1626 /* Calculates which subline should be displayed for the specified line
1627 Returns true iff the subline must be refreshed */
1628 static bool update_curr_subline(struct gui_wps
*gwps
, int line
)
1630 struct wps_data
*data
= gwps
->data
;
1632 int search
, search_start
, num_sublines
;
1634 bool new_subline_refresh
;
1635 bool only_one_subline
;
1637 num_sublines
= data
->lines
[line
].num_sublines
;
1638 reset_subline
= (data
->lines
[line
].curr_subline
== SUBLINE_RESET
);
1639 new_subline_refresh
= false;
1640 only_one_subline
= false;
1642 /* if time to advance to next sub-line */
1643 if (TIME_AFTER(current_tick
, data
->lines
[line
].subline_expire_time
- 1) ||
1646 /* search all sublines until the next subline with time > 0
1647 is found or we get back to the subline we started with */
1651 search_start
= data
->lines
[line
].curr_subline
;
1653 for (search
= 0; search
< num_sublines
; search
++)
1655 data
->lines
[line
].curr_subline
++;
1657 /* wrap around if beyond last defined subline or WPS_MAX_SUBLINES */
1658 if (data
->lines
[line
].curr_subline
== num_sublines
)
1660 if (data
->lines
[line
].curr_subline
== 1)
1661 only_one_subline
= true;
1662 data
->lines
[line
].curr_subline
= 0;
1665 /* if back where we started after search or
1666 only one subline is defined on the line */
1667 if (((search
> 0) &&
1668 (data
->lines
[line
].curr_subline
== search_start
)) ||
1671 /* no other subline with a time > 0 exists */
1672 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1674 data
->lines
[line
].subline_expire_time
) + 100 * HZ
;
1679 /* get initial time multiplier for this subline */
1680 get_subline_timeout(gwps
, line
, data
->lines
[line
].curr_subline
);
1682 int subline_idx
= wps_subline_index(data
, line
,
1683 data
->lines
[line
].curr_subline
);
1685 /* only use this subline if subline time > 0 */
1686 if (data
->sublines
[subline_idx
].time_mult
> 0)
1688 new_subline_refresh
= true;
1689 data
->lines
[line
].subline_expire_time
= (reset_subline
?
1690 current_tick
: data
->lines
[line
].subline_expire_time
) +
1691 TIMEOUT_UNIT
*data
->sublines
[subline_idx
].time_mult
;
1698 return new_subline_refresh
;
1701 /* Display a line appropriately according to its alignment format.
1702 format_align contains the text, separated between left, center and right.
1703 line is the index of the line on the screen.
1704 scroll indicates whether the line is a scrolling one or not.
1706 static void write_line(struct screen
*display
,
1707 struct align_pos
*format_align
,
1711 int left_width
= 0, left_xpos
;
1712 int center_width
= 0, center_xpos
;
1713 int right_width
= 0, right_xpos
;
1719 /* calculate different string sizes and positions */
1720 display
->getstringsize((unsigned char *)" ", &space_width
, &string_height
);
1721 if (format_align
->left
!= 0) {
1722 display
->getstringsize((unsigned char *)format_align
->left
,
1723 &left_width
, &string_height
);
1726 if (format_align
->right
!= 0) {
1727 display
->getstringsize((unsigned char *)format_align
->right
,
1728 &right_width
, &string_height
);
1731 if (format_align
->center
!= 0) {
1732 display
->getstringsize((unsigned char *)format_align
->center
,
1733 ¢er_width
, &string_height
);
1737 right_xpos
= (display
->getwidth() - right_width
);
1738 center_xpos
= (display
->getwidth() + left_xpos
- center_width
) / 2;
1740 scroll_width
= display
->getwidth() - left_xpos
;
1742 /* Checks for overlapping strings.
1743 If needed the overlapping strings will be merged, separated by a
1746 /* CASE 1: left and centered string overlap */
1747 /* there is a left string, need to merge left and center */
1748 if ((left_width
!= 0 && center_width
!= 0) &&
1749 (left_xpos
+ left_width
+ space_width
> center_xpos
)) {
1750 /* replace the former separator '\0' of left and
1751 center string with a space */
1752 *(--format_align
->center
) = ' ';
1753 /* calculate the new width and position of the merged string */
1754 left_width
= left_width
+ space_width
+ center_width
;
1755 /* there is no centered string anymore */
1758 /* there is no left string, move center to left */
1759 if ((left_width
== 0 && center_width
!= 0) &&
1760 (left_xpos
+ left_width
> center_xpos
)) {
1761 /* move the center string to the left string */
1762 format_align
->left
= format_align
->center
;
1763 /* calculate the new width and position of the string */
1764 left_width
= center_width
;
1765 /* there is no centered string anymore */
1769 /* CASE 2: centered and right string overlap */
1770 /* there is a right string, need to merge center and right */
1771 if ((center_width
!= 0 && right_width
!= 0) &&
1772 (center_xpos
+ center_width
+ space_width
> right_xpos
)) {
1773 /* replace the former separator '\0' of center and
1774 right string with a space */
1775 *(--format_align
->right
) = ' ';
1776 /* move the center string to the right after merge */
1777 format_align
->right
= format_align
->center
;
1778 /* calculate the new width and position of the merged string */
1779 right_width
= center_width
+ space_width
+ right_width
;
1780 right_xpos
= (display
->getwidth() - right_width
);
1781 /* there is no centered string anymore */
1784 /* there is no right string, move center to right */
1785 if ((center_width
!= 0 && right_width
== 0) &&
1786 (center_xpos
+ center_width
> right_xpos
)) {
1787 /* move the center string to the right string */
1788 format_align
->right
= format_align
->center
;
1789 /* calculate the new width and position of the string */
1790 right_width
= center_width
;
1791 right_xpos
= (display
->getwidth() - right_width
);
1792 /* there is no centered string anymore */
1796 /* CASE 3: left and right overlap
1797 There is no center string anymore, either there never
1798 was one or it has been merged in case 1 or 2 */
1799 /* there is a left string, need to merge left and right */
1800 if ((left_width
!= 0 && center_width
== 0 && right_width
!= 0) &&
1801 (left_xpos
+ left_width
+ space_width
> right_xpos
)) {
1802 /* replace the former separator '\0' of left and
1803 right string with a space */
1804 *(--format_align
->right
) = ' ';
1805 /* calculate the new width and position of the string */
1806 left_width
= left_width
+ space_width
+ right_width
;
1807 /* there is no right string anymore */
1810 /* there is no left string, move right to left */
1811 if ((left_width
== 0 && center_width
== 0 && right_width
!= 0) &&
1812 (left_width
> right_xpos
)) {
1813 /* move the right string to the left string */
1814 format_align
->left
= format_align
->right
;
1815 /* calculate the new width and position of the string */
1816 left_width
= right_width
;
1817 /* there is no right string anymore */
1821 ypos
= (line
* string_height
);
1824 if (scroll
&& ((left_width
> scroll_width
) ||
1825 (center_width
> scroll_width
) ||
1826 (right_width
> scroll_width
)))
1828 display
->puts_scroll(0, line
,
1829 (unsigned char *)format_align
->left
);
1833 #ifdef HAVE_LCD_BITMAP
1834 /* clear the line first */
1835 display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
1836 display
->fillrect(left_xpos
, ypos
, display
->getwidth(), string_height
);
1837 display
->set_drawmode(DRMODE_SOLID
);
1840 /* Nasty hack: we output an empty scrolling string,
1841 which will reset the scroller for that line */
1842 display
->puts_scroll(0, line
, (unsigned char *)"");
1844 /* print aligned strings */
1845 if (left_width
!= 0)
1847 display
->putsxy(left_xpos
, ypos
,
1848 (unsigned char *)format_align
->left
);
1850 if (center_width
!= 0)
1852 display
->putsxy(center_xpos
, ypos
,
1853 (unsigned char *)format_align
->center
);
1855 if (right_width
!= 0)
1857 display
->putsxy(right_xpos
, ypos
,
1858 (unsigned char *)format_align
->right
);
1863 bool gui_wps_redraw(struct gui_wps
*gwps
,
1865 unsigned refresh_mode
)
1867 struct wps_data
*data
= gwps
->data
;
1868 struct screen
*display
= gwps
->display
;
1869 struct wps_state
*state
= gwps
->state
;
1871 if (!data
|| !state
|| !display
)
1874 struct mp3entry
*id3
= state
->id3
;
1879 int v
, line
, i
, subline_idx
;
1881 char linebuf
[MAX_PATH
];
1883 struct align_pos align
;
1885 align
.center
= NULL
;
1888 bool update_line
, new_subline_refresh
;
1890 #ifdef HAVE_LCD_BITMAP
1892 /* to find out wether the peak meter is enabled we
1893 assume it wasn't until we find a line that contains
1894 the peak meter. We can't use peak_meter_enabled itself
1895 because that would mean to turn off the meter thread
1896 temporarily. (That shouldn't matter unless yield
1897 or sleep is called but who knows...)
1899 bool enable_pm
= false;
1903 /* reset to first subline if refresh all flag is set */
1904 if (refresh_mode
== WPS_REFRESH_ALL
)
1906 display
->set_viewport(&data
->viewports
[0].vp
);
1907 display
->clear_viewport();
1909 for (i
= 0; i
<= data
->num_lines
; i
++)
1911 data
->lines
[i
].curr_subline
= SUBLINE_RESET
;
1915 #ifdef HAVE_LCD_CHARCELLS
1916 for (i
= 0; i
< 8; i
++)
1918 if (data
->wps_progress_pat
[i
] == 0)
1919 data
->wps_progress_pat
[i
] = display
->get_locked_pattern();
1923 state
->ff_rewind_count
= ffwd_offset
;
1925 /* disable any viewports which are conditionally displayed */
1926 for (v
= 0; v
< data
->num_viewports
; v
++)
1928 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDEABLE
)
1930 if (data
->viewports
[v
].hidden_flags
&VP_DRAW_HIDDEN
)
1931 data
->viewports
[v
].hidden_flags
|= VP_DRAW_WASHIDDEN
;
1933 data
->viewports
[v
].hidden_flags
|= VP_DRAW_HIDDEN
;
1936 for (v
= 0; v
< data
->num_viewports
; v
++)
1938 struct wps_viewport
*wps_vp
= &(data
->viewports
[v
]);
1939 unsigned vp_refresh_mode
= refresh_mode
;
1940 display
->set_viewport(&wps_vp
->vp
);
1942 #ifdef HAVE_LCD_BITMAP
1943 /* Set images to not to be displayed */
1944 for (i
= 0; i
< MAX_IMAGES
; i
++)
1946 data
->img
[i
].display
= -1;
1949 /* dont redraw the viewport if its disabled */
1950 if ((wps_vp
->hidden_flags
&VP_DRAW_HIDDEN
))
1952 if (!(wps_vp
->hidden_flags
&VP_DRAW_WASHIDDEN
))
1953 display
->scroll_stop(&wps_vp
->vp
);
1954 wps_vp
->hidden_flags
|= VP_DRAW_WASHIDDEN
;
1957 else if (((wps_vp
->hidden_flags
&
1958 (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
))
1959 == (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
)))
1961 vp_refresh_mode
= WPS_REFRESH_ALL
;
1962 wps_vp
->hidden_flags
= VP_DRAW_HIDEABLE
;
1964 if (vp_refresh_mode
== WPS_REFRESH_ALL
)
1966 display
->clear_viewport();
1969 for (line
= wps_vp
->first_line
;
1970 line
<= wps_vp
->last_line
; line
++)
1972 memset(linebuf
, 0, sizeof(linebuf
));
1973 update_line
= false;
1975 /* get current subline for the line */
1976 new_subline_refresh
= update_curr_subline(gwps
, line
);
1978 subline_idx
= wps_subline_index(data
, line
,
1979 data
->lines
[line
].curr_subline
);
1980 flags
= data
->sublines
[subline_idx
].line_type
;
1982 if (vp_refresh_mode
== WPS_REFRESH_ALL
|| (flags
& vp_refresh_mode
)
1983 || new_subline_refresh
)
1985 /* get_line tells us if we need to update the line */
1986 update_line
= get_line(gwps
, line
, data
->lines
[line
].curr_subline
,
1987 &align
, linebuf
, sizeof(linebuf
));
1989 #ifdef HAVE_LCD_BITMAP
1991 if (flags
& vp_refresh_mode
& WPS_REFRESH_PEAK_METER
)
1993 /* the peakmeter should be alone on its line */
1994 update_line
= false;
1996 int h
= font_get(wps_vp
->vp
.font
)->height
;
1997 int peak_meter_y
= (line
- wps_vp
->first_line
)* h
;
1999 /* The user might decide to have the peak meter in the last
2000 line so that it is only displayed if no status bar is
2001 visible. If so we neither want do draw nor enable the
2003 if (peak_meter_y
+ h
<= display
->getheight()) {
2004 /* found a line with a peak meter -> remember that we must
2007 peak_meter_enabled
= true;
2008 peak_meter_screen(gwps
->display
, 0, peak_meter_y
,
2009 MIN(h
, display
->getheight() - peak_meter_y
));
2013 peak_meter_enabled
= false;
2017 #else /* HAVE_LCD_CHARCELL */
2020 if (flags
& vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2022 if (data
->full_line_progressbar
)
2023 draw_player_fullbar(gwps
, linebuf
, sizeof(linebuf
));
2025 draw_player_progress(gwps
);
2030 /* conditionals clear the line which means if the %Vd is put into the default
2031 viewport there will be a blank line.
2032 To get around this we dont allow any actual drawing to happen in the
2033 deault vp if other vp's are defined */
2034 ((data
->num_viewports
>1 && v
!=0) || data
->num_viewports
== 1))
2036 if (flags
& WPS_REFRESH_SCROLL
)
2038 /* if the line is a scrolling one we don't want to update
2039 too often, so that it has the time to scroll */
2040 if ((vp_refresh_mode
& WPS_REFRESH_SCROLL
) || new_subline_refresh
)
2041 write_line(display
, &align
, line
- wps_vp
->first_line
, true);
2044 write_line(display
, &align
, line
- wps_vp
->first_line
, false);
2048 #ifdef HAVE_LCD_BITMAP
2050 if (vp_refresh_mode
& WPS_REFRESH_PLAYER_PROGRESS
)
2054 draw_progressbar(gwps
, wps_vp
);
2057 /* Now display any images in this viewport */
2058 wps_display_images(gwps
, &wps_vp
->vp
);
2062 #ifdef HAVE_LCD_BITMAP
2063 data
->peak_meter_enabled
= enable_pm
;
2066 if (refresh_mode
& WPS_REFRESH_STATUSBAR
)
2068 gwps_draw_statusbars();
2070 /* Restore the default viewport */
2071 display
->set_viewport(NULL
);