slightly rework peakmeter handling to make it cleaner and be able to be used in condi...
[kugel-rb.git] / apps / gui / skin_engine / skin_display.c
blob9b42a7c18b450dc813ce026d105ed5f3b1b83c2a
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-extra.h"
25 #include "misc.h"
26 #include "font.h"
27 #include "system.h"
28 #include "rbunicode.h"
29 #include "sound.h"
30 #include "powermgmt.h"
31 #ifdef DEBUG
32 #include "debug.h"
33 #endif
34 #include "abrepeat.h"
35 #include "lang.h"
36 #include "language.h"
37 #include "statusbar.h"
38 #include "settings.h"
39 #include "scrollbar.h"
40 #include "screen_access.h"
41 #include "playlist.h"
42 #include "audio.h"
43 #include "tagcache.h"
45 #ifdef HAVE_LCD_BITMAP
46 #include "peakmeter.h"
47 /* Image stuff */
48 #include "bmp.h"
49 #ifdef HAVE_ALBUMART
50 #include "albumart.h"
51 #endif
52 #endif
54 #include "cuesheet.h"
55 #if CONFIG_CODEC == SWCODEC
56 #include "playback.h"
57 #endif
58 #include "backdrop.h"
59 #include "viewport.h"
62 #include "wps_internals.h"
63 #include "skin_engine.h"
64 #include "statusbar-skinned.h"
66 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode);
68 /* update a skinned screen, update_type is WPS_REFRESH_* values.
69 * Usually it should only be WPS_REFRESH_NON_STATIC
70 * A full update will be done if required (state.do_full_update == true)
72 bool skin_update(struct gui_wps *gwps, unsigned int update_type)
74 bool retval;
75 /* This maybe shouldnt be here, but while the skin is only used to
76 * display the music screen this is better than whereever we are being
77 * called from. This is also safe for skined screen which dont use the id3 */
78 struct mp3entry *id3 = gwps->state->id3;
79 bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
80 gwps->sync_data->do_full_update |= cuesheet_update;
82 retval = skin_redraw(gwps, gwps->sync_data->do_full_update ?
83 WPS_REFRESH_ALL : update_type);
84 return retval;
87 #ifdef HAVE_LCD_BITMAP
89 void skin_statusbar_changed(struct gui_wps *skin)
91 if (!skin)
92 return;
93 struct wps_data *data = skin->data;
94 const struct screen *display = skin->display;
95 const int screen = display->screen_type;
97 struct viewport *vp = &find_viewport(VP_DEFAULT_LABEL, data)->vp;
98 viewport_set_defaults(vp, screen);
100 if (data->wps_sb_tag)
101 { /* fix up the default viewport */
102 if (data->show_sb_on_wps)
104 if (statusbar_position(screen) != STATUSBAR_OFF)
105 return; /* vp is fixed already */
107 vp->y = STATUSBAR_HEIGHT;
108 vp->height = display->lcdheight - STATUSBAR_HEIGHT;
110 else
112 if (statusbar_position(screen) == STATUSBAR_OFF)
113 return; /* vp is fixed already */
114 vp->y = vp->x = 0;
115 vp->height = display->lcdheight;
116 vp->width = display->lcdwidth;
121 static void draw_progressbar(struct gui_wps *gwps,
122 struct progressbar *pb)
124 struct screen *display = gwps->display;
125 struct viewport *vp = pb->vp;
126 struct wps_state *state = gwps->state;
127 struct mp3entry *id3 = state->id3;
128 int y = pb->y, height = pb->height;
129 unsigned long length, elapsed;
131 if (height < 0)
132 height = font_get(vp->font)->height;
134 if (y < 0)
136 int line_height = font_get(vp->font)->height;
137 /* center the pb in the line, but only if the line is higher than the pb */
138 int center = (line_height-height)/2;
139 /* if Y was not set calculate by font height,Y is -line_number-1 */
140 y = (-y -1)*line_height + (0 > center ? 0 : center);
143 if (pb->type == WPS_TOKEN_VOLUMEBAR)
145 int minvol = sound_min(SOUND_VOLUME);
146 int maxvol = sound_max(SOUND_VOLUME);
147 length = maxvol-minvol;
148 elapsed = global_settings.volume-minvol;
150 else if (pb->type == WPS_TOKEN_BATTERY_PERCENTBAR)
152 length = 100;
153 elapsed = battery_level();
155 else if (id3 && id3->length)
157 length = id3->length;
158 elapsed = id3->elapsed + state->ff_rewind_count;
160 else
162 length = 1;
163 elapsed = 0;
166 if (pb->have_bitmap_pb)
167 gui_bitmap_scrollbar_draw(display, pb->bm,
168 pb->x, y, pb->width, pb->bm.height,
169 length, 0, elapsed, HORIZONTAL);
170 else
171 gui_scrollbar_draw(display, pb->x, y, pb->width, height,
172 length, 0, elapsed, HORIZONTAL);
174 if (pb->type == WPS_TOKEN_PROGRESSBAR && id3 && id3->length)
176 #ifdef AB_REPEAT_ENABLE
177 if (ab_repeat_mode_enabled())
178 ab_draw_markers(display, id3->length,
179 pb->x, y, pb->width, height);
180 #endif
182 if (id3->cuesheet)
183 cue_draw_markers(display, id3->cuesheet, id3->length,
184 pb->x, y+1, pb->width, height-2);
188 static void draw_playlist_viewer_list(struct gui_wps *gwps,
189 struct playlistviewer *viewer)
191 struct wps_state *state = gwps->state;
192 int lines = viewport_get_nb_lines(viewer->vp);
193 int line_height = font_get(viewer->vp->font)->height;
194 int cur_playlist_pos = playlist_get_display_index();
195 int start_item = MAX(0, cur_playlist_pos + viewer->start_offset);
196 int i;
197 struct wps_token token;
198 int x, length, alignment = WPS_TOKEN_ALIGN_LEFT;
200 struct mp3entry *pid3;
201 char buf[MAX_PATH*2], tempbuf[MAX_PATH];
202 const char *filename;
204 gwps->display->set_viewport(viewer->vp);
205 for(i=start_item; (i-start_item)<lines && i<=playlist_amount(); i++)
207 filename = playlist_peek(i-cur_playlist_pos);
208 if (i == cur_playlist_pos)
210 pid3 = state->id3;
212 else if (i == cur_playlist_pos+1)
214 pid3 = state->nid3;
216 #if CONFIG_CODEC == SWCODEC
217 else if (i>cur_playlist_pos)
219 #ifdef HAVE_TC_RAMCACHE
220 if (tagcache_fill_tags(&viewer->tempid3, filename))
222 pid3 = &viewer->tempid3;
224 else
225 #endif
226 if (!audio_peek_track(&pid3, i-cur_playlist_pos))
227 pid3 = NULL;
229 #endif
230 else
232 pid3 = NULL;
235 int line = pid3 ? TRACK_HAS_INFO : TRACK_HAS_NO_INFO;
236 int j = 0, cur_string = 0;
237 unsigned int line_len = 0;
238 buf[0] = '\0';
239 while (j < viewer->lines[line].count && line_len < sizeof(buf))
241 const char *out = NULL;
242 token.type = viewer->lines[line].tokens[j];
243 token.value.i = 0;
244 token.next = false;
245 out = get_id3_token(&token, pid3, tempbuf, sizeof(tempbuf), -1, NULL);
246 if (out)
248 line_len = strlcat(buf, out, sizeof(buf));
249 j++;
250 continue;
252 switch (viewer->lines[line].tokens[j])
254 case WPS_TOKEN_ALIGN_CENTER:
255 case WPS_TOKEN_ALIGN_LEFT:
256 case WPS_TOKEN_ALIGN_LEFT_RTL:
257 case WPS_TOKEN_ALIGN_RIGHT:
258 case WPS_TOKEN_ALIGN_RIGHT_RTL:
259 alignment = viewer->lines[line].tokens[j];
260 tempbuf[0] = '\0';
261 break;
262 case WPS_TOKEN_STRING:
263 case WPS_TOKEN_CHARACTER:
264 snprintf(tempbuf, sizeof(tempbuf), "%s",
265 viewer->lines[line].strings[cur_string]);
266 cur_string++;
267 break;
268 case WPS_TOKEN_PLAYLIST_POSITION:
269 snprintf(tempbuf, sizeof(tempbuf), "%d", i);
270 break;
271 case WPS_TOKEN_FILE_NAME:
272 get_dir(tempbuf, sizeof(tempbuf), filename, 0);
273 break;
274 case WPS_TOKEN_FILE_PATH:
275 snprintf(tempbuf, sizeof(tempbuf), "%s", filename);
276 break;
277 default:
278 tempbuf[0] = '\0';
279 break;
281 if (tempbuf[0])
283 line_len = strlcat(buf, tempbuf, sizeof(buf));
285 j++;
288 int vpwidth = viewer->vp->width;
289 length = gwps->display->getstringsize(buf, NULL, NULL);
290 if (viewer->lines[line].scroll && length >= vpwidth)
292 gwps->display->puts_scroll(0, (i-start_item), buf );
294 else
296 if (length >= vpwidth)
297 x = 0;
298 else
300 switch (alignment)
302 case WPS_TOKEN_ALIGN_CENTER:
303 x = (vpwidth-length)/2;
304 break;
305 case WPS_TOKEN_ALIGN_LEFT_RTL:
306 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
308 x = vpwidth - length;
309 break;
311 case WPS_TOKEN_ALIGN_LEFT:
312 x = 0;
313 break;
314 case WPS_TOKEN_ALIGN_RIGHT_RTL:
315 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
317 x = 0;
318 break;
320 case WPS_TOKEN_ALIGN_RIGHT:
321 x = vpwidth - length;
322 break;
323 default:
324 x = 0;
325 break;
328 gwps->display->putsxy(x, (i-start_item)*line_height, buf );
334 /* clears the area where the image was shown */
335 static void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
337 if(!gwps)
338 return;
339 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
340 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
341 gwps->display->set_drawmode(DRMODE_SOLID);
344 static void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
346 struct screen *display = gwps->display;
347 if(img->always_display)
348 display->set_drawmode(DRMODE_FG);
349 else
350 display->set_drawmode(DRMODE_SOLID);
352 #if LCD_DEPTH > 1
353 if(img->bm.format == FORMAT_MONO) {
354 #endif
355 display->mono_bitmap_part(img->bm.data,
356 0, img->subimage_height * subimage,
357 img->bm.width, img->x,
358 img->y, img->bm.width,
359 img->subimage_height);
360 #if LCD_DEPTH > 1
361 } else {
362 display->transparent_bitmap_part((fb_data *)img->bm.data,
363 0, img->subimage_height * subimage,
364 STRIDE(display->screen_type,
365 img->bm.width, img->bm.height),
366 img->x, img->y, img->bm.width,
367 img->subimage_height);
369 #endif
372 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
374 if(!gwps || !gwps->data || !gwps->display)
375 return;
377 struct wps_data *data = gwps->data;
378 struct screen *display = gwps->display;
379 struct skin_token_list *list = data->images;
381 while (list)
383 struct gui_img *img = (struct gui_img*)list->token->value.data;
384 if (img->loaded)
386 if (img->display >= 0)
388 wps_draw_image(gwps, img, img->display);
390 else if (img->always_display && img->vp == vp)
392 wps_draw_image(gwps, img, 0);
395 list = list->next;
397 #ifdef HAVE_ALBUMART
398 /* now draw the AA */
399 if (data->albumart && data->albumart->vp == vp
400 && data->albumart->draw)
402 draw_album_art(gwps, playback_current_aa_hid(data->playback_aa_slot),
403 false);
404 data->albumart->draw = false;
406 #endif
408 display->set_drawmode(DRMODE_SOLID);
411 #else /* HAVE_LCD_CHARCELL */
413 static bool draw_player_progress(struct gui_wps *gwps)
415 struct wps_state *state = gwps->state;
416 struct screen *display = gwps->display;
417 unsigned char progress_pattern[7];
418 int pos = 0;
419 int i;
421 int elapsed, length;
422 if (LIKELY(state->id3))
424 elapsed = state->id3->elapsed;
425 length = state->id3->length;
427 else
429 elapsed = 0;
430 length = 0;
433 if (length)
434 pos = 36 * (elapsed + state->ff_rewind_count) / length;
436 for (i = 0; i < 7; i++, pos -= 5)
438 if (pos <= 0)
439 progress_pattern[i] = 0x1fu;
440 else if (pos >= 5)
441 progress_pattern[i] = 0x00u;
442 else
443 progress_pattern[i] = 0x1fu >> pos;
446 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
447 return true;
450 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
452 static const unsigned char numbers[10][4] = {
453 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
454 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
455 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
456 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
457 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
458 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
459 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
460 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
461 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
462 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
465 struct wps_state *state = gwps->state;
466 struct screen *display = gwps->display;
467 struct wps_data *data = gwps->data;
468 unsigned char progress_pattern[7];
469 char timestr[10];
470 int time;
471 int time_idx = 0;
472 int pos = 0;
473 int pat_idx = 1;
474 int digit, i, j;
475 bool softchar;
477 int elapsed, length;
478 if (LIKELY(state->id3))
480 elapsed = state->id3->elapsed;
481 length = state->id3->length;
483 else
485 elapsed = 0;
486 length = 0;
489 if (buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
490 return;
492 time = elapsed + state->ff_rewind_count;
493 if (length)
494 pos = 55 * time / length;
496 memset(timestr, 0, sizeof(timestr));
497 format_time(timestr, sizeof(timestr)-2, time);
498 timestr[strlen(timestr)] = ':'; /* always safe */
500 for (i = 0; i < 11; i++, pos -= 5)
502 softchar = false;
503 memset(progress_pattern, 0, sizeof(progress_pattern));
505 if ((digit = timestr[time_idx]))
507 softchar = true;
508 digit -= '0';
510 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
512 memcpy(progress_pattern, numbers[digit], 4);
513 time_idx += 2;
515 else /* tens, shifted right */
517 for (j = 0; j < 4; j++)
518 progress_pattern[j] = numbers[digit][j] >> 1;
520 if (time_idx > 0) /* not the first group, add colon in front */
522 progress_pattern[1] |= 0x10u;
523 progress_pattern[3] |= 0x10u;
525 time_idx++;
528 if (pos >= 5)
529 progress_pattern[5] = progress_pattern[6] = 0x1fu;
532 if (pos > 0 && pos < 5)
534 softchar = true;
535 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
538 if (softchar && pat_idx < 8)
540 display->define_pattern(data->wps_progress_pat[pat_idx],
541 progress_pattern);
542 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
543 pat_idx++;
545 else if (pos <= 0)
546 buf = utf8encode(' ', buf);
547 else
548 buf = utf8encode(0xe115, buf); /* 2/7 _ */
550 *buf = '\0';
553 #endif /* HAVE_LCD_CHARCELL */
555 /* Return the index to the end token for the conditional token at index.
556 The conditional token can be either a start token or a separator
557 (i.e. option) token.
559 static int find_conditional_end(struct wps_data *data, int index)
561 int ret = index;
562 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
563 ret = data->tokens[ret].value.i;
565 /* ret now is the index to the end token for the conditional. */
566 return ret;
569 /* Evaluate the conditional that is at *token_index and return whether a skip
570 has ocurred. *token_index is updated with the new position.
572 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
574 if (!gwps)
575 return false;
577 struct wps_data *data = gwps->data;
579 int i, cond_end;
580 int cond_index = *token_index;
581 char result[128];
582 const char *value;
583 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
584 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
586 /* treat ?xx<true> constructs as if they had 2 options. */
587 if (num_options < 2)
588 num_options = 2;
590 int intval = num_options;
591 /* get_token_value needs to know the number of options in the enum */
592 value = get_token_value(gwps, &data->tokens[cond_index + 1],
593 result, sizeof(result), &intval);
595 /* intval is now the number of the enum option we want to read,
596 starting from 1. If intval is -1, we check if value is empty. */
597 if (intval == -1)
598 intval = (value && *value) ? 1 : num_options;
599 else if (intval > num_options || intval < 1)
600 intval = num_options;
602 data->tokens[cond_index].value.i = (intval << 8) + num_options;
604 /* skip to the appropriate enum case */
605 int next = cond_index + 2;
606 for (i = 1; i < intval; i++)
608 next = data->tokens[next].value.i;
610 *token_index = next;
612 if (prev_val == intval)
614 /* Same conditional case as previously. Return without clearing the
615 pictures */
616 return false;
619 cond_end = find_conditional_end(data, cond_index + 2);
620 for (i = cond_index + 3; i < cond_end; i++)
622 #ifdef HAVE_LCD_BITMAP
623 /* clear all pictures in the conditional and nested ones */
624 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
625 clear_image_pos(gwps, find_image(data->tokens[i].value.i&0xFF, data));
626 else if (data->tokens[i].type == WPS_TOKEN_VOLUMEBAR ||
627 data->tokens[i].type == WPS_TOKEN_PROGRESSBAR ||
628 data->tokens[i].type == WPS_TOKEN_BATTERY_PERCENTBAR )
630 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
631 bar->draw = false;
633 else if (data->tokens[i].type == WPS_TOKEN_PEAKMETER)
635 data->peak_meter_enabled = false;
637 #endif
638 #ifdef HAVE_ALBUMART
639 if (data->albumart && data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
641 draw_album_art(gwps,
642 playback_current_aa_hid(data->playback_aa_slot), true);
643 data->albumart->draw = false;
645 #endif
648 return true;
652 /* Read a (sub)line to the given alignment format buffer.
653 linebuf is the buffer where the data is actually stored.
654 align is the alignment format that'll be used to display the text.
655 The return value indicates whether the line needs to be updated.
657 static bool get_line(struct gui_wps *gwps,
658 struct skin_subline *subline,
659 struct align_pos *align,
660 char *linebuf,
661 int linebuf_size,
662 unsigned refresh_mode)
664 struct wps_data *data = gwps->data;
666 char temp_buf[128];
667 char *buf = linebuf; /* will always point to the writing position */
668 char *linebuf_end = linebuf + linebuf_size - 1;
669 bool update = false;
670 int i;
671 (void)refresh_mode; /* silence warning on charcell */
673 /* alignment-related variables */
674 int cur_align;
675 char* cur_align_start;
676 cur_align_start = buf;
677 cur_align = WPS_ALIGN_LEFT;
678 align->left = NULL;
679 align->center = NULL;
680 align->right = NULL;
681 /* Process all tokens of the desired subline */
682 for (i = subline->first_token_idx;
683 i <= subline->last_token_idx; i++)
685 switch(data->tokens[i].type)
687 case WPS_TOKEN_CONDITIONAL:
688 /* place ourselves in the right conditional case */
689 update |= evaluate_conditional(gwps, &i);
690 break;
692 case WPS_TOKEN_CONDITIONAL_OPTION:
693 /* we've finished in the curent conditional case,
694 skip to the end of the conditional structure */
695 i = find_conditional_end(data, i);
696 break;
698 #ifdef HAVE_LCD_BITMAP
699 case WPS_TOKEN_PEAKMETER:
700 data->peak_meter_enabled = true;
701 break;
702 case WPS_TOKEN_VOLUMEBAR:
703 case WPS_TOKEN_BATTERY_PERCENTBAR:
704 case WPS_TOKEN_PROGRESSBAR:
706 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
707 bar->draw = true;
709 break;
710 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
712 char n = data->tokens[i].value.i & 0xFF;
713 int subimage = data->tokens[i].value.i >> 8;
714 struct gui_img *img = find_image(n, data);
716 if (img && img->loaded)
717 img->display = subimage;
718 break;
720 case WPS_TOKEN_DRAW_INBUILTBAR:
721 gui_statusbar_draw(&(statusbars.statusbars[gwps->display->screen_type]),
722 refresh_mode == WPS_REFRESH_ALL,
723 data->tokens[i].value.data);
724 break;
725 #endif
727 case WPS_TOKEN_ALIGN_LEFT:
728 case WPS_TOKEN_ALIGN_LEFT_RTL:
729 case WPS_TOKEN_ALIGN_CENTER:
730 case WPS_TOKEN_ALIGN_RIGHT:
731 case WPS_TOKEN_ALIGN_RIGHT_RTL:
732 /* remember where the current aligned text started */
733 switch (cur_align)
735 case WPS_ALIGN_LEFT:
736 align->left = cur_align_start;
737 break;
739 case WPS_ALIGN_CENTER:
740 align->center = cur_align_start;
741 break;
743 case WPS_ALIGN_RIGHT:
744 align->right = cur_align_start;
745 break;
747 /* start a new alignment */
748 switch (data->tokens[i].type)
750 case WPS_TOKEN_ALIGN_LEFT:
751 cur_align = WPS_ALIGN_LEFT;
752 break;
753 case WPS_TOKEN_ALIGN_LEFT_RTL:
754 cur_align = lang_is_rtl() ? WPS_ALIGN_RIGHT :
755 WPS_ALIGN_LEFT;
756 break;
757 case WPS_TOKEN_ALIGN_CENTER:
758 cur_align = WPS_ALIGN_CENTER;
759 break;
760 case WPS_TOKEN_ALIGN_RIGHT:
761 cur_align = WPS_ALIGN_RIGHT;
762 break;
763 case WPS_TOKEN_ALIGN_RIGHT_RTL:
764 cur_align = lang_is_rtl() ? WPS_ALIGN_LEFT :
765 WPS_ALIGN_RIGHT;
766 break;
767 default:
768 break;
770 *buf++ = 0;
771 cur_align_start = buf;
772 break;
773 case WPS_VIEWPORT_ENABLE:
775 char label = data->tokens[i].value.i;
776 char temp = VP_DRAW_HIDEABLE;
777 /* viewports are allowed to share id's so find and enable
778 * all of them */
779 struct skin_token_list *list = data->viewports;
780 while (list)
782 struct skin_viewport *vp =
783 (struct skin_viewport *)list->token->value.data;
784 if (vp->label == label)
786 if (vp->hidden_flags&VP_DRAW_WASHIDDEN)
787 temp |= VP_DRAW_WASHIDDEN;
788 vp->hidden_flags = temp;
790 list = list->next;
793 break;
794 #ifdef HAVE_LCD_BITMAP
795 case WPS_TOKEN_UIVIEWPORT_ENABLE:
796 sb_set_info_vp(gwps->display->screen_type,
797 data->tokens[i].value.i|VP_INFO_LABEL);
798 break;
799 case WPS_VIEWPORT_CUSTOMLIST:
800 draw_playlist_viewer_list(gwps, data->tokens[i].value.data);
801 break;
802 #endif
803 default:
805 /* get the value of the tag and copy it to the buffer */
806 const char *value = get_token_value(gwps, &data->tokens[i],
807 temp_buf, sizeof(temp_buf), NULL);
808 if (value)
810 update = true;
811 while (*value && (buf < linebuf_end))
812 *buf++ = *value++;
814 break;
819 /* close the current alignment */
820 switch (cur_align)
822 case WPS_ALIGN_LEFT:
823 align->left = cur_align_start;
824 break;
826 case WPS_ALIGN_CENTER:
827 align->center = cur_align_start;
828 break;
830 case WPS_ALIGN_RIGHT:
831 align->right = cur_align_start;
832 break;
835 return update;
837 static void get_subline_timeout(struct gui_wps *gwps, struct skin_subline *subline)
839 struct wps_data *data = gwps->data;
840 int i;
841 subline->time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
843 for (i = subline->first_token_idx;
844 i <= subline->last_token_idx; i++)
846 switch(data->tokens[i].type)
848 case WPS_TOKEN_CONDITIONAL:
849 /* place ourselves in the right conditional case */
850 evaluate_conditional(gwps, &i);
851 break;
853 case WPS_TOKEN_CONDITIONAL_OPTION:
854 /* we've finished in the curent conditional case,
855 skip to the end of the conditional structure */
856 i = find_conditional_end(data, i);
857 break;
859 case WPS_TOKEN_SUBLINE_TIMEOUT:
860 subline->time_mult = data->tokens[i].value.i;
861 break;
863 default:
864 break;
869 /* Calculates which subline should be displayed for the specified line
870 Returns true iff the subline must be refreshed */
871 static bool update_curr_subline(struct gui_wps *gwps, struct skin_line *line)
873 /* shortcut this whole thing if we need to reset the line completly */
874 if (line->curr_subline == NULL)
876 line->subline_expire_time = current_tick;
877 line->curr_subline = &line->sublines;
878 if (!line->curr_subline->next)
880 line->subline_expire_time += 100*HZ;
882 else
884 get_subline_timeout(gwps, line->curr_subline);
885 line->subline_expire_time += TIMEOUT_UNIT*line->curr_subline->time_mult;
887 return true;
889 /* if time to advance to next sub-line */
890 if (TIME_AFTER(current_tick, line->subline_expire_time - 1))
892 /* if there is only one subline, there is no need to search for a new one */
893 if (&line->sublines == line->curr_subline &&
894 line->curr_subline->next == NULL)
896 line->subline_expire_time += 100 * HZ;
897 return false;
899 if (line->curr_subline->next)
900 line->curr_subline = line->curr_subline->next;
901 else
902 line->curr_subline = &line->sublines;
903 get_subline_timeout(gwps, line->curr_subline);
904 line->subline_expire_time = current_tick + TIMEOUT_UNIT*line->curr_subline->time_mult;
905 return true;
907 return false;
910 /* Display a line appropriately according to its alignment format.
911 format_align contains the text, separated between left, center and right.
912 line is the index of the line on the screen.
913 scroll indicates whether the line is a scrolling one or not.
915 static void write_line(struct screen *display,
916 struct align_pos *format_align,
917 int line,
918 bool scroll)
920 int left_width = 0, left_xpos;
921 int center_width = 0, center_xpos;
922 int right_width = 0, right_xpos;
923 int ypos;
924 int space_width;
925 int string_height;
926 int scroll_width;
928 /* calculate different string sizes and positions */
929 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
930 if (format_align->left != 0) {
931 display->getstringsize((unsigned char *)format_align->left,
932 &left_width, &string_height);
935 if (format_align->right != 0) {
936 display->getstringsize((unsigned char *)format_align->right,
937 &right_width, &string_height);
940 if (format_align->center != 0) {
941 display->getstringsize((unsigned char *)format_align->center,
942 &center_width, &string_height);
945 left_xpos = 0;
946 right_xpos = (display->getwidth() - right_width);
947 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
949 scroll_width = display->getwidth() - left_xpos;
951 /* Checks for overlapping strings.
952 If needed the overlapping strings will be merged, separated by a
953 space */
955 /* CASE 1: left and centered string overlap */
956 /* there is a left string, need to merge left and center */
957 if ((left_width != 0 && center_width != 0) &&
958 (left_xpos + left_width + space_width > center_xpos)) {
959 /* replace the former separator '\0' of left and
960 center string with a space */
961 *(--format_align->center) = ' ';
962 /* calculate the new width and position of the merged string */
963 left_width = left_width + space_width + center_width;
964 /* there is no centered string anymore */
965 center_width = 0;
967 /* there is no left string, move center to left */
968 if ((left_width == 0 && center_width != 0) &&
969 (left_xpos + left_width > center_xpos)) {
970 /* move the center string to the left string */
971 format_align->left = format_align->center;
972 /* calculate the new width and position of the string */
973 left_width = center_width;
974 /* there is no centered string anymore */
975 center_width = 0;
978 /* CASE 2: centered and right string overlap */
979 /* there is a right string, need to merge center and right */
980 if ((center_width != 0 && right_width != 0) &&
981 (center_xpos + center_width + space_width > right_xpos)) {
982 /* replace the former separator '\0' of center and
983 right string with a space */
984 *(--format_align->right) = ' ';
985 /* move the center string to the right after merge */
986 format_align->right = format_align->center;
987 /* calculate the new width and position of the merged string */
988 right_width = center_width + space_width + right_width;
989 right_xpos = (display->getwidth() - right_width);
990 /* there is no centered string anymore */
991 center_width = 0;
993 /* there is no right string, move center to right */
994 if ((center_width != 0 && right_width == 0) &&
995 (center_xpos + center_width > right_xpos)) {
996 /* move the center string to the right string */
997 format_align->right = format_align->center;
998 /* calculate the new width and position of the string */
999 right_width = center_width;
1000 right_xpos = (display->getwidth() - right_width);
1001 /* there is no centered string anymore */
1002 center_width = 0;
1005 /* CASE 3: left and right overlap
1006 There is no center string anymore, either there never
1007 was one or it has been merged in case 1 or 2 */
1008 /* there is a left string, need to merge left and right */
1009 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1010 (left_xpos + left_width + space_width > right_xpos)) {
1011 /* replace the former separator '\0' of left and
1012 right string with a space */
1013 *(--format_align->right) = ' ';
1014 /* calculate the new width and position of the string */
1015 left_width = left_width + space_width + right_width;
1016 /* there is no right string anymore */
1017 right_width = 0;
1019 /* there is no left string, move right to left */
1020 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1021 (left_width > right_xpos)) {
1022 /* move the right string to the left string */
1023 format_align->left = format_align->right;
1024 /* calculate the new width and position of the string */
1025 left_width = right_width;
1026 /* there is no right string anymore */
1027 right_width = 0;
1030 ypos = (line * string_height);
1033 if (scroll && ((left_width > scroll_width) ||
1034 (center_width > scroll_width) ||
1035 (right_width > scroll_width)))
1037 display->puts_scroll(0, line,
1038 (unsigned char *)format_align->left);
1040 else
1042 #ifdef HAVE_LCD_BITMAP
1043 /* clear the line first */
1044 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1045 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1046 display->set_drawmode(DRMODE_SOLID);
1047 #endif
1049 /* Nasty hack: we output an empty scrolling string,
1050 which will reset the scroller for that line */
1051 display->puts_scroll(0, line, (unsigned char *)"");
1053 /* print aligned strings */
1054 if (left_width != 0)
1056 display->putsxy(left_xpos, ypos,
1057 (unsigned char *)format_align->left);
1059 if (center_width != 0)
1061 display->putsxy(center_xpos, ypos,
1062 (unsigned char *)format_align->center);
1064 if (right_width != 0)
1066 display->putsxy(right_xpos, ypos,
1067 (unsigned char *)format_align->right);
1072 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode)
1074 struct wps_data *data = gwps->data;
1075 struct screen *display = gwps->display;
1077 if (!data || !display || !gwps->state)
1078 return false;
1080 unsigned flags;
1081 char linebuf[MAX_PATH];
1083 struct align_pos align;
1084 align.left = NULL;
1085 align.center = NULL;
1086 align.right = NULL;
1089 struct skin_token_list *viewport_list;
1091 bool update_line, new_subline_refresh;
1093 /* reset to first subline if refresh all flag is set */
1094 if (refresh_mode == WPS_REFRESH_ALL)
1096 struct skin_line *line;
1097 struct skin_viewport *skin_viewport = find_viewport(VP_DEFAULT_LABEL, data);
1099 if (!(skin_viewport->hidden_flags & VP_NEVER_VISIBLE))
1101 display->set_viewport(&skin_viewport->vp);
1102 display->clear_viewport();
1105 for (viewport_list = data->viewports;
1106 viewport_list; viewport_list = viewport_list->next)
1108 skin_viewport =
1109 (struct skin_viewport *)viewport_list->token->value.data;
1110 for(line = skin_viewport->lines; line; line = line->next)
1112 line->curr_subline = NULL;
1117 #ifdef HAVE_LCD_CHARCELLS
1118 int i;
1119 for (i = 0; i < 8; i++)
1121 if (data->wps_progress_pat[i] == 0)
1122 data->wps_progress_pat[i] = display->get_locked_pattern();
1124 #endif
1126 /* disable any viewports which are conditionally displayed.
1127 * If we are only refreshing the peak meter then don't change the viewport
1128 * enabled flags as this will stop scrolling. viewports cant be
1129 * toggled in this refresh mode anyway (FS#10215)*/
1130 if (refresh_mode != WPS_REFRESH_PEAK_METER)
1132 for (viewport_list = data->viewports;
1133 viewport_list; viewport_list = viewport_list->next)
1135 struct skin_viewport *skin_viewport =
1136 (struct skin_viewport *)viewport_list->token->value.data;
1137 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1139 continue;
1141 if (skin_viewport->hidden_flags&VP_DRAW_HIDEABLE)
1143 if (skin_viewport->hidden_flags&VP_DRAW_HIDDEN)
1144 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1145 else
1146 skin_viewport->hidden_flags |= VP_DRAW_HIDDEN;
1150 int viewport_count = 0;
1151 for (viewport_list = data->viewports;
1152 viewport_list; viewport_list = viewport_list->next, viewport_count++)
1154 struct skin_viewport *skin_viewport =
1155 (struct skin_viewport *)viewport_list->token->value.data;
1156 unsigned vp_refresh_mode = refresh_mode;
1158 display->set_viewport(&skin_viewport->vp);
1160 int hidden_vp = 0;
1162 #ifdef HAVE_LCD_BITMAP
1163 /* Set images to not to be displayed */
1164 struct skin_token_list *imglist = data->images;
1165 while (imglist)
1167 struct gui_img *img = (struct gui_img *)imglist->token->value.data;
1168 img->display = -1;
1169 imglist = imglist->next;
1171 #endif
1172 /* dont redraw the viewport if its disabled */
1173 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1174 { /* don't draw anything into this one */
1175 vp_refresh_mode = 0; hidden_vp = true;
1177 else if ((skin_viewport->hidden_flags&VP_DRAW_HIDDEN))
1179 if (!(skin_viewport->hidden_flags&VP_DRAW_WASHIDDEN))
1180 display->scroll_stop(&skin_viewport->vp);
1181 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1182 continue;
1184 else if (((skin_viewport->hidden_flags&
1185 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1186 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1188 vp_refresh_mode = WPS_REFRESH_ALL;
1189 skin_viewport->hidden_flags = VP_DRAW_HIDEABLE;
1192 if (vp_refresh_mode == WPS_REFRESH_ALL)
1194 display->clear_viewport();
1197 /* loop over the lines for this viewport */
1198 struct skin_line *line;
1199 int line_count = 0;
1201 for (line = skin_viewport->lines; line; line = line->next, line_count++)
1203 struct skin_subline *subline;
1204 memset(linebuf, 0, sizeof(linebuf));
1205 update_line = false;
1207 /* get current subline for the line */
1208 new_subline_refresh = update_curr_subline(gwps, line);
1209 subline = line->curr_subline;
1210 flags = line->curr_subline->line_type;
1212 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1213 || new_subline_refresh || hidden_vp)
1215 /* get_line tells us if we need to update the line */
1216 update_line = get_line(gwps, subline, &align,
1217 linebuf, sizeof(linebuf), vp_refresh_mode);
1219 #ifdef HAVE_LCD_BITMAP
1220 /* peakmeter */
1221 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1223 if (!data->peak_meter_enabled)
1225 peak_meter_enable(false);
1227 else
1229 /* the peakmeter should be alone on its line */
1230 update_line = false;
1232 int h = font_get(skin_viewport->vp.font)->height;
1233 int peak_meter_y = line_count* h;
1235 /* The user might decide to have the peak meter in the last
1236 line so that it is only displayed if no status bar is
1237 visible. If so we neither want do draw nor enable the
1238 peak meter. */
1239 if (peak_meter_y + h <= skin_viewport->vp.y+skin_viewport->vp.height) {
1240 peak_meter_enable(true);
1241 peak_meter_screen(gwps->display, 0, peak_meter_y,
1242 MIN(h, skin_viewport->vp.y+skin_viewport->vp.height - peak_meter_y));
1247 #else /* HAVE_LCD_CHARCELL */
1249 /* progressbar */
1250 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1252 if (data->full_line_progressbar)
1253 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1254 else
1255 draw_player_progress(gwps);
1257 #endif
1259 if (update_line && !hidden_vp &&
1260 /* conditionals clear the line which means if the %Vd is put into the default
1261 viewport there will be a blank line.
1262 To get around this we dont allow any actual drawing to happen in the
1263 deault vp if other vp's are defined */
1264 ((skin_viewport->label != VP_DEFAULT_LABEL && viewport_list->next) ||
1265 !viewport_list->next))
1267 if (flags & WPS_REFRESH_SCROLL)
1269 /* if the line is a scrolling one we don't want to update
1270 too often, so that it has the time to scroll */
1271 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1272 write_line(display, &align, line_count, true);
1274 else
1275 write_line(display, &align, line_count, false);
1278 #ifdef HAVE_LCD_BITMAP
1279 /* progressbar */
1280 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1282 struct skin_token_list *bar = gwps->data->progressbars;
1283 while (bar)
1285 struct progressbar *thisbar = (struct progressbar*)bar->token->value.data;
1286 if (thisbar->vp == &skin_viewport->vp && thisbar->draw)
1288 draw_progressbar(gwps, thisbar);
1290 bar = bar->next;
1293 /* Now display any images in this viewport */
1294 if (!hidden_vp)
1295 wps_display_images(gwps, &skin_viewport->vp);
1296 #endif
1299 /* Restore the default viewport */
1300 display->set_viewport(NULL);
1302 display->update();
1304 return true;