1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * $Id: skin_parser.c 26752 2010-06-10 21:22:16Z bieber $
10 * Copyright (C) 2010 Jonathan Gordon
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
34 #include "skin_display.h"
35 #include "skin_engine.h"
36 #include "skin_parser.h"
37 #include "tag_table.h"
38 #include "skin_scan.h"
47 #include "root_menu.h"
53 struct skin_draw_info
{
55 struct skin_viewport
*skin_vp
;
57 unsigned long refresh_type
;
59 char* cur_align_start
;
60 struct align_pos align
;
68 int offset
; /* used by the playlist viewer */
71 typedef bool (*skin_render_func
)(struct skin_element
* alternator
, struct skin_draw_info
*info
);
72 bool skin_render_alternator(struct skin_element
* alternator
, struct skin_draw_info
*info
);
74 #ifdef HAVE_LCD_BITMAP
75 static void skin_render_playlistviewer(struct playlistviewer
* viewer
,
77 struct skin_viewport
* skin_viewport
,
78 unsigned long refresh_type
);
81 static bool do_non_text_tags(struct gui_wps
*gwps
, struct skin_draw_info
*info
,
82 struct skin_element
*element
, struct viewport
* vp
)
84 #ifndef HAVE_LCD_BITMAP
85 (void)vp
; /* silence warnings */
88 struct wps_token
*token
= (struct wps_token
*)element
->data
;
90 #ifdef HAVE_LCD_BITMAP
91 struct wps_data
*data
= gwps
->data
;
92 bool do_refresh
= (element
->tag
->flags
& info
->refresh_type
) > 0;
96 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
97 case SKIN_TOKEN_VIEWPORT_FGCOLOUR
:
99 struct viewport_colour
*col
= token
->value
.data
;
100 col
->vp
->fg_pattern
= col
->colour
;
103 case SKIN_TOKEN_VIEWPORT_BGCOLOUR
:
105 struct viewport_colour
*col
= token
->value
.data
;
106 col
->vp
->bg_pattern
= col
->colour
;
110 case SKIN_TOKEN_VIEWPORT_ENABLE
:
112 char *label
= token
->value
.data
;
113 char temp
= VP_DRAW_HIDEABLE
;
114 struct skin_element
*viewport
= gwps
->data
->tree
;
117 struct skin_viewport
*skinvp
= (struct skin_viewport
*)viewport
->data
;
118 if (skinvp
->label
&& !skinvp
->is_infovp
&&
119 !strcmp(skinvp
->label
, label
))
121 if (skinvp
->hidden_flags
&VP_DRAW_HIDDEN
)
123 temp
|= VP_DRAW_WASHIDDEN
;
125 skinvp
->hidden_flags
= temp
;
127 viewport
= viewport
->next
;
131 #ifdef HAVE_LCD_BITMAP
132 case SKIN_TOKEN_UIVIEWPORT_ENABLE
:
133 sb_set_info_vp(gwps
->display
->screen_type
,
136 case SKIN_TOKEN_PEAKMETER
:
137 data
->peak_meter_enabled
= true;
139 draw_peakmeters(gwps
, info
->line_number
, vp
);
142 #ifdef HAVE_LCD_BITMAP
143 case SKIN_TOKEN_PEAKMETER_LEFTBAR
:
144 case SKIN_TOKEN_PEAKMETER_RIGHTBAR
:
145 data
->peak_meter_enabled
= true;
146 /* fall through to the progressbar code */
148 case SKIN_TOKEN_VOLUMEBAR
:
149 case SKIN_TOKEN_BATTERY_PERCENTBAR
:
150 #ifdef HAVE_LCD_BITMAP
151 case SKIN_TOKEN_PROGRESSBAR
:
152 case SKIN_TOKEN_TUNER_RSSI_BAR
:
154 struct progressbar
*bar
= (struct progressbar
*)token
->value
.data
;
156 draw_progressbar(gwps
, info
->line_number
, bar
);
160 #ifdef HAVE_LCD_BITMAP
161 case SKIN_TOKEN_IMAGE_DISPLAY_LISTICON
:
162 case SKIN_TOKEN_IMAGE_PRELOAD_DISPLAY
:
164 struct image_display
*id
= token
->value
.data
;
165 const char* label
= id
->label
;
166 struct gui_img
*img
= find_image(label
, data
);
167 if (img
&& img
->loaded
)
169 if (id
->token
== NULL
)
171 img
->display
= id
->subimage
;
177 int a
= img
->num_subimages
;
178 out
= get_token_value(gwps
, id
->token
, info
->offset
,
179 buf
, sizeof(buf
), &a
);
181 /* NOTE: get_token_value() returns values starting at 1! */
183 a
= (out
&& *out
) ? 1 : 2;
184 if (token
->type
== SKIN_TOKEN_IMAGE_DISPLAY_LISTICON
)
185 a
-= 2; /* 2 is added in statusbar-skinned.c! */
190 /* Clear the image, as in conditionals */
191 clear_image_pos(gwps
, img
);
193 /* If the token returned a value which is higher than
194 * the amount of subimages, don't draw it. */
195 if (a
>= 0 && a
< img
->num_subimages
)
204 case SKIN_TOKEN_ALBUMART_DISPLAY
:
205 /* now draw the AA */
206 if (do_refresh
&& data
->albumart
)
208 int handle
= playback_current_aa_hid(data
->playback_aa_slot
);
210 if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF
))
212 struct dim dim
= {data
->albumart
->width
, data
->albumart
->height
};
213 handle
= radio_get_art_hid(&dim
);
216 data
->albumart
->draw_handle
= handle
;
220 case SKIN_TOKEN_DRAW_INBUILTBAR
:
221 gui_statusbar_draw(&(statusbars
.statusbars
[gwps
->display
->screen_type
]),
222 info
->refresh_type
== SKIN_REFRESH_ALL
,
225 case SKIN_TOKEN_VIEWPORT_CUSTOMLIST
:
227 skin_render_playlistviewer(token
->value
.data
, gwps
,
228 info
->skin_vp
, info
->refresh_type
);
231 #endif /* HAVE_LCD_BITMAP */
240 static void do_tags_in_hidden_conditional(struct skin_element
* branch
,
241 struct skin_draw_info
*info
)
243 #ifdef HAVE_LCD_BITMAP
244 struct gui_wps
*gwps
= info
->gwps
;
245 struct wps_data
*data
= gwps
->data
;
247 /* Tags here are ones which need to be "turned off" or cleared
248 * if they are in a conditional branch which isnt being used */
249 if (branch
->type
== LINE_ALTERNATOR
)
252 for (i
=0; i
<branch
->children_count
; i
++)
254 do_tags_in_hidden_conditional(branch
->children
[i
], info
);
257 else if (branch
->type
== LINE
&& branch
->children_count
)
259 struct skin_element
*child
= branch
->children
[0];
260 struct wps_token
*token
;
263 if (child
->type
== CONDITIONAL
)
266 for (i
=0; i
<child
->children_count
; i
++)
268 do_tags_in_hidden_conditional(child
->children
[i
], info
);
273 else if (child
->type
!= TAG
|| !child
->data
)
278 token
= (struct wps_token
*)child
->data
;
279 #ifdef HAVE_LCD_BITMAP
280 /* clear all pictures in the conditional and nested ones */
281 if (token
->type
== SKIN_TOKEN_IMAGE_PRELOAD_DISPLAY
)
283 struct image_display
*id
= token
->value
.data
;
284 struct gui_img
*img
= find_image(id
->label
, data
);
285 clear_image_pos(gwps
, img
);
287 else if (token
->type
== SKIN_TOKEN_PEAKMETER
)
289 data
->peak_meter_enabled
= false;
291 else if (token
->type
== SKIN_TOKEN_VIEWPORT_ENABLE
)
293 char *label
= token
->value
.data
;
294 struct skin_element
*viewport
;
295 for (viewport
= data
->tree
;
297 viewport
= viewport
->next
)
299 struct skin_viewport
*skin_viewport
= (struct skin_viewport
*)viewport
->data
;
300 if (skin_viewport
->label
&& strcmp(skin_viewport
->label
, label
))
302 if (skin_viewport
->hidden_flags
&VP_NEVER_VISIBLE
)
306 if (skin_viewport
->hidden_flags
&VP_DRAW_HIDEABLE
)
308 if (skin_viewport
->hidden_flags
&VP_DRAW_HIDDEN
)
309 skin_viewport
->hidden_flags
|= VP_DRAW_WASHIDDEN
;
312 gwps
->display
->set_viewport(&skin_viewport
->vp
);
313 gwps
->display
->clear_viewport();
314 gwps
->display
->scroll_stop(&skin_viewport
->vp
);
315 gwps
->display
->set_viewport(&info
->skin_vp
->vp
);
316 skin_viewport
->hidden_flags
|= VP_DRAW_HIDDEN
;
323 else if (data
->albumart
&& token
->type
== SKIN_TOKEN_ALBUMART_DISPLAY
)
326 playback_current_aa_hid(data
->playback_aa_slot
), true);
334 static void fix_line_alignment(struct skin_draw_info
*info
, struct skin_element
*element
)
336 struct align_pos
*align
= &info
->align
;
337 char *cur_pos
= info
->cur_align_start
+ strlen(info
->cur_align_start
);
338 switch (element
->tag
->type
)
340 case SKIN_TOKEN_ALIGN_LEFT
:
341 *cur_pos
= '\0'; cur_pos
++; *cur_pos
= '\0';
342 align
->left
= cur_pos
;
343 info
->cur_align_start
= cur_pos
;
345 case SKIN_TOKEN_ALIGN_LEFT_RTL
:
346 *cur_pos
= '\0'; cur_pos
++; *cur_pos
= '\0';
348 align
->right
= cur_pos
;
350 align
->left
= cur_pos
;
351 info
->cur_align_start
= cur_pos
;
353 case SKIN_TOKEN_ALIGN_CENTER
:
354 *cur_pos
= '\0'; cur_pos
++; *cur_pos
= '\0';
355 align
->center
= cur_pos
;
356 info
->cur_align_start
= cur_pos
;
358 case SKIN_TOKEN_ALIGN_RIGHT
:
359 *cur_pos
= '\0'; cur_pos
++; *cur_pos
= '\0';
360 align
->right
= cur_pos
;
361 info
->cur_align_start
= cur_pos
;
363 case SKIN_TOKEN_ALIGN_RIGHT_RTL
:
364 *cur_pos
= '\0'; cur_pos
++; *cur_pos
= '\0';
366 align
->left
= cur_pos
;
368 align
->right
= cur_pos
;
369 info
->cur_align_start
= cur_pos
;
376 /* Draw a LINE element onto the display */
377 static bool skin_render_line(struct skin_element
* line
, struct skin_draw_info
*info
)
379 bool needs_update
= false;
380 int last_value
, value
;
382 if (line
->children_count
== 0)
383 return false; /* empty line, do nothing */
385 struct skin_element
*child
= line
->children
[0];
386 struct conditional
*conditional
;
387 skin_render_func func
= skin_render_line
;
388 int old_refresh_mode
= info
->refresh_type
;
394 conditional
= (struct conditional
*)child
->data
;
395 last_value
= conditional
->last_value
;
396 value
= evaluate_conditional(info
->gwps
, info
->offset
,
397 conditional
, child
->children_count
);
398 conditional
->last_value
= value
;
399 if (child
->children_count
== 1)
401 /* special handling so
402 * %?aa<true> and %?<true|false> need special handlng here */
404 if (value
== -1) /* tag is false */
406 /* we are in a false branch of a %?aa<true> conditional */
408 do_tags_in_hidden_conditional(child
->children
[0], info
);
414 if (last_value
>= 0 && value
!= last_value
&& last_value
< child
->children_count
)
415 do_tags_in_hidden_conditional(child
->children
[last_value
], info
);
417 if (child
->children
[value
]->type
== LINE_ALTERNATOR
)
419 func
= skin_render_alternator
;
421 else if (child
->children
[value
]->type
== LINE
)
422 func
= skin_render_line
;
424 if (value
!= last_value
)
426 info
->refresh_type
= SKIN_REFRESH_ALL
;
427 info
->force_redraw
= true;
430 if (func(child
->children
[value
], info
))
433 needs_update
= needs_update
|| (last_value
!= value
);
435 info
->refresh_type
= old_refresh_mode
;
438 if (child
->tag
->flags
& NOBREAK
)
439 info
->no_line_break
= true;
440 if (child
->tag
->type
== SKIN_TOKEN_SUBLINE_SCROLL
)
441 info
->line_scrolls
= true;
443 fix_line_alignment(info
, child
);
449 if (!do_non_text_tags(info
->gwps
, info
, child
, &info
->skin_vp
->vp
))
451 static char tempbuf
[128];
452 const char *value
= get_token_value(info
->gwps
, child
->data
,
453 info
->offset
, tempbuf
,
454 sizeof(tempbuf
), NULL
);
458 if (child
->tag
->flags
&SKIN_RTC_REFRESH
)
459 needs_update
= needs_update
|| info
->refresh_type
&SKIN_REFRESH_DYNAMIC
;
461 needs_update
= needs_update
||
462 ((child
->tag
->flags
&info
->refresh_type
)!=0);
463 strlcat(info
->cur_align_start
, value
,
464 info
->buf_size
- (info
->cur_align_start
-info
->buf
));
469 strlcat(info
->cur_align_start
, child
->data
,
470 info
->buf_size
- (info
->cur_align_start
-info
->buf
));
471 needs_update
= needs_update
||
472 (info
->refresh_type
&SKIN_REFRESH_STATIC
) != 0;
484 static int get_subline_timeout(struct gui_wps
*gwps
, struct skin_element
* line
)
486 struct skin_element
*element
=line
;
487 struct wps_token
*token
;
488 int retval
= DEFAULT_SUBLINE_TIME_MULTIPLIER
*TIMEOUT_UNIT
;
489 if (element
->type
== LINE
)
491 if (element
->children_count
== 0)
492 return retval
; /* empty line, so force redraw */
493 element
= element
->children
[0];
497 if (element
->type
== TAG
&&
498 element
->tag
->type
== SKIN_TOKEN_SUBLINE_TIMEOUT
)
500 token
= element
->data
;
501 return token
->value
.i
;
503 else if (element
->type
== CONDITIONAL
)
505 struct conditional
*conditional
= element
->data
;
506 int val
= evaluate_conditional(gwps
, 0, conditional
,
507 element
->children_count
);
510 retval
= get_subline_timeout(gwps
, element
->children
[val
]);
515 element
= element
->next
;
520 bool skin_render_alternator(struct skin_element
* element
, struct skin_draw_info
*info
)
522 bool changed_lines
= false;
523 struct line_alternator
*alternator
= (struct line_alternator
*)element
->data
;
524 unsigned old_refresh
= info
->refresh_type
;
525 if (info
->refresh_type
== SKIN_REFRESH_ALL
)
527 alternator
->current_line
= element
->children_count
-1;
528 changed_lines
= true;
530 else if (TIME_AFTER(current_tick
, alternator
->next_change_tick
))
532 changed_lines
= true;
537 struct skin_element
*current_line
= element
->children
[alternator
->current_line
];
538 int start
= alternator
->current_line
;
539 int try_line
= start
;
540 bool suitable
= false;
541 int rettimeout
= DEFAULT_SUBLINE_TIME_MULTIPLIER
*TIMEOUT_UNIT
;
543 /* find a subline which has at least one token in it,
544 * and that line doesnt have a timeout set to 0 through conditionals */
547 if (try_line
>= element
->children_count
)
549 if (element
->children
[try_line
]->children_count
!= 0)
551 current_line
= element
->children
[try_line
];
552 rettimeout
= get_subline_timeout(info
->gwps
,
553 current_line
->children
[0]);
560 while (try_line
!= start
&& !suitable
);
564 alternator
->current_line
= try_line
;
565 alternator
->next_change_tick
= current_tick
+ rettimeout
;
568 info
->refresh_type
= SKIN_REFRESH_ALL
;
569 info
->force_redraw
= true;
571 bool ret
= skin_render_line(element
->children
[alternator
->current_line
], info
);
572 info
->refresh_type
= old_refresh
;
573 return changed_lines
|| ret
;
576 static void skin_render_viewport(struct skin_element
* viewport
, struct gui_wps
*gwps
,
577 struct skin_viewport
* skin_viewport
, unsigned long refresh_type
)
579 struct screen
*display
= gwps
->display
;
580 char linebuf
[MAX_LINE
];
581 skin_render_func func
= skin_render_line
;
582 struct skin_element
* line
= viewport
;
583 struct skin_draw_info info
= {
586 .buf_size
= sizeof(linebuf
),
588 .no_line_break
= false,
589 .line_scrolls
= false,
590 .refresh_type
= refresh_type
,
591 .skin_vp
= skin_viewport
,
595 struct align_pos
* align
= &info
.align
;
597 #ifdef HAVE_LCD_BITMAP
598 /* Set images to not to be displayed */
599 struct skin_token_list
*imglist
= gwps
->data
->images
;
602 struct gui_img
*img
= (struct gui_img
*)imglist
->token
->value
.data
;
604 imglist
= imglist
->next
;
611 info
.no_line_break
= false;
612 info
.line_scrolls
= false;
613 info
.force_redraw
= false;
615 info
.cur_align_start
= info
.buf
;
616 align
->left
= info
.buf
;
617 align
->center
= NULL
;
621 if (line
->type
== LINE_ALTERNATOR
)
622 func
= skin_render_alternator
;
623 else if (line
->type
== LINE
)
624 func
= skin_render_line
;
626 needs_update
= func(line
, &info
);
627 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
628 if (skin_viewport
->vp
.fg_pattern
!= skin_viewport
->start_fgcolour
||
629 skin_viewport
->vp
.bg_pattern
!= skin_viewport
->start_bgcolour
)
631 /* 2bit lcd drivers need lcd_set_viewport() to be called to change
632 * the colour, 16bit doesnt. But doing this makes static text
633 * get the new colour also */
635 display
->set_viewport(&skin_viewport
->vp
);
638 /* only update if the line needs to be, and there is something to write */
639 if (refresh_type
&& needs_update
)
641 if (info
.line_scrolls
)
643 /* if the line is a scrolling one we don't want to update
644 too often, so that it has the time to scroll */
645 if ((refresh_type
& SKIN_REFRESH_SCROLL
) || info
.force_redraw
)
646 write_line(display
, align
, info
.line_number
, true);
649 write_line(display
, align
, info
.line_number
, false);
651 if (!info
.no_line_break
)
655 #ifdef HAVE_LCD_BITMAP
656 wps_display_images(gwps
, &skin_viewport
->vp
);
660 void skin_render(struct gui_wps
*gwps
, unsigned refresh_mode
)
662 struct wps_data
*data
= gwps
->data
;
663 struct screen
*display
= gwps
->display
;
665 struct skin_element
* viewport
= data
->tree
;
666 struct skin_viewport
* skin_viewport
;
668 int old_refresh_mode
= refresh_mode
;
670 #ifdef HAVE_LCD_CHARCELLS
672 for (i
= 0; i
< 8; i
++)
674 if (data
->wps_progress_pat
[i
] == 0)
675 data
->wps_progress_pat
[i
] = display
->get_locked_pattern();
678 viewport
= data
->tree
;
679 skin_viewport
= (struct skin_viewport
*)viewport
->data
;
680 if (skin_viewport
->label
&& viewport
->next
&&
681 !strcmp(skin_viewport
->label
,VP_DEFAULT_LABEL
))
684 for (viewport
= data
->tree
;
686 viewport
= viewport
->next
)
689 skin_viewport
= (struct skin_viewport
*)viewport
->data
;
690 unsigned vp_refresh_mode
= refresh_mode
;
691 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)
692 skin_viewport
->vp
.fg_pattern
= skin_viewport
->start_fgcolour
;
693 skin_viewport
->vp
.bg_pattern
= skin_viewport
->start_bgcolour
;
696 /* dont redraw the viewport if its disabled */
697 if (skin_viewport
->hidden_flags
&VP_NEVER_VISIBLE
)
698 { /* don't draw anything into this one */
701 else if ((skin_viewport
->hidden_flags
&VP_DRAW_HIDDEN
))
703 skin_viewport
->hidden_flags
|= VP_DRAW_WASHIDDEN
;
706 else if (((skin_viewport
->hidden_flags
&
707 (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
))
708 == (VP_DRAW_WASHIDDEN
|VP_DRAW_HIDEABLE
)))
710 vp_refresh_mode
= SKIN_REFRESH_ALL
;
711 skin_viewport
->hidden_flags
= VP_DRAW_HIDEABLE
;
714 display
->set_viewport(&skin_viewport
->vp
);
715 if ((vp_refresh_mode
&SKIN_REFRESH_ALL
) == SKIN_REFRESH_ALL
)
717 display
->clear_viewport();
720 if (viewport
->children_count
)
721 skin_render_viewport(viewport
->children
[0], gwps
,
722 skin_viewport
, vp_refresh_mode
);
723 refresh_mode
= old_refresh_mode
;
726 /* Restore the default viewport */
727 display
->set_viewport(NULL
);
731 #ifdef HAVE_LCD_BITMAP
732 static __attribute__((noinline
)) void skin_render_playlistviewer(struct playlistviewer
* viewer
,
733 struct gui_wps
*gwps
,
734 struct skin_viewport
* skin_viewport
,
735 unsigned long refresh_type
)
737 struct screen
*display
= gwps
->display
;
738 char linebuf
[MAX_LINE
];
739 skin_render_func func
= skin_render_line
;
740 struct skin_element
* line
;
741 struct skin_draw_info info
= {
744 .buf_size
= sizeof(linebuf
),
746 .no_line_break
= false,
747 .line_scrolls
= false,
748 .refresh_type
= refresh_type
,
749 .skin_vp
= skin_viewport
,
750 .offset
= viewer
->start_offset
753 struct align_pos
* align
= &info
.align
;
755 int cur_pos
, start_item
, max
;
756 int nb_lines
= viewport_get_nb_lines(viewer
->vp
);
758 if (current_screen() == GO_TO_FM
)
760 cur_pos
= radio_current_preset();
761 start_item
= cur_pos
+ viewer
->start_offset
;
762 max
= start_item
+radio_preset_count();
767 struct cuesheet
*cue
= skin_get_global_state()->id3
?
768 skin_get_global_state()->id3
->cuesheet
: NULL
;
769 cur_pos
= playlist_get_display_index();
770 max
= playlist_amount()+1;
772 max
+= cue
->track_count
;
773 start_item
= MAX(0, cur_pos
+ viewer
->start_offset
);
775 if (max
-start_item
> nb_lines
)
776 max
= start_item
+ nb_lines
;
779 while (start_item
< max
)
782 info
.no_line_break
= false;
783 info
.line_scrolls
= false;
784 info
.force_redraw
= false;
786 info
.cur_align_start
= info
.buf
;
787 align
->left
= info
.buf
;
788 align
->center
= NULL
;
792 if (line
->type
== LINE_ALTERNATOR
)
793 func
= skin_render_alternator
;
794 else if (line
->type
== LINE
)
795 func
= skin_render_line
;
797 needs_update
= func(line
, &info
);
799 /* only update if the line needs to be, and there is something to write */
800 if (refresh_type
&& needs_update
)
802 if (info
.line_scrolls
)
804 /* if the line is a scrolling one we don't want to update
805 too often, so that it has the time to scroll */
806 if ((refresh_type
& SKIN_REFRESH_SCROLL
) || info
.force_redraw
)
807 write_line(display
, align
, info
.line_number
, true);
810 write_line(display
, align
, info
.line_number
, false);