fix red and yellow (most of it anyway)
[kugel-rb.git] / apps / gui / skin_engine / skin_display.c
blob16b2dc1213e43708d0dd8b16272381edf8702b86
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 "action.h"
35 #include "abrepeat.h"
36 #include "lang.h"
37 #include "language.h"
38 #include "statusbar.h"
39 #include "settings.h"
40 #include "scrollbar.h"
41 #include "screen_access.h"
42 #include "playlist.h"
43 #include "audio.h"
44 #include "tagcache.h"
46 #ifdef HAVE_LCD_BITMAP
47 #include "peakmeter.h"
48 /* Image stuff */
49 #include "bmp.h"
50 #ifdef HAVE_ALBUMART
51 #include "albumart.h"
52 #endif
53 #endif
55 #include "cuesheet.h"
56 #if CONFIG_CODEC == SWCODEC
57 #include "playback.h"
58 #endif
59 #include "backdrop.h"
60 #include "viewport.h"
61 #include "radio.h"
62 #include "tuner.h"
63 #include "root_menu.h"
66 #include "wps_internals.h"
67 #include "skin_engine.h"
68 #include "statusbar-skinned.h"
70 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode);
72 /* update a skinned screen, update_type is WPS_REFRESH_* values.
73 * Usually it should only be WPS_REFRESH_NON_STATIC
74 * A full update will be done if required (state.do_full_update == true)
76 bool skin_update(struct gui_wps *gwps, unsigned int update_type)
78 bool retval;
79 /* This maybe shouldnt be here, but while the skin is only used to
80 * display the music screen this is better than whereever we are being
81 * called from. This is also safe for skined screen which dont use the id3 */
82 struct mp3entry *id3 = gwps->state->id3;
83 bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
84 gwps->sync_data->do_full_update |= cuesheet_update;
86 retval = skin_redraw(gwps, gwps->sync_data->do_full_update ?
87 WPS_REFRESH_ALL : update_type);
88 return retval;
91 #ifdef HAVE_LCD_BITMAP
93 void skin_statusbar_changed(struct gui_wps *skin)
95 if (!skin)
96 return;
97 struct wps_data *data = skin->data;
98 const struct screen *display = skin->display;
99 const int screen = display->screen_type;
101 struct viewport *vp = &find_viewport(VP_DEFAULT_LABEL, data)->vp;
102 viewport_set_defaults(vp, screen);
104 if (data->wps_sb_tag)
105 { /* fix up the default viewport */
106 if (data->show_sb_on_wps)
108 if (statusbar_position(screen) != STATUSBAR_OFF)
109 return; /* vp is fixed already */
111 vp->y = STATUSBAR_HEIGHT;
112 vp->height = display->lcdheight - STATUSBAR_HEIGHT;
114 else
116 if (statusbar_position(screen) == STATUSBAR_OFF)
117 return; /* vp is fixed already */
118 vp->y = vp->x = 0;
119 vp->height = display->lcdheight;
120 vp->width = display->lcdwidth;
125 static void draw_progressbar(struct gui_wps *gwps,
126 struct progressbar *pb)
128 struct screen *display = gwps->display;
129 struct viewport *vp = pb->vp;
130 struct wps_state *state = gwps->state;
131 struct mp3entry *id3 = state->id3;
132 int y = pb->y, height = pb->height;
133 unsigned long length, elapsed;
135 if (height < 0)
136 height = font_get(vp->font)->height;
138 if (y < 0)
140 int line_height = font_get(vp->font)->height;
141 /* center the pb in the line, but only if the line is higher than the pb */
142 int center = (line_height-height)/2;
143 /* if Y was not set calculate by font height,Y is -line_number-1 */
144 y = (-y -1)*line_height + (0 > center ? 0 : center);
147 if (pb->type == WPS_TOKEN_VOLUMEBAR)
149 int minvol = sound_min(SOUND_VOLUME);
150 int maxvol = sound_max(SOUND_VOLUME);
151 length = maxvol-minvol;
152 elapsed = global_settings.volume-minvol;
154 else if (pb->type == WPS_TOKEN_BATTERY_PERCENTBAR)
156 length = 100;
157 elapsed = battery_level();
159 else if (id3 && id3->length)
161 length = id3->length;
162 elapsed = id3->elapsed + state->ff_rewind_count;
164 #if CONFIG_TUNER
165 else if (in_radio_screen())
167 int min = fm_region_data[global_settings.fm_region].freq_min;
168 elapsed = radio_current_frequency() - min;
169 length = fm_region_data[global_settings.fm_region].freq_max - min;
171 #endif
172 else
174 length = 1;
175 elapsed = 0;
178 if (pb->have_bitmap_pb)
179 gui_bitmap_scrollbar_draw(display, pb->bm,
180 pb->x, y, pb->width, pb->bm.height,
181 length, 0, elapsed, HORIZONTAL);
182 else
183 gui_scrollbar_draw(display, pb->x, y, pb->width, height,
184 length, 0, elapsed, HORIZONTAL);
186 if (pb->type == WPS_TOKEN_PROGRESSBAR && id3 && id3->length)
188 #ifdef AB_REPEAT_ENABLE
189 if (ab_repeat_mode_enabled())
190 ab_draw_markers(display, id3->length,
191 pb->x, y, pb->width, height);
192 #endif
194 if (id3->cuesheet)
195 cue_draw_markers(display, id3->cuesheet, id3->length,
196 pb->x, y+1, pb->width, height-2);
200 static void draw_playlist_viewer_list(struct gui_wps *gwps,
201 struct playlistviewer *viewer)
203 struct wps_state *state = gwps->state;
204 int lines = viewport_get_nb_lines(viewer->vp);
205 int line_height = font_get(viewer->vp->font)->height;
206 int cur_pos, count;
207 int start_item;
208 int i;
209 struct wps_token token;
210 int x, length, alignment = WPS_TOKEN_ALIGN_LEFT;
212 struct mp3entry *pid3;
213 char buf[MAX_PATH*2], tempbuf[MAX_PATH];
214 const char *filename;
215 #if CONFIG_TUNER
216 if (current_screen() == GO_TO_FM)
218 cur_pos = radio_current_preset();
219 count = radio_preset_count();
221 else
222 #endif
224 cur_pos = playlist_get_display_index();
225 count = playlist_amount()+1;
227 start_item = MAX(0, cur_pos + viewer->start_offset);
229 gwps->display->set_viewport(viewer->vp);
230 for(i=start_item; (i-start_item)<lines && i<count; i++)
232 int line;
233 #if CONFIG_TUNER
234 if (current_screen() == GO_TO_FM)
236 pid3 = NULL;
237 line = TRACK_HAS_INFO;
238 filename = "";
240 else
241 #endif
243 filename = playlist_peek(i-cur_pos);
244 if (i == cur_pos)
246 pid3 = state->id3;
248 else if (i == cur_pos+1)
250 pid3 = state->nid3;
252 #if CONFIG_CODEC == SWCODEC
253 else if (i>cur_pos)
255 #ifdef HAVE_TC_RAMCACHE
256 if (tagcache_fill_tags(&viewer->tempid3, filename))
258 pid3 = &viewer->tempid3;
260 else
261 #endif
262 if (!audio_peek_track(&pid3, i-cur_pos))
263 pid3 = NULL;
265 #endif
266 else
268 pid3 = NULL;
270 line = pid3 ? TRACK_HAS_INFO : TRACK_HAS_NO_INFO;
272 int j = 0, cur_string = 0;
273 unsigned int line_len = 0;
274 buf[0] = '\0';
275 while (j < viewer->lines[line].count && line_len < sizeof(buf))
277 const char *out = NULL;
278 token.type = viewer->lines[line].tokens[j];
279 token.value.i = 0;
280 token.next = false;
281 out = get_id3_token(&token, pid3, tempbuf, sizeof(tempbuf), -1, NULL);
282 #if CONFIG_TUNER
283 if (!out)
284 out = get_radio_token(&token, i-cur_pos,
285 tempbuf, sizeof(tempbuf), -1, NULL);
286 #endif
287 if (out)
289 line_len = strlcat(buf, out, sizeof(buf));
290 j++;
291 continue;
294 switch (viewer->lines[line].tokens[j])
296 case WPS_TOKEN_ALIGN_CENTER:
297 case WPS_TOKEN_ALIGN_LEFT:
298 case WPS_TOKEN_ALIGN_LEFT_RTL:
299 case WPS_TOKEN_ALIGN_RIGHT:
300 case WPS_TOKEN_ALIGN_RIGHT_RTL:
301 alignment = viewer->lines[line].tokens[j];
302 tempbuf[0] = '\0';
303 break;
304 case WPS_TOKEN_STRING:
305 case WPS_TOKEN_CHARACTER:
306 snprintf(tempbuf, sizeof(tempbuf), "%s",
307 viewer->lines[line].strings[cur_string]);
308 cur_string++;
309 break;
310 case WPS_TOKEN_PLAYLIST_POSITION:
311 snprintf(tempbuf, sizeof(tempbuf), "%d", i);
312 break;
313 case WPS_TOKEN_FILE_NAME:
314 get_dir(tempbuf, sizeof(tempbuf), filename, 0);
315 break;
316 case WPS_TOKEN_FILE_PATH:
317 snprintf(tempbuf, sizeof(tempbuf), "%s", filename);
318 break;
319 default:
320 tempbuf[0] = '\0';
321 break;
323 if (tempbuf[0])
325 line_len = strlcat(buf, tempbuf, sizeof(buf));
327 j++;
330 int vpwidth = viewer->vp->width;
331 length = gwps->display->getstringsize(buf, NULL, NULL);
332 if (viewer->lines[line].scroll && length >= vpwidth)
334 gwps->display->puts_scroll(0, (i-start_item), buf );
336 else
338 if (length >= vpwidth)
339 x = 0;
340 else
342 switch (alignment)
344 case WPS_TOKEN_ALIGN_CENTER:
345 x = (vpwidth-length)/2;
346 break;
347 case WPS_TOKEN_ALIGN_LEFT_RTL:
348 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
350 x = vpwidth - length;
351 break;
353 case WPS_TOKEN_ALIGN_LEFT:
354 x = 0;
355 break;
356 case WPS_TOKEN_ALIGN_RIGHT_RTL:
357 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
359 x = 0;
360 break;
362 case WPS_TOKEN_ALIGN_RIGHT:
363 x = vpwidth - length;
364 break;
365 default:
366 x = 0;
367 break;
370 gwps->display->putsxy(x, (i-start_item)*line_height, buf );
376 /* clears the area where the image was shown */
377 static void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
379 if(!gwps)
380 return;
381 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
382 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
383 gwps->display->set_drawmode(DRMODE_SOLID);
386 static void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
388 struct screen *display = gwps->display;
389 if(img->always_display)
390 display->set_drawmode(DRMODE_FG);
391 else
392 display->set_drawmode(DRMODE_SOLID);
394 #if LCD_DEPTH > 1
395 if(img->bm.format == FORMAT_MONO) {
396 #endif
397 display->mono_bitmap_part(img->bm.data,
398 0, img->subimage_height * subimage,
399 img->bm.width, img->x,
400 img->y, img->bm.width,
401 img->subimage_height);
402 #if LCD_DEPTH > 1
403 } else {
404 display->transparent_bitmap_part((fb_data *)img->bm.data,
405 0, img->subimage_height * subimage,
406 STRIDE(display->screen_type,
407 img->bm.width, img->bm.height),
408 img->x, img->y, img->bm.width,
409 img->subimage_height);
411 #endif
414 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
416 if(!gwps || !gwps->data || !gwps->display)
417 return;
419 struct wps_data *data = gwps->data;
420 struct screen *display = gwps->display;
421 struct skin_token_list *list = data->images;
423 while (list)
425 struct gui_img *img = (struct gui_img*)list->token->value.data;
426 if (img->loaded)
428 if (img->display >= 0)
430 wps_draw_image(gwps, img, img->display);
432 else if (img->always_display && img->vp == vp)
434 wps_draw_image(gwps, img, 0);
437 list = list->next;
439 #ifdef HAVE_ALBUMART
440 /* now draw the AA */
441 if (data->albumart && data->albumart->vp == vp
442 && data->albumart->draw)
444 draw_album_art(gwps, playback_current_aa_hid(data->playback_aa_slot),
445 false);
446 data->albumart->draw = false;
448 #endif
450 display->set_drawmode(DRMODE_SOLID);
453 #else /* HAVE_LCD_CHARCELL */
455 static bool draw_player_progress(struct gui_wps *gwps)
457 struct wps_state *state = gwps->state;
458 struct screen *display = gwps->display;
459 unsigned char progress_pattern[7];
460 int pos = 0;
461 int i;
463 int elapsed, length;
464 if (LIKELY(state->id3))
466 elapsed = state->id3->elapsed;
467 length = state->id3->length;
469 else
471 elapsed = 0;
472 length = 0;
475 if (length)
476 pos = 36 * (elapsed + state->ff_rewind_count) / length;
478 for (i = 0; i < 7; i++, pos -= 5)
480 if (pos <= 0)
481 progress_pattern[i] = 0x1fu;
482 else if (pos >= 5)
483 progress_pattern[i] = 0x00u;
484 else
485 progress_pattern[i] = 0x1fu >> pos;
488 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
489 return true;
492 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
494 static const unsigned char numbers[10][4] = {
495 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
496 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
497 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
498 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
499 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
500 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
501 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
502 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
503 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
504 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
507 struct wps_state *state = gwps->state;
508 struct screen *display = gwps->display;
509 struct wps_data *data = gwps->data;
510 unsigned char progress_pattern[7];
511 char timestr[10];
512 int time;
513 int time_idx = 0;
514 int pos = 0;
515 int pat_idx = 1;
516 int digit, i, j;
517 bool softchar;
519 int elapsed, length;
520 if (LIKELY(state->id3))
522 elapsed = state->id3->elapsed;
523 length = state->id3->length;
525 else
527 elapsed = 0;
528 length = 0;
531 if (buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
532 return;
534 time = elapsed + state->ff_rewind_count;
535 if (length)
536 pos = 55 * time / length;
538 memset(timestr, 0, sizeof(timestr));
539 format_time(timestr, sizeof(timestr)-2, time);
540 timestr[strlen(timestr)] = ':'; /* always safe */
542 for (i = 0; i < 11; i++, pos -= 5)
544 softchar = false;
545 memset(progress_pattern, 0, sizeof(progress_pattern));
547 if ((digit = timestr[time_idx]))
549 softchar = true;
550 digit -= '0';
552 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
554 memcpy(progress_pattern, numbers[digit], 4);
555 time_idx += 2;
557 else /* tens, shifted right */
559 for (j = 0; j < 4; j++)
560 progress_pattern[j] = numbers[digit][j] >> 1;
562 if (time_idx > 0) /* not the first group, add colon in front */
564 progress_pattern[1] |= 0x10u;
565 progress_pattern[3] |= 0x10u;
567 time_idx++;
570 if (pos >= 5)
571 progress_pattern[5] = progress_pattern[6] = 0x1fu;
574 if (pos > 0 && pos < 5)
576 softchar = true;
577 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
580 if (softchar && pat_idx < 8)
582 display->define_pattern(data->wps_progress_pat[pat_idx],
583 progress_pattern);
584 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
585 pat_idx++;
587 else if (pos <= 0)
588 buf = utf8encode(' ', buf);
589 else
590 buf = utf8encode(0xe115, buf); /* 2/7 _ */
592 *buf = '\0';
595 #endif /* HAVE_LCD_CHARCELL */
597 /* Return the index to the end token for the conditional token at index.
598 The conditional token can be either a start token or a separator
599 (i.e. option) token.
601 static int find_conditional_end(struct wps_data *data, int index)
603 int ret = index;
604 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
605 ret = data->tokens[ret].value.i;
607 /* ret now is the index to the end token for the conditional. */
608 return ret;
611 /* Evaluate the conditional that is at *token_index and return whether a skip
612 has ocurred. *token_index is updated with the new position.
614 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
616 if (!gwps)
617 return false;
619 struct wps_data *data = gwps->data;
621 int i, cond_end;
622 int cond_index = *token_index;
623 char result[128];
624 const char *value;
625 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
626 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
628 /* treat ?xx<true> constructs as if they had 2 options. */
629 if (num_options < 2)
630 num_options = 2;
632 int intval = num_options;
633 /* get_token_value needs to know the number of options in the enum */
634 value = get_token_value(gwps, &data->tokens[cond_index + 1],
635 result, sizeof(result), &intval);
637 /* intval is now the number of the enum option we want to read,
638 starting from 1. If intval is -1, we check if value is empty. */
639 if (intval == -1)
640 intval = (value && *value) ? 1 : num_options;
641 else if (intval > num_options || intval < 1)
642 intval = num_options;
644 data->tokens[cond_index].value.i = (intval << 8) + num_options;
646 /* skip to the appropriate enum case */
647 int next = cond_index + 2;
648 for (i = 1; i < intval; i++)
650 next = data->tokens[next].value.i;
652 *token_index = next;
654 if (prev_val == intval)
656 /* Same conditional case as previously. Return without clearing the
657 pictures */
658 return false;
661 cond_end = find_conditional_end(data, cond_index + 2);
662 for (i = cond_index + 3; i < cond_end; i++)
664 #ifdef HAVE_LCD_BITMAP
665 /* clear all pictures in the conditional and nested ones */
666 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
667 clear_image_pos(gwps, find_image(data->tokens[i].value.i&0xFF, data));
668 else if (data->tokens[i].type == WPS_TOKEN_VOLUMEBAR ||
669 data->tokens[i].type == WPS_TOKEN_PROGRESSBAR ||
670 data->tokens[i].type == WPS_TOKEN_BATTERY_PERCENTBAR )
672 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
673 bar->draw = false;
675 else if (data->tokens[i].type == WPS_TOKEN_PEAKMETER)
677 data->peak_meter_enabled = false;
679 #endif
680 #ifdef HAVE_ALBUMART
681 if (data->albumart && data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
683 draw_album_art(gwps,
684 playback_current_aa_hid(data->playback_aa_slot), true);
685 data->albumart->draw = false;
687 #endif
690 return true;
694 /* Read a (sub)line to the given alignment format buffer.
695 linebuf is the buffer where the data is actually stored.
696 align is the alignment format that'll be used to display the text.
697 The return value indicates whether the line needs to be updated.
699 static bool get_line(struct gui_wps *gwps,
700 struct skin_subline *subline,
701 struct align_pos *align,
702 char *linebuf,
703 int linebuf_size,
704 unsigned refresh_mode)
706 struct wps_data *data = gwps->data;
708 char temp_buf[128];
709 char *buf = linebuf; /* will always point to the writing position */
710 char *linebuf_end = linebuf + linebuf_size - 1;
711 bool update = false;
712 int i;
713 (void)refresh_mode; /* silence warning on charcell */
715 /* alignment-related variables */
716 int cur_align;
717 char* cur_align_start;
718 cur_align_start = buf;
719 cur_align = WPS_ALIGN_LEFT;
720 align->left = NULL;
721 align->center = NULL;
722 align->right = NULL;
723 /* Process all tokens of the desired subline */
724 for (i = subline->first_token_idx;
725 i <= subline->last_token_idx; i++)
727 switch(data->tokens[i].type)
729 case WPS_TOKEN_CONDITIONAL:
730 /* place ourselves in the right conditional case */
731 update |= evaluate_conditional(gwps, &i);
732 break;
734 case WPS_TOKEN_CONDITIONAL_OPTION:
735 /* we've finished in the curent conditional case,
736 skip to the end of the conditional structure */
737 i = find_conditional_end(data, i);
738 break;
740 #ifdef HAVE_LCD_BITMAP
741 case WPS_TOKEN_PEAKMETER:
742 data->peak_meter_enabled = true;
743 break;
744 case WPS_TOKEN_VOLUMEBAR:
745 case WPS_TOKEN_BATTERY_PERCENTBAR:
746 case WPS_TOKEN_PROGRESSBAR:
748 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
749 bar->draw = true;
751 break;
752 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
754 char n = data->tokens[i].value.i & 0xFF;
755 int subimage = data->tokens[i].value.i >> 8;
756 struct gui_img *img = find_image(n, data);
758 if (img && img->loaded)
759 img->display = subimage;
760 break;
762 case WPS_TOKEN_DRAW_INBUILTBAR:
763 gui_statusbar_draw(&(statusbars.statusbars[gwps->display->screen_type]),
764 refresh_mode == WPS_REFRESH_ALL,
765 data->tokens[i].value.data);
766 break;
767 #endif
769 case WPS_TOKEN_ALIGN_LEFT:
770 case WPS_TOKEN_ALIGN_LEFT_RTL:
771 case WPS_TOKEN_ALIGN_CENTER:
772 case WPS_TOKEN_ALIGN_RIGHT:
773 case WPS_TOKEN_ALIGN_RIGHT_RTL:
774 /* remember where the current aligned text started */
775 switch (cur_align)
777 case WPS_ALIGN_LEFT:
778 align->left = cur_align_start;
779 break;
781 case WPS_ALIGN_CENTER:
782 align->center = cur_align_start;
783 break;
785 case WPS_ALIGN_RIGHT:
786 align->right = cur_align_start;
787 break;
789 /* start a new alignment */
790 switch (data->tokens[i].type)
792 case WPS_TOKEN_ALIGN_LEFT:
793 cur_align = WPS_ALIGN_LEFT;
794 break;
795 case WPS_TOKEN_ALIGN_LEFT_RTL:
796 cur_align = lang_is_rtl() ? WPS_ALIGN_RIGHT :
797 WPS_ALIGN_LEFT;
798 break;
799 case WPS_TOKEN_ALIGN_CENTER:
800 cur_align = WPS_ALIGN_CENTER;
801 break;
802 case WPS_TOKEN_ALIGN_RIGHT:
803 cur_align = WPS_ALIGN_RIGHT;
804 break;
805 case WPS_TOKEN_ALIGN_RIGHT_RTL:
806 cur_align = lang_is_rtl() ? WPS_ALIGN_LEFT :
807 WPS_ALIGN_RIGHT;
808 break;
809 default:
810 break;
812 *buf++ = 0;
813 cur_align_start = buf;
814 break;
815 case WPS_VIEWPORT_ENABLE:
817 char label = data->tokens[i].value.i;
818 char temp = VP_DRAW_HIDEABLE;
819 /* viewports are allowed to share id's so find and enable
820 * all of them */
821 struct skin_token_list *list = data->viewports;
822 while (list)
824 struct skin_viewport *vp =
825 (struct skin_viewport *)list->token->value.data;
826 if (vp->label == label)
828 if (vp->hidden_flags&VP_DRAW_WASHIDDEN)
829 temp |= VP_DRAW_WASHIDDEN;
830 vp->hidden_flags = temp;
832 list = list->next;
835 break;
836 #ifdef HAVE_LCD_BITMAP
837 case WPS_TOKEN_UIVIEWPORT_ENABLE:
838 sb_set_info_vp(gwps->display->screen_type,
839 data->tokens[i].value.i|VP_INFO_LABEL);
840 break;
841 case WPS_VIEWPORT_CUSTOMLIST:
842 draw_playlist_viewer_list(gwps, data->tokens[i].value.data);
843 break;
844 #endif
845 default:
847 /* get the value of the tag and copy it to the buffer */
848 const char *value = get_token_value(gwps, &data->tokens[i],
849 temp_buf, sizeof(temp_buf), NULL);
850 if (value)
852 update = true;
853 while (*value && (buf < linebuf_end))
854 *buf++ = *value++;
856 break;
861 /* close the current alignment */
862 switch (cur_align)
864 case WPS_ALIGN_LEFT:
865 align->left = cur_align_start;
866 break;
868 case WPS_ALIGN_CENTER:
869 align->center = cur_align_start;
870 break;
872 case WPS_ALIGN_RIGHT:
873 align->right = cur_align_start;
874 break;
877 return update;
879 static void get_subline_timeout(struct gui_wps *gwps, struct skin_subline *subline)
881 struct wps_data *data = gwps->data;
882 int i;
883 subline->time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
885 for (i = subline->first_token_idx;
886 i <= subline->last_token_idx; i++)
888 switch(data->tokens[i].type)
890 case WPS_TOKEN_CONDITIONAL:
891 /* place ourselves in the right conditional case */
892 evaluate_conditional(gwps, &i);
893 break;
895 case WPS_TOKEN_CONDITIONAL_OPTION:
896 /* we've finished in the curent conditional case,
897 skip to the end of the conditional structure */
898 i = find_conditional_end(data, i);
899 break;
901 case WPS_TOKEN_SUBLINE_TIMEOUT:
902 subline->time_mult = data->tokens[i].value.i;
903 break;
905 default:
906 break;
911 /* Calculates which subline should be displayed for the specified line
912 Returns true iff the subline must be refreshed */
913 static bool update_curr_subline(struct gui_wps *gwps, struct skin_line *line)
915 /* shortcut this whole thing if we need to reset the line completly */
916 if (line->curr_subline == NULL)
918 line->subline_expire_time = current_tick;
919 line->curr_subline = &line->sublines;
920 if (!line->curr_subline->next)
922 line->subline_expire_time += 100*HZ;
924 else
926 get_subline_timeout(gwps, line->curr_subline);
927 line->subline_expire_time += TIMEOUT_UNIT*line->curr_subline->time_mult;
929 return true;
931 /* if time to advance to next sub-line */
932 if (TIME_AFTER(current_tick, line->subline_expire_time - 1))
934 /* if there is only one subline, there is no need to search for a new one */
935 if (&line->sublines == line->curr_subline &&
936 line->curr_subline->next == NULL)
938 line->subline_expire_time += 100 * HZ;
939 return false;
941 if (line->curr_subline->next)
942 line->curr_subline = line->curr_subline->next;
943 else
944 line->curr_subline = &line->sublines;
945 get_subline_timeout(gwps, line->curr_subline);
946 line->subline_expire_time = current_tick + TIMEOUT_UNIT*line->curr_subline->time_mult;
947 return true;
949 return false;
952 /* Display a line appropriately according to its alignment format.
953 format_align contains the text, separated between left, center and right.
954 line is the index of the line on the screen.
955 scroll indicates whether the line is a scrolling one or not.
957 static void write_line(struct screen *display,
958 struct align_pos *format_align,
959 int line,
960 bool scroll)
962 int left_width = 0, left_xpos;
963 int center_width = 0, center_xpos;
964 int right_width = 0, right_xpos;
965 int ypos;
966 int space_width;
967 int string_height;
968 int scroll_width;
970 /* calculate different string sizes and positions */
971 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
972 if (format_align->left != 0) {
973 display->getstringsize((unsigned char *)format_align->left,
974 &left_width, &string_height);
977 if (format_align->right != 0) {
978 display->getstringsize((unsigned char *)format_align->right,
979 &right_width, &string_height);
982 if (format_align->center != 0) {
983 display->getstringsize((unsigned char *)format_align->center,
984 &center_width, &string_height);
987 left_xpos = 0;
988 right_xpos = (display->getwidth() - right_width);
989 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
991 scroll_width = display->getwidth() - left_xpos;
993 /* Checks for overlapping strings.
994 If needed the overlapping strings will be merged, separated by a
995 space */
997 /* CASE 1: left and centered string overlap */
998 /* there is a left string, need to merge left and center */
999 if ((left_width != 0 && center_width != 0) &&
1000 (left_xpos + left_width + space_width > center_xpos)) {
1001 /* replace the former separator '\0' of left and
1002 center string with a space */
1003 *(--format_align->center) = ' ';
1004 /* calculate the new width and position of the merged string */
1005 left_width = left_width + space_width + center_width;
1006 /* there is no centered string anymore */
1007 center_width = 0;
1009 /* there is no left string, move center to left */
1010 if ((left_width == 0 && center_width != 0) &&
1011 (left_xpos + left_width > center_xpos)) {
1012 /* move the center string to the left string */
1013 format_align->left = format_align->center;
1014 /* calculate the new width and position of the string */
1015 left_width = center_width;
1016 /* there is no centered string anymore */
1017 center_width = 0;
1020 /* CASE 2: centered and right string overlap */
1021 /* there is a right string, need to merge center and right */
1022 if ((center_width != 0 && right_width != 0) &&
1023 (center_xpos + center_width + space_width > right_xpos)) {
1024 /* replace the former separator '\0' of center and
1025 right string with a space */
1026 *(--format_align->right) = ' ';
1027 /* move the center string to the right after merge */
1028 format_align->right = format_align->center;
1029 /* calculate the new width and position of the merged string */
1030 right_width = center_width + space_width + right_width;
1031 right_xpos = (display->getwidth() - right_width);
1032 /* there is no centered string anymore */
1033 center_width = 0;
1035 /* there is no right string, move center to right */
1036 if ((center_width != 0 && right_width == 0) &&
1037 (center_xpos + center_width > right_xpos)) {
1038 /* move the center string to the right string */
1039 format_align->right = format_align->center;
1040 /* calculate the new width and position of the string */
1041 right_width = center_width;
1042 right_xpos = (display->getwidth() - right_width);
1043 /* there is no centered string anymore */
1044 center_width = 0;
1047 /* CASE 3: left and right overlap
1048 There is no center string anymore, either there never
1049 was one or it has been merged in case 1 or 2 */
1050 /* there is a left string, need to merge left and right */
1051 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1052 (left_xpos + left_width + space_width > right_xpos)) {
1053 /* replace the former separator '\0' of left and
1054 right string with a space */
1055 *(--format_align->right) = ' ';
1056 /* calculate the new width and position of the string */
1057 left_width = left_width + space_width + right_width;
1058 /* there is no right string anymore */
1059 right_width = 0;
1061 /* there is no left string, move right to left */
1062 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1063 (left_width > right_xpos)) {
1064 /* move the right string to the left string */
1065 format_align->left = format_align->right;
1066 /* calculate the new width and position of the string */
1067 left_width = right_width;
1068 /* there is no right string anymore */
1069 right_width = 0;
1072 ypos = (line * string_height);
1075 if (scroll && ((left_width > scroll_width) ||
1076 (center_width > scroll_width) ||
1077 (right_width > scroll_width)))
1079 display->puts_scroll(0, line,
1080 (unsigned char *)format_align->left);
1082 else
1084 #ifdef HAVE_LCD_BITMAP
1085 /* clear the line first */
1086 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1087 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1088 display->set_drawmode(DRMODE_SOLID);
1089 #endif
1091 /* Nasty hack: we output an empty scrolling string,
1092 which will reset the scroller for that line */
1093 display->puts_scroll(0, line, (unsigned char *)"");
1095 /* print aligned strings */
1096 if (left_width != 0)
1098 display->putsxy(left_xpos, ypos,
1099 (unsigned char *)format_align->left);
1101 if (center_width != 0)
1103 display->putsxy(center_xpos, ypos,
1104 (unsigned char *)format_align->center);
1106 if (right_width != 0)
1108 display->putsxy(right_xpos, ypos,
1109 (unsigned char *)format_align->right);
1114 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode)
1116 struct wps_data *data = gwps->data;
1117 struct screen *display = gwps->display;
1119 if (!data || !display || !gwps->state)
1120 return false;
1122 unsigned flags;
1123 char linebuf[MAX_PATH];
1125 struct align_pos align;
1126 align.left = NULL;
1127 align.center = NULL;
1128 align.right = NULL;
1131 struct skin_token_list *viewport_list;
1133 bool update_line, new_subline_refresh;
1135 /* reset to first subline if refresh all flag is set */
1136 if (refresh_mode == WPS_REFRESH_ALL)
1138 struct skin_line *line;
1139 struct skin_viewport *skin_viewport = find_viewport(VP_DEFAULT_LABEL, data);
1141 if (!(skin_viewport->hidden_flags & VP_NEVER_VISIBLE))
1143 display->set_viewport(&skin_viewport->vp);
1144 display->clear_viewport();
1147 for (viewport_list = data->viewports;
1148 viewport_list; viewport_list = viewport_list->next)
1150 skin_viewport =
1151 (struct skin_viewport *)viewport_list->token->value.data;
1152 for(line = skin_viewport->lines; line; line = line->next)
1154 line->curr_subline = NULL;
1159 #ifdef HAVE_LCD_CHARCELLS
1160 int i;
1161 for (i = 0; i < 8; i++)
1163 if (data->wps_progress_pat[i] == 0)
1164 data->wps_progress_pat[i] = display->get_locked_pattern();
1166 #endif
1168 /* disable any viewports which are conditionally displayed.
1169 * If we are only refreshing the peak meter then don't change the viewport
1170 * enabled flags as this will stop scrolling. viewports cant be
1171 * toggled in this refresh mode anyway (FS#10215)*/
1172 if (refresh_mode != WPS_REFRESH_PEAK_METER)
1174 for (viewport_list = data->viewports;
1175 viewport_list; viewport_list = viewport_list->next)
1177 struct skin_viewport *skin_viewport =
1178 (struct skin_viewport *)viewport_list->token->value.data;
1179 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1181 continue;
1183 if (skin_viewport->hidden_flags&VP_DRAW_HIDEABLE)
1185 if (skin_viewport->hidden_flags&VP_DRAW_HIDDEN)
1186 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1187 else
1188 skin_viewport->hidden_flags |= VP_DRAW_HIDDEN;
1192 int viewport_count = 0;
1193 for (viewport_list = data->viewports;
1194 viewport_list; viewport_list = viewport_list->next, viewport_count++)
1196 struct skin_viewport *skin_viewport =
1197 (struct skin_viewport *)viewport_list->token->value.data;
1198 unsigned vp_refresh_mode = refresh_mode;
1200 display->set_viewport(&skin_viewport->vp);
1202 int hidden_vp = 0;
1204 #ifdef HAVE_LCD_BITMAP
1205 /* Set images to not to be displayed */
1206 struct skin_token_list *imglist = data->images;
1207 while (imglist)
1209 struct gui_img *img = (struct gui_img *)imglist->token->value.data;
1210 img->display = -1;
1211 imglist = imglist->next;
1213 #endif
1214 /* dont redraw the viewport if its disabled */
1215 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1216 { /* don't draw anything into this one */
1217 vp_refresh_mode = 0; hidden_vp = true;
1219 else if ((skin_viewport->hidden_flags&VP_DRAW_HIDDEN))
1221 if (!(skin_viewport->hidden_flags&VP_DRAW_WASHIDDEN))
1222 display->scroll_stop(&skin_viewport->vp);
1223 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1224 continue;
1226 else if (((skin_viewport->hidden_flags&
1227 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1228 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1230 vp_refresh_mode = WPS_REFRESH_ALL;
1231 skin_viewport->hidden_flags = VP_DRAW_HIDEABLE;
1234 if (vp_refresh_mode == WPS_REFRESH_ALL)
1236 display->clear_viewport();
1239 /* loop over the lines for this viewport */
1240 struct skin_line *line;
1241 int line_count = 0;
1243 for (line = skin_viewport->lines; line; line = line->next, line_count++)
1245 struct skin_subline *subline;
1246 memset(linebuf, 0, sizeof(linebuf));
1247 update_line = false;
1249 /* get current subline for the line */
1250 new_subline_refresh = update_curr_subline(gwps, line);
1251 subline = line->curr_subline;
1252 flags = line->curr_subline->line_type;
1254 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1255 || new_subline_refresh || hidden_vp)
1257 /* get_line tells us if we need to update the line */
1258 update_line = get_line(gwps, subline, &align,
1259 linebuf, sizeof(linebuf), vp_refresh_mode);
1261 #ifdef HAVE_LCD_BITMAP
1262 /* peakmeter */
1263 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1265 if (!data->peak_meter_enabled)
1267 peak_meter_enable(false);
1269 else
1271 /* the peakmeter should be alone on its line */
1272 update_line = false;
1274 int h = font_get(skin_viewport->vp.font)->height;
1275 int peak_meter_y = line_count* h;
1277 /* The user might decide to have the peak meter in the last
1278 line so that it is only displayed if no status bar is
1279 visible. If so we neither want do draw nor enable the
1280 peak meter. */
1281 if (peak_meter_y + h <= skin_viewport->vp.y+skin_viewport->vp.height) {
1282 peak_meter_enable(true);
1283 peak_meter_screen(gwps->display, 0, peak_meter_y,
1284 MIN(h, skin_viewport->vp.y+skin_viewport->vp.height - peak_meter_y));
1289 #else /* HAVE_LCD_CHARCELL */
1291 /* progressbar */
1292 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1294 if (data->full_line_progressbar)
1295 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1296 else
1297 draw_player_progress(gwps);
1299 #endif
1301 if (update_line && !hidden_vp &&
1302 /* conditionals clear the line which means if the %Vd is put into the default
1303 viewport there will be a blank line.
1304 To get around this we dont allow any actual drawing to happen in the
1305 deault vp if other vp's are defined */
1306 ((skin_viewport->label != VP_DEFAULT_LABEL && viewport_list->next) ||
1307 !viewport_list->next))
1309 if (flags & WPS_REFRESH_SCROLL)
1311 /* if the line is a scrolling one we don't want to update
1312 too often, so that it has the time to scroll */
1313 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1314 write_line(display, &align, line_count, true);
1316 else
1317 write_line(display, &align, line_count, false);
1320 #ifdef HAVE_LCD_BITMAP
1321 /* progressbar */
1322 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1324 struct skin_token_list *bar = gwps->data->progressbars;
1325 while (bar)
1327 struct progressbar *thisbar = (struct progressbar*)bar->token->value.data;
1328 if (thisbar->vp == &skin_viewport->vp && thisbar->draw)
1330 draw_progressbar(gwps, thisbar);
1332 bar = bar->next;
1335 /* Now display any images in this viewport */
1336 if (!hidden_vp)
1337 wps_display_images(gwps, &skin_viewport->vp);
1338 #endif
1341 /* Restore the default viewport */
1342 display->set_viewport(NULL);
1344 display->update();
1346 return true;
1349 bool skin_has_sbs(enum screen_type screen, struct wps_data *data)
1351 (void)screen;
1352 bool draw = false;
1353 #ifdef HAVE_LCD_BITMAP
1354 if (data->wps_sb_tag)
1355 draw = data->show_sb_on_wps;
1356 else if (statusbar_position(screen) != STATUSBAR_OFF)
1357 draw = true;
1358 #endif
1359 return draw;
1362 /* do the button loop as often as required for the peak meters to update
1363 * with a good refresh rate.
1364 * gwps is really gwps[NB_SCREENS]! don't wrap this if FOR_NB_SCREENS()
1366 int skin_wait_for_action(struct gui_wps *gwps, int context, int timeout)
1368 #ifdef HAVE_LCD_BITMAP
1369 int i;
1370 int button = ACTION_NONE;
1371 /* when the peak meter is enabled we want to have a
1372 few extra updates to make it look smooth. On the
1373 other hand we don't want to waste energy if it
1374 isn't displayed */
1375 bool pm=false;
1376 FOR_NB_SCREENS(i)
1378 if(gwps[i].data->peak_meter_enabled)
1379 pm = true;
1382 if (pm) {
1383 long next_refresh = current_tick;
1384 long next_big_refresh = current_tick + timeout;
1385 button = BUTTON_NONE;
1386 while (TIME_BEFORE(current_tick, next_big_refresh)) {
1387 button = get_action(context,TIMEOUT_NOBLOCK);
1388 if (button != ACTION_NONE) {
1389 break;
1391 peak_meter_peek();
1392 sleep(0); /* Sleep until end of current tick. */
1394 if (TIME_AFTER(current_tick, next_refresh)) {
1395 FOR_NB_SCREENS(i)
1397 if(gwps[i].data->peak_meter_enabled)
1398 skin_update(&gwps[i], WPS_REFRESH_PEAK_METER);
1399 next_refresh += HZ / PEAK_METER_FPS;
1406 /* The peak meter is disabled
1407 -> no additional screen updates needed */
1408 else
1409 #endif
1411 button = get_action(context, timeout);
1413 return button;