Fix the mechanism to fail to parse skins if images fail to load and fix having no...
[kugel-rb.git] / apps / gui / skin_engine / skin_display.c
blobdf200bec5f8bb27a330a10a0fe6dbb24fd2651f1
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
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 "config.h"
23 #include <stdio.h>
24 #include <string.h>
25 #include "misc.h"
26 #include "font.h"
27 #include "system.h"
28 #include "rbunicode.h"
29 #ifdef DEBUG
30 #include "debug.h"
31 #endif
32 #include "abrepeat.h"
33 #include "lang.h"
34 #include "language.h"
35 #include "statusbar.h"
36 #include "scrollbar.h"
37 #include "screen_access.h"
38 #include "playlist.h"
39 #include "audio.h"
41 #ifdef HAVE_LCD_BITMAP
42 #include "peakmeter.h"
43 /* Image stuff */
44 #include "bmp.h"
45 #ifdef HAVE_ALBUMART
46 #include "albumart.h"
47 #endif
48 #endif
50 #include "cuesheet.h"
51 #if CONFIG_CODEC == SWCODEC
52 #include "playback.h"
53 #endif
54 #include "backdrop.h"
55 #include "viewport.h"
58 #include "wps_internals.h"
59 #include "skin_engine.h"
60 #include "statusbar-skinned.h"
62 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode);
64 /* update a skinned screen, update_type is WPS_REFRESH_* values.
65 * Usually it should only be WPS_REFRESH_NON_STATIC
66 * A full update will be done if required (state.do_full_update == true)
68 bool skin_update(struct gui_wps *gwps, unsigned int update_type)
70 bool retval;
71 /* This maybe shouldnt be here, but while the skin is only used to
72 * display the music screen this is better than whereever we are being
73 * called from. This is also safe for skined screen which dont use the id3 */
74 struct mp3entry *id3 = gwps->state->id3;
75 bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
76 gwps->sync_data->do_full_update |= cuesheet_update;
78 retval = skin_redraw(gwps, gwps->sync_data->do_full_update ?
79 WPS_REFRESH_ALL : update_type);
80 return retval;
83 #ifdef HAVE_LCD_BITMAP
85 void skin_statusbar_changed(struct gui_wps *skin)
87 if (!skin)
88 return;
89 struct wps_data *data = skin->data;
90 const struct screen *display = skin->display;
91 const int screen = display->screen_type;
93 struct viewport *vp = &find_viewport(VP_DEFAULT_LABEL, data)->vp;
94 viewport_set_defaults(vp, screen);
96 if (data->wps_sb_tag)
97 { /* fix up the default viewport */
98 if (data->show_sb_on_wps)
100 if (statusbar_position(screen) != STATUSBAR_OFF)
101 return; /* vp is fixed already */
103 vp->y = STATUSBAR_HEIGHT;
104 vp->height = display->lcdheight - STATUSBAR_HEIGHT;
106 else
108 if (statusbar_position(screen) == STATUSBAR_OFF)
109 return; /* vp is fixed already */
110 vp->y = vp->x = 0;
111 vp->height = display->lcdheight;
112 vp->width = display->lcdwidth;
117 static void draw_progressbar(struct gui_wps *gwps,
118 struct skin_viewport *wps_vp)
120 struct screen *display = gwps->display;
121 struct wps_state *state = gwps->state;
122 struct progressbar *pb = wps_vp->pb;
123 struct mp3entry *id3 = state->id3;
124 int y = pb->y;
126 if (y < 0)
128 int line_height = font_get(wps_vp->vp.font)->height;
129 /* center the pb in the line, but only if the line is higher than the pb */
130 int center = (line_height-pb->height)/2;
131 /* if Y was not set calculate by font height,Y is -line_number-1 */
132 y = (-y -1)*line_height + (0 > center ? 0 : center);
135 int elapsed, length;
136 if (id3)
138 elapsed = id3->elapsed;
139 length = id3->length;
141 else
143 elapsed = 0;
144 length = 0;
147 if (pb->have_bitmap_pb)
148 gui_bitmap_scrollbar_draw(display, pb->bm,
149 pb->x, y, pb->width, pb->bm.height,
150 length ? length : 1, 0,
151 length ? elapsed + state->ff_rewind_count : 0,
152 HORIZONTAL);
153 else
154 gui_scrollbar_draw(display, pb->x, y, pb->width, pb->height,
155 length ? length : 1, 0,
156 length ? elapsed + state->ff_rewind_count : 0,
157 HORIZONTAL);
158 #ifdef AB_REPEAT_ENABLE
159 if ( ab_repeat_mode_enabled() && length != 0 )
160 ab_draw_markers(display, length,
161 pb->x, pb->x + pb->width, y, pb->height);
162 #endif
164 if (id3 && id3->cuesheet)
165 cue_draw_markers(display, state->id3->cuesheet, length,
166 pb->x, pb->x + pb->width, y+1, pb->height-2);
168 bool audio_peek_track(struct mp3entry* id3, int offset);
169 static void draw_playlist_viewer_list(struct gui_wps *gwps,
170 struct playlistviewer *viewer)
172 struct wps_state *state = gwps->state;
173 int lines = viewport_get_nb_lines(viewer->vp);
174 int line_height = font_get(viewer->vp->font)->height;
175 int cur_playlist_pos = playlist_get_display_index();
176 int start_item = MAX(0, cur_playlist_pos + viewer->start_offset);
177 int i;
178 struct wps_token token;
179 int x, length, alignment = WPS_TOKEN_ALIGN_LEFT;
181 struct mp3entry *pid3;
182 #if CONFIG_CODEC == SWCODEC
183 struct mp3entry id3;
184 #endif
185 char buf[MAX_PATH*2], tempbuf[MAX_PATH];
186 unsigned int buf_used = 0;
189 gwps->display->set_viewport(viewer->vp);
190 for(i=start_item; (i-start_item)<lines && i<playlist_amount(); i++)
192 if (i == cur_playlist_pos)
194 pid3 = state->id3;
196 else if (i == cur_playlist_pos+1)
198 pid3 = state->nid3;
200 #if CONFIG_CODEC == SWCODEC
201 else if ((i>cur_playlist_pos) && audio_peek_track(&id3, i-cur_playlist_pos))
203 pid3 = &id3;
205 #endif
206 else
208 pid3 = NULL;
211 int line = pid3 ? TRACK_HAS_INFO : TRACK_HAS_NO_INFO;
212 int j = 0, cur_string = 0;
213 char *filename = playlist_peek(i-cur_playlist_pos);
214 buf[0] = '\0';
215 buf_used = 0;
216 while (j < viewer->lines[line].count && (buf_used<sizeof(buf)))
218 const char *out = NULL;
219 token.type = viewer->lines[line].tokens[j];
220 token.value.i = 0;
221 token.next = false;
222 out = get_id3_token(&token, pid3, tempbuf, sizeof(tempbuf), -1, NULL);
223 if (out)
225 snprintf(&buf[buf_used], sizeof(buf)-buf_used, "%s", out);
226 buf_used += strlen(out);
227 j++;
228 continue;
230 switch (viewer->lines[line].tokens[j])
232 case WPS_TOKEN_ALIGN_CENTER:
233 case WPS_TOKEN_ALIGN_LEFT:
234 case WPS_TOKEN_ALIGN_LEFT_RTL:
235 case WPS_TOKEN_ALIGN_RIGHT:
236 case WPS_TOKEN_ALIGN_RIGHT_RTL:
237 alignment = viewer->lines[line].tokens[j];
238 tempbuf[0] = '\0';
239 break;
240 case WPS_TOKEN_STRING:
241 case WPS_TOKEN_CHARACTER:
242 snprintf(tempbuf, sizeof(tempbuf), "%s",
243 viewer->lines[line].strings[cur_string]);
244 cur_string++;
245 break;
246 case WPS_TOKEN_PLAYLIST_POSITION:
247 snprintf(tempbuf, sizeof(tempbuf), "%d", i);
248 break;
249 case WPS_TOKEN_FILE_NAME:
250 get_dir(tempbuf, sizeof(tempbuf), filename, 0);
251 break;
252 case WPS_TOKEN_FILE_PATH:
253 snprintf(tempbuf, sizeof(tempbuf), "%s", filename);
254 break;
255 default:
256 tempbuf[0] = '\0';
257 break;
259 if (tempbuf[0])
261 snprintf(&buf[buf_used], sizeof(buf)-buf_used, "%s", tempbuf);
262 buf_used += strlen(tempbuf);
264 j++;
267 int vpwidth = viewer->vp->width;
268 length = gwps->display->getstringsize(buf, NULL, NULL);
269 if (viewer->lines[line].scroll && length >= vpwidth)
271 gwps->display->puts_scroll(0, (i-start_item), buf );
273 else
275 if (length >= vpwidth)
276 x = 0;
277 else
279 switch (alignment)
281 case WPS_TOKEN_ALIGN_CENTER:
282 x = (vpwidth-length)/2;
283 break;
284 case WPS_TOKEN_ALIGN_LEFT_RTL:
285 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
287 x = vpwidth - length;
288 break;
290 case WPS_TOKEN_ALIGN_LEFT:
291 x = 0;
292 break;
293 case WPS_TOKEN_ALIGN_RIGHT_RTL:
294 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
296 x = 0;
297 break;
299 case WPS_TOKEN_ALIGN_RIGHT:
300 x = vpwidth - length;
301 break;
302 default:
303 x = 0;
304 break;
307 gwps->display->putsxy(x, (i-start_item)*line_height, buf );
313 /* clears the area where the image was shown */
314 static void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
316 if(!gwps)
317 return;
318 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
319 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
320 gwps->display->set_drawmode(DRMODE_SOLID);
323 static void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
325 struct screen *display = gwps->display;
326 if(img->always_display)
327 display->set_drawmode(DRMODE_FG);
328 else
329 display->set_drawmode(DRMODE_SOLID);
331 #if LCD_DEPTH > 1
332 if(img->bm.format == FORMAT_MONO) {
333 #endif
334 display->mono_bitmap_part(img->bm.data,
335 0, img->subimage_height * subimage,
336 img->bm.width, img->x,
337 img->y, img->bm.width,
338 img->subimage_height);
339 #if LCD_DEPTH > 1
340 } else {
341 display->transparent_bitmap_part((fb_data *)img->bm.data,
342 0, img->subimage_height * subimage,
343 STRIDE(display->screen_type,
344 img->bm.width, img->bm.height),
345 img->x, img->y, img->bm.width,
346 img->subimage_height);
348 #endif
351 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
353 if(!gwps || !gwps->data || !gwps->display)
354 return;
356 struct wps_data *data = gwps->data;
357 struct screen *display = gwps->display;
358 struct skin_token_list *list = data->images;
360 while (list)
362 struct gui_img *img = (struct gui_img*)list->token->value.data;
363 if (img->loaded)
365 if (img->display >= 0)
367 wps_draw_image(gwps, img, img->display);
369 else if (img->always_display && img->vp == vp)
371 wps_draw_image(gwps, img, 0);
374 list = list->next;
376 #ifdef HAVE_ALBUMART
377 /* now draw the AA */
378 if (data->albumart && data->albumart->vp == vp
379 && data->albumart->draw)
381 draw_album_art(gwps, playback_current_aa_hid(data->playback_aa_slot),
382 false);
383 data->albumart->draw = false;
385 #endif
387 display->set_drawmode(DRMODE_SOLID);
390 #else /* HAVE_LCD_CHARCELL */
392 static bool draw_player_progress(struct gui_wps *gwps)
394 struct wps_state *state = gwps->state;
395 struct screen *display = gwps->display;
396 unsigned char progress_pattern[7];
397 int pos = 0;
398 int i;
400 int elapsed, length;
401 if (LIKELY(state->id3))
403 elapsed = state->id3->elapsed;
404 length = state->id3->length;
406 else
408 elapsed = 0;
409 length = 0;
412 if (length)
413 pos = 36 * (elapsed + state->ff_rewind_count) / length;
415 for (i = 0; i < 7; i++, pos -= 5)
417 if (pos <= 0)
418 progress_pattern[i] = 0x1fu;
419 else if (pos >= 5)
420 progress_pattern[i] = 0x00u;
421 else
422 progress_pattern[i] = 0x1fu >> pos;
425 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
426 return true;
429 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
431 static const unsigned char numbers[10][4] = {
432 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
433 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
434 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
435 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
436 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
437 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
438 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
439 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
440 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
441 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
444 struct wps_state *state = gwps->state;
445 struct screen *display = gwps->display;
446 struct wps_data *data = gwps->data;
447 unsigned char progress_pattern[7];
448 char timestr[10];
449 int time;
450 int time_idx = 0;
451 int pos = 0;
452 int pat_idx = 1;
453 int digit, i, j;
454 bool softchar;
456 int elapsed, length;
457 if (LIKELY(state->id3))
459 elapsed = state->id3->elapsed;
460 length = state->id3->length;
462 else
464 elapsed = 0;
465 length = 0;
468 if (buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
469 return;
471 time = elapsed + state->ff_rewind_count;
472 if (length)
473 pos = 55 * time / length;
475 memset(timestr, 0, sizeof(timestr));
476 format_time(timestr, sizeof(timestr)-2, time);
477 timestr[strlen(timestr)] = ':'; /* always safe */
479 for (i = 0; i < 11; i++, pos -= 5)
481 softchar = false;
482 memset(progress_pattern, 0, sizeof(progress_pattern));
484 if ((digit = timestr[time_idx]))
486 softchar = true;
487 digit -= '0';
489 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
491 memcpy(progress_pattern, numbers[digit], 4);
492 time_idx += 2;
494 else /* tens, shifted right */
496 for (j = 0; j < 4; j++)
497 progress_pattern[j] = numbers[digit][j] >> 1;
499 if (time_idx > 0) /* not the first group, add colon in front */
501 progress_pattern[1] |= 0x10u;
502 progress_pattern[3] |= 0x10u;
504 time_idx++;
507 if (pos >= 5)
508 progress_pattern[5] = progress_pattern[6] = 0x1fu;
511 if (pos > 0 && pos < 5)
513 softchar = true;
514 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
517 if (softchar && pat_idx < 8)
519 display->define_pattern(data->wps_progress_pat[pat_idx],
520 progress_pattern);
521 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
522 pat_idx++;
524 else if (pos <= 0)
525 buf = utf8encode(' ', buf);
526 else
527 buf = utf8encode(0xe115, buf); /* 2/7 _ */
529 *buf = '\0';
532 #endif /* HAVE_LCD_CHARCELL */
534 /* Return the index to the end token for the conditional token at index.
535 The conditional token can be either a start token or a separator
536 (i.e. option) token.
538 static int find_conditional_end(struct wps_data *data, int index)
540 int ret = index;
541 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
542 ret = data->tokens[ret].value.i;
544 /* ret now is the index to the end token for the conditional. */
545 return ret;
548 /* Evaluate the conditional that is at *token_index and return whether a skip
549 has ocurred. *token_index is updated with the new position.
551 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
553 if (!gwps)
554 return false;
556 struct wps_data *data = gwps->data;
558 int i, cond_end;
559 int cond_index = *token_index;
560 char result[128];
561 const char *value;
562 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
563 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
565 /* treat ?xx<true> constructs as if they had 2 options. */
566 if (num_options < 2)
567 num_options = 2;
569 int intval = num_options;
570 /* get_token_value needs to know the number of options in the enum */
571 value = get_token_value(gwps, &data->tokens[cond_index + 1],
572 result, sizeof(result), &intval);
574 /* intval is now the number of the enum option we want to read,
575 starting from 1. If intval is -1, we check if value is empty. */
576 if (intval == -1)
577 intval = (value && *value) ? 1 : num_options;
578 else if (intval > num_options || intval < 1)
579 intval = num_options;
581 data->tokens[cond_index].value.i = (intval << 8) + num_options;
583 /* skip to the appropriate enum case */
584 int next = cond_index + 2;
585 for (i = 1; i < intval; i++)
587 next = data->tokens[next].value.i;
589 *token_index = next;
591 if (prev_val == intval)
593 /* Same conditional case as previously. Return without clearing the
594 pictures */
595 return false;
598 cond_end = find_conditional_end(data, cond_index + 2);
599 for (i = cond_index + 3; i < cond_end; i++)
601 #ifdef HAVE_LCD_BITMAP
602 /* clear all pictures in the conditional and nested ones */
603 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
604 clear_image_pos(gwps, find_image(data->tokens[i].value.i&0xFF, data));
605 #endif
606 #ifdef HAVE_ALBUMART
607 if (data->albumart && data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
609 draw_album_art(gwps,
610 playback_current_aa_hid(data->playback_aa_slot), true);
611 data->albumart->draw = false;
613 #endif
616 return true;
620 /* Read a (sub)line to the given alignment format buffer.
621 linebuf is the buffer where the data is actually stored.
622 align is the alignment format that'll be used to display the text.
623 The return value indicates whether the line needs to be updated.
625 static bool get_line(struct gui_wps *gwps,
626 struct skin_subline *subline,
627 struct align_pos *align,
628 char *linebuf,
629 int linebuf_size)
631 struct wps_data *data = gwps->data;
633 char temp_buf[128];
634 char *buf = linebuf; /* will always point to the writing position */
635 char *linebuf_end = linebuf + linebuf_size - 1;
636 bool update = false;
637 int i;
639 /* alignment-related variables */
640 int cur_align;
641 char* cur_align_start;
642 cur_align_start = buf;
643 cur_align = WPS_ALIGN_LEFT;
644 align->left = NULL;
645 align->center = NULL;
646 align->right = NULL;
647 /* Process all tokens of the desired subline */
648 for (i = subline->first_token_idx;
649 i <= subline->last_token_idx; i++)
651 switch(data->tokens[i].type)
653 case WPS_TOKEN_CONDITIONAL:
654 /* place ourselves in the right conditional case */
655 update |= evaluate_conditional(gwps, &i);
656 break;
658 case WPS_TOKEN_CONDITIONAL_OPTION:
659 /* we've finished in the curent conditional case,
660 skip to the end of the conditional structure */
661 i = find_conditional_end(data, i);
662 break;
664 #ifdef HAVE_LCD_BITMAP
665 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
667 char n = data->tokens[i].value.i & 0xFF;
668 int subimage = data->tokens[i].value.i >> 8;
669 struct gui_img *img = find_image(n, data);
671 if (img && img->loaded)
672 img->display = subimage;
673 break;
675 #endif
677 case WPS_TOKEN_ALIGN_LEFT:
678 case WPS_TOKEN_ALIGN_LEFT_RTL:
679 case WPS_TOKEN_ALIGN_CENTER:
680 case WPS_TOKEN_ALIGN_RIGHT:
681 case WPS_TOKEN_ALIGN_RIGHT_RTL:
682 /* remember where the current aligned text started */
683 switch (cur_align)
685 case WPS_ALIGN_LEFT:
686 align->left = cur_align_start;
687 break;
689 case WPS_ALIGN_CENTER:
690 align->center = cur_align_start;
691 break;
693 case WPS_ALIGN_RIGHT:
694 align->right = cur_align_start;
695 break;
697 /* start a new alignment */
698 switch (data->tokens[i].type)
700 case WPS_TOKEN_ALIGN_LEFT:
701 cur_align = WPS_ALIGN_LEFT;
702 break;
703 case WPS_TOKEN_ALIGN_LEFT_RTL:
704 cur_align = lang_is_rtl() ? WPS_ALIGN_RIGHT :
705 WPS_ALIGN_LEFT;
706 break;
707 case WPS_TOKEN_ALIGN_CENTER:
708 cur_align = WPS_ALIGN_CENTER;
709 break;
710 case WPS_TOKEN_ALIGN_RIGHT:
711 cur_align = WPS_ALIGN_RIGHT;
712 break;
713 case WPS_TOKEN_ALIGN_RIGHT_RTL:
714 cur_align = lang_is_rtl() ? WPS_ALIGN_LEFT :
715 WPS_ALIGN_RIGHT;
716 break;
717 default:
718 break;
720 *buf++ = 0;
721 cur_align_start = buf;
722 break;
723 case WPS_VIEWPORT_ENABLE:
725 char label = data->tokens[i].value.i;
726 char temp = VP_DRAW_HIDEABLE;
727 /* viewports are allowed to share id's so find and enable
728 * all of them */
729 struct skin_token_list *list = data->viewports;
730 while (list)
732 struct skin_viewport *vp =
733 (struct skin_viewport *)list->token->value.data;
734 if (vp->label == label)
736 if (vp->hidden_flags&VP_DRAW_WASHIDDEN)
737 temp |= VP_DRAW_WASHIDDEN;
738 vp->hidden_flags = temp;
740 list = list->next;
743 break;
744 #ifdef HAVE_LCD_BITMAP
745 case WPS_VIEWPORT_CUSTOMLIST:
746 draw_playlist_viewer_list(gwps, data->tokens[i].value.data);
747 break;
748 #endif
749 default:
751 /* get the value of the tag and copy it to the buffer */
752 const char *value = get_token_value(gwps, &data->tokens[i],
753 temp_buf, sizeof(temp_buf), NULL);
754 if (value)
756 update = true;
757 while (*value && (buf < linebuf_end))
758 *buf++ = *value++;
760 break;
765 /* close the current alignment */
766 switch (cur_align)
768 case WPS_ALIGN_LEFT:
769 align->left = cur_align_start;
770 break;
772 case WPS_ALIGN_CENTER:
773 align->center = cur_align_start;
774 break;
776 case WPS_ALIGN_RIGHT:
777 align->right = cur_align_start;
778 break;
781 return update;
783 static void get_subline_timeout(struct gui_wps *gwps, struct skin_subline *subline)
785 struct wps_data *data = gwps->data;
786 int i;
787 subline->time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
789 for (i = subline->first_token_idx;
790 i <= subline->last_token_idx; i++)
792 switch(data->tokens[i].type)
794 case WPS_TOKEN_CONDITIONAL:
795 /* place ourselves in the right conditional case */
796 evaluate_conditional(gwps, &i);
797 break;
799 case WPS_TOKEN_CONDITIONAL_OPTION:
800 /* we've finished in the curent conditional case,
801 skip to the end of the conditional structure */
802 i = find_conditional_end(data, i);
803 break;
805 case WPS_TOKEN_SUBLINE_TIMEOUT:
806 subline->time_mult = data->tokens[i].value.i;
807 break;
809 default:
810 break;
815 /* Calculates which subline should be displayed for the specified line
816 Returns true iff the subline must be refreshed */
817 static bool update_curr_subline(struct gui_wps *gwps, struct skin_line *line)
819 /* shortcut this whole thing if we need to reset the line completly */
820 if (line->curr_subline == NULL)
822 line->subline_expire_time = current_tick;
823 line->curr_subline = &line->sublines;
824 if (!line->curr_subline->next)
826 line->subline_expire_time += 100*HZ;
828 else
830 get_subline_timeout(gwps, line->curr_subline);
831 line->subline_expire_time += TIMEOUT_UNIT*line->curr_subline->time_mult;
833 return true;
835 /* if time to advance to next sub-line */
836 if (TIME_AFTER(current_tick, line->subline_expire_time - 1))
838 /* if there is only one subline, there is no need to search for a new one */
839 if (&line->sublines == line->curr_subline &&
840 line->curr_subline->next == NULL)
842 line->subline_expire_time += 100 * HZ;
843 return false;
845 if (line->curr_subline->next)
846 line->curr_subline = line->curr_subline->next;
847 else
848 line->curr_subline = &line->sublines;
849 get_subline_timeout(gwps, line->curr_subline);
850 line->subline_expire_time += TIMEOUT_UNIT*line->curr_subline->time_mult;
851 return true;
853 return false;
856 /* Display a line appropriately according to its alignment format.
857 format_align contains the text, separated between left, center and right.
858 line is the index of the line on the screen.
859 scroll indicates whether the line is a scrolling one or not.
861 static void write_line(struct screen *display,
862 struct align_pos *format_align,
863 int line,
864 bool scroll)
866 int left_width = 0, left_xpos;
867 int center_width = 0, center_xpos;
868 int right_width = 0, right_xpos;
869 int ypos;
870 int space_width;
871 int string_height;
872 int scroll_width;
874 /* calculate different string sizes and positions */
875 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
876 if (format_align->left != 0) {
877 display->getstringsize((unsigned char *)format_align->left,
878 &left_width, &string_height);
881 if (format_align->right != 0) {
882 display->getstringsize((unsigned char *)format_align->right,
883 &right_width, &string_height);
886 if (format_align->center != 0) {
887 display->getstringsize((unsigned char *)format_align->center,
888 &center_width, &string_height);
891 left_xpos = 0;
892 right_xpos = (display->getwidth() - right_width);
893 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
895 scroll_width = display->getwidth() - left_xpos;
897 /* Checks for overlapping strings.
898 If needed the overlapping strings will be merged, separated by a
899 space */
901 /* CASE 1: left and centered string overlap */
902 /* there is a left string, need to merge left and center */
903 if ((left_width != 0 && center_width != 0) &&
904 (left_xpos + left_width + space_width > center_xpos)) {
905 /* replace the former separator '\0' of left and
906 center string with a space */
907 *(--format_align->center) = ' ';
908 /* calculate the new width and position of the merged string */
909 left_width = left_width + space_width + center_width;
910 /* there is no centered string anymore */
911 center_width = 0;
913 /* there is no left string, move center to left */
914 if ((left_width == 0 && center_width != 0) &&
915 (left_xpos + left_width > center_xpos)) {
916 /* move the center string to the left string */
917 format_align->left = format_align->center;
918 /* calculate the new width and position of the string */
919 left_width = center_width;
920 /* there is no centered string anymore */
921 center_width = 0;
924 /* CASE 2: centered and right string overlap */
925 /* there is a right string, need to merge center and right */
926 if ((center_width != 0 && right_width != 0) &&
927 (center_xpos + center_width + space_width > right_xpos)) {
928 /* replace the former separator '\0' of center and
929 right string with a space */
930 *(--format_align->right) = ' ';
931 /* move the center string to the right after merge */
932 format_align->right = format_align->center;
933 /* calculate the new width and position of the merged string */
934 right_width = center_width + space_width + right_width;
935 right_xpos = (display->getwidth() - right_width);
936 /* there is no centered string anymore */
937 center_width = 0;
939 /* there is no right string, move center to right */
940 if ((center_width != 0 && right_width == 0) &&
941 (center_xpos + center_width > right_xpos)) {
942 /* move the center string to the right string */
943 format_align->right = format_align->center;
944 /* calculate the new width and position of the string */
945 right_width = center_width;
946 right_xpos = (display->getwidth() - right_width);
947 /* there is no centered string anymore */
948 center_width = 0;
951 /* CASE 3: left and right overlap
952 There is no center string anymore, either there never
953 was one or it has been merged in case 1 or 2 */
954 /* there is a left string, need to merge left and right */
955 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
956 (left_xpos + left_width + space_width > right_xpos)) {
957 /* replace the former separator '\0' of left and
958 right string with a space */
959 *(--format_align->right) = ' ';
960 /* calculate the new width and position of the string */
961 left_width = left_width + space_width + right_width;
962 /* there is no right string anymore */
963 right_width = 0;
965 /* there is no left string, move right to left */
966 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
967 (left_width > right_xpos)) {
968 /* move the right string to the left string */
969 format_align->left = format_align->right;
970 /* calculate the new width and position of the string */
971 left_width = right_width;
972 /* there is no right string anymore */
973 right_width = 0;
976 ypos = (line * string_height);
979 if (scroll && ((left_width > scroll_width) ||
980 (center_width > scroll_width) ||
981 (right_width > scroll_width)))
983 display->puts_scroll(0, line,
984 (unsigned char *)format_align->left);
986 else
988 #ifdef HAVE_LCD_BITMAP
989 /* clear the line first */
990 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
991 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
992 display->set_drawmode(DRMODE_SOLID);
993 #endif
995 /* Nasty hack: we output an empty scrolling string,
996 which will reset the scroller for that line */
997 display->puts_scroll(0, line, (unsigned char *)"");
999 /* print aligned strings */
1000 if (left_width != 0)
1002 display->putsxy(left_xpos, ypos,
1003 (unsigned char *)format_align->left);
1005 if (center_width != 0)
1007 display->putsxy(center_xpos, ypos,
1008 (unsigned char *)format_align->center);
1010 if (right_width != 0)
1012 display->putsxy(right_xpos, ypos,
1013 (unsigned char *)format_align->right);
1018 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode)
1020 struct wps_data *data = gwps->data;
1021 struct screen *display = gwps->display;
1023 if (!data || !display || !gwps->state)
1024 return false;
1026 unsigned flags;
1027 char linebuf[MAX_PATH];
1029 struct align_pos align;
1030 align.left = NULL;
1031 align.center = NULL;
1032 align.right = NULL;
1035 struct skin_token_list *viewport_list;
1037 bool update_line, new_subline_refresh;
1039 #ifdef HAVE_LCD_BITMAP
1041 /* to find out wether the peak meter is enabled we
1042 assume it wasn't until we find a line that contains
1043 the peak meter. We can't use peak_meter_enabled itself
1044 because that would mean to turn off the meter thread
1045 temporarily. (That shouldn't matter unless yield
1046 or sleep is called but who knows...)
1048 bool enable_pm = false;
1050 #endif
1052 /* reset to first subline if refresh all flag is set */
1053 if (refresh_mode == WPS_REFRESH_ALL)
1055 struct skin_line *line;
1056 struct skin_viewport *skin_viewport = find_viewport(VP_DEFAULT_LABEL, data);
1058 if (!(skin_viewport->hidden_flags & VP_NEVER_VISIBLE))
1060 display->set_viewport(&skin_viewport->vp);
1061 display->clear_viewport();
1064 for (viewport_list = data->viewports;
1065 viewport_list; viewport_list = viewport_list->next)
1067 skin_viewport =
1068 (struct skin_viewport *)viewport_list->token->value.data;
1069 for(line = skin_viewport->lines; line; line = line->next)
1071 line->curr_subline = NULL;
1076 #ifdef HAVE_LCD_CHARCELLS
1077 int i;
1078 for (i = 0; i < 8; i++)
1080 if (data->wps_progress_pat[i] == 0)
1081 data->wps_progress_pat[i] = display->get_locked_pattern();
1083 #endif
1085 /* disable any viewports which are conditionally displayed.
1086 * If we are only refreshing the peak meter then don't change the viewport
1087 * enabled flags as this will stop scrolling. viewports cant be
1088 * toggled in this refresh mode anyway (FS#10215)*/
1089 if (refresh_mode != WPS_REFRESH_PEAK_METER)
1091 for (viewport_list = data->viewports;
1092 viewport_list; viewport_list = viewport_list->next)
1094 struct skin_viewport *skin_viewport =
1095 (struct skin_viewport *)viewport_list->token->value.data;
1096 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1098 continue;
1100 if (skin_viewport->hidden_flags&VP_DRAW_HIDEABLE)
1102 if (skin_viewport->hidden_flags&VP_DRAW_HIDDEN)
1103 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1104 else
1105 skin_viewport->hidden_flags |= VP_DRAW_HIDDEN;
1109 int viewport_count = 0;
1110 for (viewport_list = data->viewports;
1111 viewport_list; viewport_list = viewport_list->next, viewport_count++)
1113 struct skin_viewport *skin_viewport =
1114 (struct skin_viewport *)viewport_list->token->value.data;
1115 unsigned vp_refresh_mode = refresh_mode;
1117 display->set_viewport(&skin_viewport->vp);
1119 int hidden_vp = 0;
1121 #ifdef HAVE_LCD_BITMAP
1122 /* Set images to not to be displayed */
1123 struct skin_token_list *imglist = data->images;
1124 while (imglist)
1126 struct gui_img *img = (struct gui_img *)imglist->token->value.data;
1127 img->display = -1;
1128 imglist = imglist->next;
1130 #endif
1131 /* dont redraw the viewport if its disabled */
1132 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1133 { /* don't draw anything into this one */
1134 vp_refresh_mode = 0; hidden_vp = true;
1136 else if ((skin_viewport->hidden_flags&VP_DRAW_HIDDEN))
1138 if (!(skin_viewport->hidden_flags&VP_DRAW_WASHIDDEN))
1139 display->scroll_stop(&skin_viewport->vp);
1140 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1141 continue;
1143 else if (((skin_viewport->hidden_flags&
1144 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1145 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1147 vp_refresh_mode = WPS_REFRESH_ALL;
1148 skin_viewport->hidden_flags = VP_DRAW_HIDEABLE;
1151 if (vp_refresh_mode == WPS_REFRESH_ALL)
1153 display->clear_viewport();
1156 /* loop over the lines for this viewport */
1157 struct skin_line *line;
1158 int line_count = 0;
1160 for (line = skin_viewport->lines; line; line = line->next, line_count++)
1162 struct skin_subline *subline;
1163 memset(linebuf, 0, sizeof(linebuf));
1164 update_line = false;
1166 /* get current subline for the line */
1167 new_subline_refresh = update_curr_subline(gwps, line);
1168 subline = line->curr_subline;
1169 flags = line->curr_subline->line_type;
1171 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1172 || new_subline_refresh || hidden_vp)
1174 /* get_line tells us if we need to update the line */
1175 update_line = get_line(gwps, subline,
1176 &align, linebuf, sizeof(linebuf));
1178 #ifdef HAVE_LCD_BITMAP
1179 /* peakmeter */
1180 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1182 /* the peakmeter should be alone on its line */
1183 update_line = false;
1185 int h = font_get(skin_viewport->vp.font)->height;
1186 int peak_meter_y = line_count* h;
1188 /* The user might decide to have the peak meter in the last
1189 line so that it is only displayed if no status bar is
1190 visible. If so we neither want do draw nor enable the
1191 peak meter. */
1192 if (peak_meter_y + h <= skin_viewport->vp.y+skin_viewport->vp.height) {
1193 /* found a line with a peak meter -> remember that we must
1194 enable it later */
1195 enable_pm = true;
1196 peak_meter_enabled = true;
1197 peak_meter_screen(gwps->display, 0, peak_meter_y,
1198 MIN(h, skin_viewport->vp.y+skin_viewport->vp.height - peak_meter_y));
1200 else
1202 peak_meter_enabled = false;
1206 #else /* HAVE_LCD_CHARCELL */
1208 /* progressbar */
1209 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1211 if (data->full_line_progressbar)
1212 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1213 else
1214 draw_player_progress(gwps);
1216 #endif
1218 if (update_line && !hidden_vp &&
1219 /* conditionals clear the line which means if the %Vd is put into the default
1220 viewport there will be a blank line.
1221 To get around this we dont allow any actual drawing to happen in the
1222 deault vp if other vp's are defined */
1223 ((skin_viewport->label != VP_DEFAULT_LABEL && viewport_list->next) ||
1224 !viewport_list->next))
1226 if (flags & WPS_REFRESH_SCROLL)
1228 /* if the line is a scrolling one we don't want to update
1229 too often, so that it has the time to scroll */
1230 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1231 write_line(display, &align, line_count, true);
1233 else
1234 write_line(display, &align, line_count, false);
1237 #ifdef HAVE_LCD_BITMAP
1238 /* progressbar */
1239 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1241 if (skin_viewport->pb)
1243 draw_progressbar(gwps, skin_viewport);
1246 /* Now display any images in this viewport */
1247 if (!hidden_vp)
1248 wps_display_images(gwps, &skin_viewport->vp);
1249 #endif
1252 #ifdef HAVE_LCD_BITMAP
1253 data->peak_meter_enabled = enable_pm;
1254 #endif
1255 /* Restore the default viewport */
1256 display->set_viewport(NULL);
1258 display->update();
1260 return true;