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 ****************************************************************************/
24 #include "string-extra.h"
28 #include "rbunicode.h"
30 #include "powermgmt.h"
38 #include "statusbar.h"
40 #include "scrollbar.h"
41 #include "screen_access.h"
46 #ifdef HAVE_LCD_BITMAP
47 #include "peakmeter.h"
56 #if CONFIG_CODEC == SWCODEC
65 #include "root_menu.h"
68 #include "wps_internals.h"
69 #include "skin_engine.h"
70 #include "statusbar-skinned.h"
71 #include "skin_display.h"
73 void skin_render(struct gui_wps
*gwps
, unsigned refresh_mode
);
75 /* update a skinned screen, update_type is WPS_REFRESH_* values.
76 * Usually it should only be WPS_REFRESH_NON_STATIC
77 * A full update will be done if required (skin_do_full_update() == true)
79 void skin_update(enum skinnable_screens skin
, enum screen_type screen
,
80 unsigned int update_type
)
82 struct gui_wps
*gwps
= skin_get_gwps(skin
, screen
);
83 /* This maybe shouldnt be here,
84 * This is also safe for skined screen which dont use the id3 */
85 struct mp3entry
*id3
= skin_get_global_state()->id3
;
86 bool cuesheet_update
= (id3
!= NULL
? cuesheet_subtrack_changed(id3
) : false);
88 skin_request_full_update(skin
);
90 skin_render(gwps
, skin_do_full_update(skin
, screen
) ?
91 SKIN_REFRESH_ALL
: update_type
);
94 #ifdef HAVE_LCD_BITMAP
96 void skin_statusbar_changed(struct gui_wps
*skin
)
100 struct wps_data
*data
= skin
->data
;
101 const struct screen
*display
= skin
->display
;
102 const int screen
= display
->screen_type
;
104 struct viewport
*vp
= &find_viewport(VP_DEFAULT_LABEL
, false, data
)->vp
;
105 viewport_set_defaults(vp
, screen
);
107 if (data
->wps_sb_tag
)
108 { /* fix up the default viewport */
109 if (data
->show_sb_on_wps
)
111 if (statusbar_position(screen
) != STATUSBAR_OFF
)
112 return; /* vp is fixed already */
114 vp
->y
= STATUSBAR_HEIGHT
;
115 vp
->height
= display
->lcdheight
- STATUSBAR_HEIGHT
;
119 if (statusbar_position(screen
) == STATUSBAR_OFF
)
120 return; /* vp is fixed already */
122 vp
->height
= display
->lcdheight
;
123 vp
->width
= display
->lcdwidth
;
128 void draw_progressbar(struct gui_wps
*gwps
, int line
, struct progressbar
*pb
)
130 struct screen
*display
= gwps
->display
;
131 struct viewport
*vp
= pb
->vp
;
132 struct wps_state
*state
= skin_get_global_state();
133 struct mp3entry
*id3
= state
->id3
;
134 int x
= pb
->x
, y
= pb
->y
, width
= pb
->width
, height
= pb
->height
;
135 unsigned long length
, end
;
136 int flags
= HORIZONTAL
;
139 height
= font_get(vp
->font
)->height
;
143 int line_height
= font_get(vp
->font
)->height
;
144 /* center the pb in the line, but only if the line is higher than the pb */
145 int center
= (line_height
-height
)/2;
146 /* if Y was not set calculate by font height,Y is -line_number-1 */
147 y
= line
*line_height
+ (0 > center
? 0 : center
);
150 if (pb
->type
== SKIN_TOKEN_VOLUMEBAR
)
152 int minvol
= sound_min(SOUND_VOLUME
);
153 int maxvol
= sound_max(SOUND_VOLUME
);
154 length
= maxvol
-minvol
;
155 end
= global_settings
.volume
-minvol
;
157 else if (pb
->type
== SKIN_TOKEN_BATTERY_PERCENTBAR
)
160 end
= battery_level();
162 else if (pb
->type
== SKIN_TOKEN_PEAKMETER_LEFTBAR
||
163 pb
->type
== SKIN_TOKEN_PEAKMETER_RIGHTBAR
)
165 int left
, right
, val
;
166 peak_meter_current_vals(&left
, &right
);
167 val
= pb
->type
== SKIN_TOKEN_PEAKMETER_LEFTBAR
? left
: right
;
169 end
= peak_meter_scale_value(val
, length
);
172 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF
))
174 #ifdef HAVE_RADIO_RSSI
175 if (pb
->type
== SKIN_TOKEN_TUNER_RSSI_BAR
)
177 int val
= tuner_get(RADIO_RSSI
);
178 int min
= tuner_get(RADIO_RSSI_MIN
);
179 int max
= tuner_get(RADIO_RSSI_MAX
);
186 int min
= fm_region_data
[global_settings
.fm_region
].freq_min
;
187 end
= radio_current_frequency() - min
;
188 length
= fm_region_data
[global_settings
.fm_region
].freq_max
- min
;
192 else if (id3
&& id3
->length
)
194 length
= id3
->length
;
195 end
= id3
->elapsed
+ state
->ff_rewind_count
;
205 /* we want to fill upwards which is technically inverted. */
209 if (pb
->invert_fill_direction
)
216 flags
|= INNER_NOFILL
;
221 struct gui_img
*img
= pb
->slider
;
222 /* clear the slider */
223 screen_clear_area(display
, x
, y
, width
, height
);
225 /* shrink the bar so the slider is inside bounds */
226 if (flags
&HORIZONTAL
)
228 width
-= img
->bm
.width
;
229 x
+= img
->bm
.width
/ 2;
233 height
-= img
->bm
.height
;
234 y
+= img
->bm
.height
/ 2;
240 struct gui_img
*img
= pb
->backdrop
;
242 if(img
->bm
.format
== FORMAT_MONO
) {
244 display
->mono_bitmap_part(img
->bm
.data
,
246 x
, y
, width
, height
);
249 display
->transparent_bitmap_part((fb_data
*)img
->bm
.data
,
251 STRIDE(display
->screen_type
,
252 img
->bm
.width
, img
->bm
.height
),
253 x
, y
, width
, height
);
256 flags
|= DONT_CLEAR_EXCESS
;
262 gui_bitmap_scrollbar_draw(display
, &pb
->image
->bm
,
264 length
, 0, end
, flags
);
266 gui_scrollbar_draw(display
, x
, y
, width
, height
,
267 length
, 0, end
, flags
);
272 int xoff
= 0, yoff
= 0;
273 int w
= width
, h
= height
;
274 struct gui_img
*img
= pb
->slider
;
276 if (flags
&HORIZONTAL
)
279 xoff
= width
* end
/ length
;
280 if (flags
&INVERTFILL
)
287 yoff
= height
* end
/ length
;
288 if (flags
&INVERTFILL
)
289 yoff
= height
- yoff
;
293 if(img
->bm
.format
== FORMAT_MONO
) {
295 display
->mono_bitmap_part(img
->bm
.data
,
297 x
+ xoff
, y
+ yoff
, w
, h
);
300 display
->transparent_bitmap_part((fb_data
*)img
->bm
.data
,
302 STRIDE(display
->screen_type
,
303 img
->bm
.width
, img
->bm
.height
),
304 x
+ xoff
, y
+ yoff
, w
, h
);
309 if (pb
->type
== SKIN_TOKEN_PROGRESSBAR
)
311 if (id3
&& id3
->length
)
313 #ifdef AB_REPEAT_ENABLE
314 if (ab_repeat_mode_enabled())
315 ab_draw_markers(display
, id3
->length
, x
, y
, width
, height
);
319 cue_draw_markers(display
, id3
->cuesheet
, id3
->length
,
320 x
, y
+1, width
, height
-2);
322 #if 0 /* disable for now CONFIG_TUNER */
323 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF
))
325 presets_draw_markers(display
, x
, y
, width
, height
);
331 /* clears the area where the image was shown */
332 void clear_image_pos(struct gui_wps
*gwps
, struct gui_img
*img
)
336 gwps
->display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
337 gwps
->display
->fillrect(img
->x
, img
->y
, img
->bm
.width
, img
->subimage_height
);
338 gwps
->display
->set_drawmode(DRMODE_SOLID
);
341 void wps_draw_image(struct gui_wps
*gwps
, struct gui_img
*img
, int subimage
)
343 struct screen
*display
= gwps
->display
;
344 if(img
->always_display
)
345 display
->set_drawmode(DRMODE_FG
);
347 display
->set_drawmode(DRMODE_SOLID
);
350 if(img
->bm
.format
== FORMAT_MONO
) {
352 display
->mono_bitmap_part(img
->bm
.data
,
353 0, img
->subimage_height
* subimage
,
354 img
->bm
.width
, img
->x
,
355 img
->y
, img
->bm
.width
,
356 img
->subimage_height
);
359 display
->transparent_bitmap_part((fb_data
*)img
->bm
.data
,
360 0, img
->subimage_height
* subimage
,
361 STRIDE(display
->screen_type
,
362 img
->bm
.width
, img
->bm
.height
),
363 img
->x
, img
->y
, img
->bm
.width
,
364 img
->subimage_height
);
370 void wps_display_images(struct gui_wps
*gwps
, struct viewport
* vp
)
372 if(!gwps
|| !gwps
->data
|| !gwps
->display
)
375 struct wps_data
*data
= gwps
->data
;
376 struct screen
*display
= gwps
->display
;
377 struct skin_token_list
*list
= data
->images
;
381 struct gui_img
*img
= (struct gui_img
*)list
->token
->value
.data
;
382 if (img
->using_preloaded_icons
&& img
->display
>= 0)
384 screen_put_icon(display
, img
->x
, img
->y
, img
->display
);
386 else if (img
->loaded
)
388 if (img
->display
>= 0)
390 wps_draw_image(gwps
, img
, img
->display
);
392 else if (img
->always_display
&& img
->vp
== vp
)
394 wps_draw_image(gwps
, img
, 0);
400 /* now draw the AA */
401 if (data
->albumart
&& data
->albumart
->vp
== vp
402 && data
->albumart
->draw_handle
>= 0)
404 draw_album_art(gwps
, data
->albumart
->draw_handle
, false);
405 data
->albumart
->draw_handle
= -1;
409 display
->set_drawmode(DRMODE_SOLID
);
412 #endif /* HAVE_LCD_BITMAP */
414 /* Evaluate the conditional that is at *token_index and return whether a skip
415 has ocurred. *token_index is updated with the new position.
417 int evaluate_conditional(struct gui_wps
*gwps
, int offset
,
418 struct conditional
*conditional
, int num_options
)
426 int intval
= num_options
< 2 ? 2 : num_options
;
427 /* get_token_value needs to know the number of options in the enum */
428 value
= get_token_value(gwps
, conditional
->token
, offset
,
429 result
, sizeof(result
), &intval
);
431 /* intval is now the number of the enum option we want to read,
432 starting from 1. If intval is -1, we check if value is empty. */
435 if (num_options
== 1) /* so %?AA<true> */
436 intval
= (value
&& *value
) ? 1 : 0; /* returned as 0 for true, -1 for false */
438 intval
= (value
&& *value
) ? 1 : num_options
;
440 else if (intval
> num_options
|| intval
< 1)
441 intval
= num_options
;
447 /* Display a line appropriately according to its alignment format.
448 format_align contains the text, separated between left, center and right.
449 line is the index of the line on the screen.
450 scroll indicates whether the line is a scrolling one or not.
452 void write_line(struct screen
*display
,
453 struct align_pos
*format_align
,
457 int left_width
= 0, left_xpos
;
458 int center_width
= 0, center_xpos
;
459 int right_width
= 0, right_xpos
;
465 /* calculate different string sizes and positions */
466 display
->getstringsize((unsigned char *)" ", &space_width
, &string_height
);
467 if (format_align
->left
!= 0) {
468 display
->getstringsize((unsigned char *)format_align
->left
,
469 &left_width
, &string_height
);
472 if (format_align
->right
!= 0) {
473 display
->getstringsize((unsigned char *)format_align
->right
,
474 &right_width
, &string_height
);
477 if (format_align
->center
!= 0) {
478 display
->getstringsize((unsigned char *)format_align
->center
,
479 ¢er_width
, &string_height
);
483 right_xpos
= (display
->getwidth() - right_width
);
484 center_xpos
= (display
->getwidth() + left_xpos
- center_width
) / 2;
486 scroll_width
= display
->getwidth() - left_xpos
;
488 /* Checks for overlapping strings.
489 If needed the overlapping strings will be merged, separated by a
492 /* CASE 1: left and centered string overlap */
493 /* there is a left string, need to merge left and center */
494 if ((left_width
!= 0 && center_width
!= 0) &&
495 (left_xpos
+ left_width
+ space_width
> center_xpos
)) {
496 /* replace the former separator '\0' of left and
497 center string with a space */
498 *(--format_align
->center
) = ' ';
499 /* calculate the new width and position of the merged string */
500 left_width
= left_width
+ space_width
+ center_width
;
501 /* there is no centered string anymore */
504 /* there is no left string, move center to left */
505 if ((left_width
== 0 && center_width
!= 0) &&
506 (left_xpos
+ left_width
> center_xpos
)) {
507 /* move the center string to the left string */
508 format_align
->left
= format_align
->center
;
509 /* calculate the new width and position of the string */
510 left_width
= center_width
;
511 /* there is no centered string anymore */
515 /* CASE 2: centered and right string overlap */
516 /* there is a right string, need to merge center and right */
517 if ((center_width
!= 0 && right_width
!= 0) &&
518 (center_xpos
+ center_width
+ space_width
> right_xpos
)) {
519 /* replace the former separator '\0' of center and
520 right string with a space */
521 *(--format_align
->right
) = ' ';
522 /* move the center string to the right after merge */
523 format_align
->right
= format_align
->center
;
524 /* calculate the new width and position of the merged string */
525 right_width
= center_width
+ space_width
+ right_width
;
526 right_xpos
= (display
->getwidth() - right_width
);
527 /* there is no centered string anymore */
530 /* there is no right string, move center to right */
531 if ((center_width
!= 0 && right_width
== 0) &&
532 (center_xpos
+ center_width
> right_xpos
)) {
533 /* move the center string to the right string */
534 format_align
->right
= format_align
->center
;
535 /* calculate the new width and position of the string */
536 right_width
= center_width
;
537 right_xpos
= (display
->getwidth() - right_width
);
538 /* there is no centered string anymore */
542 /* CASE 3: left and right overlap
543 There is no center string anymore, either there never
544 was one or it has been merged in case 1 or 2 */
545 /* there is a left string, need to merge left and right */
546 if ((left_width
!= 0 && center_width
== 0 && right_width
!= 0) &&
547 (left_xpos
+ left_width
+ space_width
> right_xpos
)) {
548 /* replace the former separator '\0' of left and
549 right string with a space */
550 *(--format_align
->right
) = ' ';
551 /* calculate the new width and position of the string */
552 left_width
= left_width
+ space_width
+ right_width
;
553 /* there is no right string anymore */
556 /* there is no left string, move right to left */
557 if ((left_width
== 0 && center_width
== 0 && right_width
!= 0) &&
558 (left_width
> right_xpos
)) {
559 /* move the right string to the left string */
560 format_align
->left
= format_align
->right
;
561 /* calculate the new width and position of the string */
562 left_width
= right_width
;
563 /* there is no right string anymore */
567 ypos
= (line
* string_height
);
570 if (scroll
&& ((left_width
> scroll_width
) ||
571 (center_width
> scroll_width
) ||
572 (right_width
> scroll_width
)))
574 display
->puts_scroll(0, line
,
575 (unsigned char *)format_align
->left
);
579 #ifdef HAVE_LCD_BITMAP
580 /* clear the line first */
581 display
->set_drawmode(DRMODE_SOLID
|DRMODE_INVERSEVID
);
582 display
->fillrect(left_xpos
, ypos
, display
->getwidth(), string_height
);
583 display
->set_drawmode(DRMODE_SOLID
);
586 /* Nasty hack: we output an empty scrolling string,
587 which will reset the scroller for that line */
588 display
->puts_scroll(0, line
, (unsigned char *)"");
590 /* print aligned strings */
593 display
->putsxy(left_xpos
, ypos
,
594 (unsigned char *)format_align
->left
);
596 if (center_width
!= 0)
598 display
->putsxy(center_xpos
, ypos
,
599 (unsigned char *)format_align
->center
);
601 if (right_width
!= 0)
603 display
->putsxy(right_xpos
, ypos
,
604 (unsigned char *)format_align
->right
);
609 #ifdef HAVE_LCD_BITMAP
610 void draw_peakmeters(struct gui_wps
*gwps
, int line_number
,
611 struct viewport
*viewport
)
613 struct wps_data
*data
= gwps
->data
;
614 if (!data
->peak_meter_enabled
)
616 peak_meter_enable(false);
620 int h
= font_get(viewport
->font
)->height
;
621 int peak_meter_y
= line_number
* h
;
623 /* The user might decide to have the peak meter in the last
624 line so that it is only displayed if no status bar is
625 visible. If so we neither want do draw nor enable the
627 if (peak_meter_y
+ h
<= viewport
->y
+viewport
->height
) {
628 peak_meter_enable(true);
629 peak_meter_screen(gwps
->display
, 0, peak_meter_y
,
630 MIN(h
, viewport
->y
+viewport
->height
- peak_meter_y
));
635 bool skin_has_sbs(enum screen_type screen
, struct wps_data
*data
)
640 #ifdef HAVE_LCD_BITMAP
641 if (data
->wps_sb_tag
)
642 draw
= data
->show_sb_on_wps
;
643 else if (statusbar_position(screen
) != STATUSBAR_OFF
)
650 /* do the button loop as often as required for the peak meters to update
651 * with a good refresh rate.
653 int skin_wait_for_action(enum skinnable_screens skin
, int context
, int timeout
)
655 (void)skin
; /* silence charcell warning */
656 int button
= ACTION_NONE
;
657 #ifdef HAVE_LCD_BITMAP
659 /* when the peak meter is enabled we want to have a
660 few extra updates to make it look smooth. On the
661 other hand we don't want to waste energy if it
666 if(skin_get_gwps(skin
, i
)->data
->peak_meter_enabled
)
671 long next_refresh
= current_tick
;
672 long next_big_refresh
= current_tick
+ timeout
;
673 button
= BUTTON_NONE
;
674 while (TIME_BEFORE(current_tick
, next_big_refresh
)) {
675 button
= get_action(context
,TIMEOUT_NOBLOCK
);
676 if (button
!= ACTION_NONE
) {
680 sleep(0); /* Sleep until end of current tick. */
682 if (TIME_AFTER(current_tick
, next_refresh
)) {
685 if(skin_get_gwps(skin
, i
)->data
->peak_meter_enabled
)
686 skin_update(skin
, i
, SKIN_REFRESH_PEAK_METER
);
687 next_refresh
+= HZ
/ PEAK_METER_FPS
;
694 /* The peak meter is disabled
695 -> no additional screen updates needed */
699 button
= get_action(context
, timeout
);