hopefully fix the problem with lines in viewports being pushed down one line.
[kugel-rb.git] / apps / gui / skin_engine / skin_display.c
blob3d3a654c30497b0f127be4dfc1314dd034ac627b
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 #if CONFIG_TUNER
62 #include "radio.h"
63 #include "tuner.h"
64 #endif
65 #include "root_menu.h"
68 #include "wps_internals.h"
69 #include "skin_engine.h"
70 #include "statusbar-skinned.h"
72 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode);
74 /* update a skinned screen, update_type is WPS_REFRESH_* values.
75 * Usually it should only be WPS_REFRESH_NON_STATIC
76 * A full update will be done if required (state.do_full_update == true)
78 bool skin_update(struct gui_wps *gwps, unsigned int update_type)
80 bool retval;
81 /* This maybe shouldnt be here, but while the skin is only used to
82 * display the music screen this is better than whereever we are being
83 * called from. This is also safe for skined screen which dont use the id3 */
84 struct mp3entry *id3 = gwps->state->id3;
85 bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
86 gwps->sync_data->do_full_update |= cuesheet_update;
88 retval = skin_redraw(gwps, gwps->sync_data->do_full_update ?
89 WPS_REFRESH_ALL : update_type);
90 return retval;
93 #ifdef HAVE_LCD_BITMAP
95 void skin_statusbar_changed(struct gui_wps *skin)
97 if (!skin)
98 return;
99 struct wps_data *data = skin->data;
100 const struct screen *display = skin->display;
101 const int screen = display->screen_type;
103 struct viewport *vp = &find_viewport(VP_DEFAULT_LABEL, data)->vp;
104 viewport_set_defaults(vp, screen);
106 if (data->wps_sb_tag)
107 { /* fix up the default viewport */
108 if (data->show_sb_on_wps)
110 if (statusbar_position(screen) != STATUSBAR_OFF)
111 return; /* vp is fixed already */
113 vp->y = STATUSBAR_HEIGHT;
114 vp->height = display->lcdheight - STATUSBAR_HEIGHT;
116 else
118 if (statusbar_position(screen) == STATUSBAR_OFF)
119 return; /* vp is fixed already */
120 vp->y = vp->x = 0;
121 vp->height = display->lcdheight;
122 vp->width = display->lcdwidth;
127 static void draw_progressbar(struct gui_wps *gwps,
128 struct progressbar *pb)
130 struct screen *display = gwps->display;
131 struct viewport *vp = pb->vp;
132 struct wps_state *state = gwps->state;
133 struct mp3entry *id3 = state->id3;
134 int y = pb->y, height = pb->height;
135 unsigned long length, elapsed;
137 if (height < 0)
138 height = font_get(vp->font)->height;
140 if (y < 0)
142 int line_height = font_get(vp->font)->height;
143 /* center the pb in the line, but only if the line is higher than the pb */
144 int center = (line_height-height)/2;
145 /* if Y was not set calculate by font height,Y is -line_number-1 */
146 y = (-y -1)*line_height + (0 > center ? 0 : center);
149 if (pb->type == WPS_TOKEN_VOLUMEBAR)
151 int minvol = sound_min(SOUND_VOLUME);
152 int maxvol = sound_max(SOUND_VOLUME);
153 length = maxvol-minvol;
154 elapsed = global_settings.volume-minvol;
156 else if (pb->type == WPS_TOKEN_BATTERY_PERCENTBAR)
158 length = 100;
159 elapsed = battery_level();
161 #if CONFIG_TUNER
162 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
164 int min = fm_region_data[global_settings.fm_region].freq_min;
165 elapsed = radio_current_frequency() - min;
166 length = fm_region_data[global_settings.fm_region].freq_max - min;
168 #endif
169 else if (id3 && id3->length)
171 length = id3->length;
172 elapsed = id3->elapsed + state->ff_rewind_count;
174 else
176 length = 1;
177 elapsed = 0;
180 if (pb->have_bitmap_pb)
181 gui_bitmap_scrollbar_draw(display, &pb->bm,
182 pb->x, y, pb->width, pb->bm.height,
183 length, 0, elapsed, HORIZONTAL);
184 else
185 gui_scrollbar_draw(display, pb->x, y, pb->width, height,
186 length, 0, elapsed, HORIZONTAL);
188 if (pb->type == WPS_TOKEN_PROGRESSBAR)
190 if (id3 && id3->length)
192 #ifdef AB_REPEAT_ENABLE
193 if (ab_repeat_mode_enabled())
194 ab_draw_markers(display, id3->length,
195 pb->x, y, pb->width, height);
196 #endif
198 if (id3->cuesheet)
199 cue_draw_markers(display, id3->cuesheet, id3->length,
200 pb->x, y+1, pb->width, height-2);
202 #if 0 /* disable for now CONFIG_TUNER */
203 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
205 presets_draw_markers(display, pb->x, y, pb->width, height);
207 #endif
211 static void draw_playlist_viewer_list(struct gui_wps *gwps,
212 struct playlistviewer *viewer)
214 struct wps_state *state = gwps->state;
215 int lines = viewport_get_nb_lines(viewer->vp);
216 int line_height = font_get(viewer->vp->font)->height;
217 int cur_pos, max;
218 int start_item;
219 int i;
220 struct wps_token token;
221 int x, length, alignment = WPS_TOKEN_ALIGN_LEFT;
223 struct mp3entry *pid3;
224 char buf[MAX_PATH*2], tempbuf[MAX_PATH];
225 const char *filename;
226 #if CONFIG_TUNER
227 if (current_screen() == GO_TO_FM)
229 cur_pos = radio_current_preset();
230 start_item = cur_pos + viewer->start_offset;
231 max = start_item+radio_preset_count();
233 else
234 #endif
236 cur_pos = playlist_get_display_index();
237 max = playlist_amount()+1;
238 start_item = MAX(0, cur_pos + viewer->start_offset);
241 gwps->display->set_viewport(viewer->vp);
242 for(i=start_item; (i-start_item)<lines && i<max; i++)
244 int line;
245 #if CONFIG_TUNER
246 if (current_screen() == GO_TO_FM)
248 pid3 = NULL;
249 line = TRACK_HAS_INFO;
250 filename = "";
252 else
253 #endif
255 filename = playlist_peek(i-cur_pos);
256 if (i == cur_pos)
258 pid3 = state->id3;
260 else if (i == cur_pos+1)
262 pid3 = state->nid3;
264 #if CONFIG_CODEC == SWCODEC
265 else if (i>cur_pos)
267 #ifdef HAVE_TC_RAMCACHE
268 if (tagcache_fill_tags(&viewer->tempid3, filename))
270 pid3 = &viewer->tempid3;
272 else
273 #endif
274 if (!audio_peek_track(&pid3, i-cur_pos))
275 pid3 = NULL;
277 #endif
278 else
280 pid3 = NULL;
282 line = pid3 ? TRACK_HAS_INFO : TRACK_HAS_NO_INFO;
284 int j = 0, cur_string = 0;
285 unsigned int line_len = 0;
286 buf[0] = '\0';
287 while (j < viewer->lines[line].count && line_len < sizeof(buf))
289 const char *out = NULL;
290 token.type = viewer->lines[line].tokens[j];
291 token.value.i = 0;
292 token.next = false;
293 out = get_id3_token(&token, pid3, tempbuf, sizeof(tempbuf), -1, NULL);
294 #if CONFIG_TUNER
295 if (!out)
296 out = get_radio_token(&token, i-cur_pos,
297 tempbuf, sizeof(tempbuf), -1, NULL);
298 #endif
299 if (out)
301 line_len = strlcat(buf, out, sizeof(buf));
302 j++;
303 continue;
306 switch (viewer->lines[line].tokens[j])
308 case WPS_TOKEN_ALIGN_CENTER:
309 case WPS_TOKEN_ALIGN_LEFT:
310 case WPS_TOKEN_ALIGN_LEFT_RTL:
311 case WPS_TOKEN_ALIGN_RIGHT:
312 case WPS_TOKEN_ALIGN_RIGHT_RTL:
313 alignment = viewer->lines[line].tokens[j];
314 tempbuf[0] = '\0';
315 break;
316 case WPS_TOKEN_STRING:
317 case WPS_TOKEN_CHARACTER:
318 snprintf(tempbuf, sizeof(tempbuf), "%s",
319 viewer->lines[line].strings[cur_string]);
320 cur_string++;
321 break;
322 case WPS_TOKEN_PLAYLIST_POSITION:
323 snprintf(tempbuf, sizeof(tempbuf), "%d", i);
324 break;
325 case WPS_TOKEN_FILE_NAME:
326 get_dir(tempbuf, sizeof(tempbuf), filename, 0);
327 break;
328 case WPS_TOKEN_FILE_PATH:
329 snprintf(tempbuf, sizeof(tempbuf), "%s", filename);
330 break;
331 default:
332 tempbuf[0] = '\0';
333 break;
335 if (tempbuf[0])
337 line_len = strlcat(buf, tempbuf, sizeof(buf));
339 j++;
342 int vpwidth = viewer->vp->width;
343 length = gwps->display->getstringsize(buf, NULL, NULL);
344 if (viewer->lines[line].scroll && length >= vpwidth)
346 gwps->display->puts_scroll(0, (i-start_item), buf );
348 else
350 if (length >= vpwidth)
351 x = 0;
352 else
354 switch (alignment)
356 case WPS_TOKEN_ALIGN_CENTER:
357 x = (vpwidth-length)/2;
358 break;
359 case WPS_TOKEN_ALIGN_LEFT_RTL:
360 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
362 x = vpwidth - length;
363 break;
365 case WPS_TOKEN_ALIGN_LEFT:
366 x = 0;
367 break;
368 case WPS_TOKEN_ALIGN_RIGHT_RTL:
369 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
371 x = 0;
372 break;
374 case WPS_TOKEN_ALIGN_RIGHT:
375 x = vpwidth - length;
376 break;
377 default:
378 x = 0;
379 break;
382 gwps->display->putsxy(x, (i-start_item)*line_height, buf );
388 /* clears the area where the image was shown */
389 static void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
391 if(!gwps)
392 return;
393 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
394 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
395 gwps->display->set_drawmode(DRMODE_SOLID);
398 static void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
400 struct screen *display = gwps->display;
401 if(img->always_display)
402 display->set_drawmode(DRMODE_FG);
403 else
404 display->set_drawmode(DRMODE_SOLID);
406 #if LCD_DEPTH > 1
407 if(img->bm.format == FORMAT_MONO) {
408 #endif
409 display->mono_bitmap_part(img->bm.data,
410 0, img->subimage_height * subimage,
411 img->bm.width, img->x,
412 img->y, img->bm.width,
413 img->subimage_height);
414 #if LCD_DEPTH > 1
415 } else {
416 display->transparent_bitmap_part((fb_data *)img->bm.data,
417 0, img->subimage_height * subimage,
418 STRIDE(display->screen_type,
419 img->bm.width, img->bm.height),
420 img->x, img->y, img->bm.width,
421 img->subimage_height);
423 #endif
426 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
428 if(!gwps || !gwps->data || !gwps->display)
429 return;
431 struct wps_data *data = gwps->data;
432 struct screen *display = gwps->display;
433 struct skin_token_list *list = data->images;
435 while (list)
437 struct gui_img *img = (struct gui_img*)list->token->value.data;
438 if (img->loaded)
440 if (img->display >= 0)
442 wps_draw_image(gwps, img, img->display);
444 else if (img->always_display && img->vp == vp)
446 wps_draw_image(gwps, img, 0);
449 list = list->next;
451 #ifdef HAVE_ALBUMART
452 /* now draw the AA */
453 if (data->albumart && data->albumart->vp == vp
454 && data->albumart->draw)
456 int handle = playback_current_aa_hid(data->playback_aa_slot);
457 #if CONFIG_TUNER
458 if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
460 struct dim dim = {data->albumart->width, data->albumart->height};
461 handle = radio_get_art_hid(&dim);
463 #endif
464 draw_album_art(gwps, handle, false);
465 data->albumart->draw = false;
467 #endif
469 display->set_drawmode(DRMODE_SOLID);
472 #else /* HAVE_LCD_CHARCELL */
474 static bool draw_player_progress(struct gui_wps *gwps)
476 struct wps_state *state = gwps->state;
477 struct screen *display = gwps->display;
478 unsigned char progress_pattern[7];
479 int pos = 0;
480 int i;
482 int elapsed, length;
483 if (LIKELY(state->id3))
485 elapsed = state->id3->elapsed;
486 length = state->id3->length;
488 else
490 elapsed = 0;
491 length = 0;
494 if (length)
495 pos = 36 * (elapsed + state->ff_rewind_count) / length;
497 for (i = 0; i < 7; i++, pos -= 5)
499 if (pos <= 0)
500 progress_pattern[i] = 0x1fu;
501 else if (pos >= 5)
502 progress_pattern[i] = 0x00u;
503 else
504 progress_pattern[i] = 0x1fu >> pos;
507 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
508 return true;
511 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
513 static const unsigned char numbers[10][4] = {
514 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
515 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
516 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
517 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
518 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
519 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
520 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
521 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
522 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
523 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
526 struct wps_state *state = gwps->state;
527 struct screen *display = gwps->display;
528 struct wps_data *data = gwps->data;
529 unsigned char progress_pattern[7];
530 char timestr[10];
531 int time;
532 int time_idx = 0;
533 int pos = 0;
534 int pat_idx = 1;
535 int digit, i, j;
536 bool softchar;
538 int elapsed, length;
539 if (LIKELY(state->id3))
541 elapsed = state->id3->elapsed;
542 length = state->id3->length;
544 else
546 elapsed = 0;
547 length = 0;
550 if (buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
551 return;
553 time = elapsed + state->ff_rewind_count;
554 if (length)
555 pos = 55 * time / length;
557 memset(timestr, 0, sizeof(timestr));
558 format_time(timestr, sizeof(timestr)-2, time);
559 timestr[strlen(timestr)] = ':'; /* always safe */
561 for (i = 0; i < 11; i++, pos -= 5)
563 softchar = false;
564 memset(progress_pattern, 0, sizeof(progress_pattern));
566 if ((digit = timestr[time_idx]))
568 softchar = true;
569 digit -= '0';
571 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
573 memcpy(progress_pattern, numbers[digit], 4);
574 time_idx += 2;
576 else /* tens, shifted right */
578 for (j = 0; j < 4; j++)
579 progress_pattern[j] = numbers[digit][j] >> 1;
581 if (time_idx > 0) /* not the first group, add colon in front */
583 progress_pattern[1] |= 0x10u;
584 progress_pattern[3] |= 0x10u;
586 time_idx++;
589 if (pos >= 5)
590 progress_pattern[5] = progress_pattern[6] = 0x1fu;
593 if (pos > 0 && pos < 5)
595 softchar = true;
596 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
599 if (softchar && pat_idx < 8)
601 display->define_pattern(data->wps_progress_pat[pat_idx],
602 progress_pattern);
603 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
604 pat_idx++;
606 else if (pos <= 0)
607 buf = utf8encode(' ', buf);
608 else
609 buf = utf8encode(0xe115, buf); /* 2/7 _ */
611 *buf = '\0';
614 #endif /* HAVE_LCD_CHARCELL */
616 /* Return the index to the end token for the conditional token at index.
617 The conditional token can be either a start token or a separator
618 (i.e. option) token.
620 static int find_conditional_end(struct wps_data *data, int index)
622 int ret = index;
623 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
624 ret = data->tokens[ret].value.i;
626 /* ret now is the index to the end token for the conditional. */
627 return ret;
630 /* Evaluate the conditional that is at *token_index and return whether a skip
631 has ocurred. *token_index is updated with the new position.
633 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
635 if (!gwps)
636 return false;
638 struct wps_data *data = gwps->data;
640 int i, cond_end;
641 int cond_index = *token_index;
642 char result[128];
643 const char *value;
644 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
645 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
647 /* treat ?xx<true> constructs as if they had 2 options. */
648 if (num_options < 2)
649 num_options = 2;
651 int intval = num_options;
652 /* get_token_value needs to know the number of options in the enum */
653 value = get_token_value(gwps, &data->tokens[cond_index + 1],
654 result, sizeof(result), &intval);
656 /* intval is now the number of the enum option we want to read,
657 starting from 1. If intval is -1, we check if value is empty. */
658 if (intval == -1)
659 intval = (value && *value) ? 1 : num_options;
660 else if (intval > num_options || intval < 1)
661 intval = num_options;
663 data->tokens[cond_index].value.i = (intval << 8) + num_options;
665 /* skip to the appropriate enum case */
666 int next = cond_index + 2;
667 for (i = 1; i < intval; i++)
669 next = data->tokens[next].value.i;
671 *token_index = next;
673 if (prev_val == intval)
675 /* Same conditional case as previously. Return without clearing the
676 pictures */
677 return false;
680 cond_end = find_conditional_end(data, cond_index + 2);
681 for (i = cond_index + 3; i < cond_end; i++)
683 #ifdef HAVE_LCD_BITMAP
684 /* clear all pictures in the conditional and nested ones */
685 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
686 clear_image_pos(gwps, find_image(data->tokens[i].value.i&0xFF, data));
687 else if (data->tokens[i].type == WPS_TOKEN_VOLUMEBAR ||
688 data->tokens[i].type == WPS_TOKEN_PROGRESSBAR ||
689 data->tokens[i].type == WPS_TOKEN_BATTERY_PERCENTBAR )
691 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
692 bar->draw = false;
694 else if (data->tokens[i].type == WPS_TOKEN_PEAKMETER)
696 data->peak_meter_enabled = false;
698 #endif
699 #ifdef HAVE_ALBUMART
700 if (data->albumart && data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
702 draw_album_art(gwps,
703 playback_current_aa_hid(data->playback_aa_slot), true);
704 data->albumart->draw = false;
706 #endif
709 return true;
713 /* Read a (sub)line to the given alignment format buffer.
714 linebuf is the buffer where the data is actually stored.
715 align is the alignment format that'll be used to display the text.
716 The return value indicates whether the line needs to be updated.
718 static bool get_line(struct gui_wps *gwps,
719 struct skin_subline *subline,
720 struct align_pos *align,
721 char *linebuf,
722 int linebuf_size,
723 unsigned refresh_mode)
725 struct wps_data *data = gwps->data;
727 char temp_buf[128];
728 char *buf = linebuf; /* will always point to the writing position */
729 char *linebuf_end = linebuf + linebuf_size - 1;
730 bool update = false;
731 int i;
732 (void)refresh_mode; /* silence warning on charcell */
734 /* alignment-related variables */
735 int cur_align;
736 char* cur_align_start;
737 cur_align_start = buf;
738 cur_align = WPS_ALIGN_LEFT;
739 align->left = NULL;
740 align->center = NULL;
741 align->right = NULL;
742 /* Process all tokens of the desired subline */
743 for (i = subline->first_token_idx;
744 i <= subline->last_token_idx; i++)
746 switch(data->tokens[i].type)
748 case WPS_TOKEN_CONDITIONAL:
749 /* place ourselves in the right conditional case */
750 update |= evaluate_conditional(gwps, &i);
751 break;
753 case WPS_TOKEN_CONDITIONAL_OPTION:
754 /* we've finished in the curent conditional case,
755 skip to the end of the conditional structure */
756 i = find_conditional_end(data, i);
757 break;
758 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
759 case WPS_TOKEN_VIEWPORT_FGCOLOUR:
761 struct viewport_colour *col = data->tokens[i].value.data;
762 col->vp->fg_pattern = col->colour;
764 break;
765 case WPS_TOKEN_VIEWPORT_BGCOLOUR:
767 struct viewport_colour *col = data->tokens[i].value.data;
768 col->vp->bg_pattern = col->colour;
770 break;
771 #endif
772 #ifdef HAVE_LCD_BITMAP
773 case WPS_TOKEN_PEAKMETER:
774 data->peak_meter_enabled = true;
775 break;
776 case WPS_TOKEN_VOLUMEBAR:
777 case WPS_TOKEN_BATTERY_PERCENTBAR:
778 case WPS_TOKEN_PROGRESSBAR:
780 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
781 bar->draw = true;
783 break;
784 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
786 char n = data->tokens[i].value.i & 0xFF;
787 int subimage = data->tokens[i].value.i >> 8;
788 struct gui_img *img = find_image(n, data);
790 if (img && img->loaded)
791 img->display = subimage;
792 break;
794 case WPS_TOKEN_DRAW_INBUILTBAR:
795 gui_statusbar_draw(&(statusbars.statusbars[gwps->display->screen_type]),
796 refresh_mode == WPS_REFRESH_ALL,
797 data->tokens[i].value.data);
798 break;
799 #endif
801 case WPS_TOKEN_ALIGN_LEFT:
802 case WPS_TOKEN_ALIGN_LEFT_RTL:
803 case WPS_TOKEN_ALIGN_CENTER:
804 case WPS_TOKEN_ALIGN_RIGHT:
805 case WPS_TOKEN_ALIGN_RIGHT_RTL:
806 /* remember where the current aligned text started */
807 switch (cur_align)
809 case WPS_ALIGN_LEFT:
810 align->left = cur_align_start;
811 break;
813 case WPS_ALIGN_CENTER:
814 align->center = cur_align_start;
815 break;
817 case WPS_ALIGN_RIGHT:
818 align->right = cur_align_start;
819 break;
821 /* start a new alignment */
822 switch (data->tokens[i].type)
824 case WPS_TOKEN_ALIGN_LEFT:
825 cur_align = WPS_ALIGN_LEFT;
826 break;
827 case WPS_TOKEN_ALIGN_LEFT_RTL:
828 cur_align = lang_is_rtl() ? WPS_ALIGN_RIGHT :
829 WPS_ALIGN_LEFT;
830 break;
831 case WPS_TOKEN_ALIGN_CENTER:
832 cur_align = WPS_ALIGN_CENTER;
833 break;
834 case WPS_TOKEN_ALIGN_RIGHT:
835 cur_align = WPS_ALIGN_RIGHT;
836 break;
837 case WPS_TOKEN_ALIGN_RIGHT_RTL:
838 cur_align = lang_is_rtl() ? WPS_ALIGN_LEFT :
839 WPS_ALIGN_RIGHT;
840 break;
841 default:
842 break;
844 *buf++ = 0;
845 cur_align_start = buf;
846 break;
847 case WPS_VIEWPORT_ENABLE:
849 char label = data->tokens[i].value.i;
850 char temp = VP_DRAW_HIDEABLE;
851 /* viewports are allowed to share id's so find and enable
852 * all of them */
853 struct skin_token_list *list = data->viewports;
854 while (list)
856 struct skin_viewport *vp =
857 (struct skin_viewport *)list->token->value.data;
858 if (vp->label == label)
860 if (vp->hidden_flags&VP_DRAW_WASHIDDEN)
861 temp |= VP_DRAW_WASHIDDEN;
862 vp->hidden_flags = temp;
864 list = list->next;
867 break;
868 #ifdef HAVE_LCD_BITMAP
869 case WPS_TOKEN_UIVIEWPORT_ENABLE:
870 sb_set_info_vp(gwps->display->screen_type,
871 data->tokens[i].value.i|VP_INFO_LABEL);
872 break;
873 case WPS_VIEWPORT_CUSTOMLIST:
874 draw_playlist_viewer_list(gwps, data->tokens[i].value.data);
875 break;
876 #endif
877 default:
879 /* get the value of the tag and copy it to the buffer */
880 const char *value = get_token_value(gwps, &data->tokens[i],
881 temp_buf, sizeof(temp_buf), NULL);
882 if (value)
884 update = true;
885 while (*value && (buf < linebuf_end))
886 *buf++ = *value++;
888 break;
893 /* close the current alignment */
894 switch (cur_align)
896 case WPS_ALIGN_LEFT:
897 align->left = cur_align_start;
898 break;
900 case WPS_ALIGN_CENTER:
901 align->center = cur_align_start;
902 break;
904 case WPS_ALIGN_RIGHT:
905 align->right = cur_align_start;
906 break;
909 return update;
911 static void get_subline_timeout(struct gui_wps *gwps, struct skin_subline *subline)
913 struct wps_data *data = gwps->data;
914 int i;
915 subline->time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
917 for (i = subline->first_token_idx;
918 i <= subline->last_token_idx; i++)
920 switch(data->tokens[i].type)
922 case WPS_TOKEN_CONDITIONAL:
923 /* place ourselves in the right conditional case */
924 evaluate_conditional(gwps, &i);
925 break;
927 case WPS_TOKEN_CONDITIONAL_OPTION:
928 /* we've finished in the curent conditional case,
929 skip to the end of the conditional structure */
930 i = find_conditional_end(data, i);
931 break;
933 case WPS_TOKEN_SUBLINE_TIMEOUT:
934 subline->time_mult = data->tokens[i].value.i;
935 break;
937 default:
938 break;
943 /* Calculates which subline should be displayed for the specified line
944 Returns true iff the subline must be refreshed */
945 static bool update_curr_subline(struct gui_wps *gwps, struct skin_line *line)
947 /* shortcut this whole thing if we need to reset the line completly */
948 if (line->curr_subline == NULL)
950 line->subline_expire_time = current_tick;
951 line->curr_subline = &line->sublines;
952 if (!line->curr_subline->next)
954 line->subline_expire_time += 100*HZ;
956 else
958 get_subline_timeout(gwps, line->curr_subline);
959 line->subline_expire_time += TIMEOUT_UNIT*line->curr_subline->time_mult;
961 return true;
963 /* if time to advance to next sub-line */
964 if (TIME_AFTER(current_tick, line->subline_expire_time - 1))
966 /* if there is only one subline, there is no need to search for a new one */
967 if (&line->sublines == line->curr_subline &&
968 line->curr_subline->next == NULL)
970 line->subline_expire_time += 100 * HZ;
971 return false;
973 if (line->curr_subline->next)
974 line->curr_subline = line->curr_subline->next;
975 else
976 line->curr_subline = &line->sublines;
977 get_subline_timeout(gwps, line->curr_subline);
978 line->subline_expire_time = current_tick + TIMEOUT_UNIT*line->curr_subline->time_mult;
979 return true;
981 return false;
984 /* Display a line appropriately according to its alignment format.
985 format_align contains the text, separated between left, center and right.
986 line is the index of the line on the screen.
987 scroll indicates whether the line is a scrolling one or not.
989 static void write_line(struct screen *display,
990 struct align_pos *format_align,
991 int line,
992 bool scroll)
994 int left_width = 0, left_xpos;
995 int center_width = 0, center_xpos;
996 int right_width = 0, right_xpos;
997 int ypos;
998 int space_width;
999 int string_height;
1000 int scroll_width;
1002 /* calculate different string sizes and positions */
1003 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
1004 if (format_align->left != 0) {
1005 display->getstringsize((unsigned char *)format_align->left,
1006 &left_width, &string_height);
1009 if (format_align->right != 0) {
1010 display->getstringsize((unsigned char *)format_align->right,
1011 &right_width, &string_height);
1014 if (format_align->center != 0) {
1015 display->getstringsize((unsigned char *)format_align->center,
1016 &center_width, &string_height);
1019 left_xpos = 0;
1020 right_xpos = (display->getwidth() - right_width);
1021 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1023 scroll_width = display->getwidth() - left_xpos;
1025 /* Checks for overlapping strings.
1026 If needed the overlapping strings will be merged, separated by a
1027 space */
1029 /* CASE 1: left and centered string overlap */
1030 /* there is a left string, need to merge left and center */
1031 if ((left_width != 0 && center_width != 0) &&
1032 (left_xpos + left_width + space_width > center_xpos)) {
1033 /* replace the former separator '\0' of left and
1034 center string with a space */
1035 *(--format_align->center) = ' ';
1036 /* calculate the new width and position of the merged string */
1037 left_width = left_width + space_width + center_width;
1038 /* there is no centered string anymore */
1039 center_width = 0;
1041 /* there is no left string, move center to left */
1042 if ((left_width == 0 && center_width != 0) &&
1043 (left_xpos + left_width > center_xpos)) {
1044 /* move the center string to the left string */
1045 format_align->left = format_align->center;
1046 /* calculate the new width and position of the string */
1047 left_width = center_width;
1048 /* there is no centered string anymore */
1049 center_width = 0;
1052 /* CASE 2: centered and right string overlap */
1053 /* there is a right string, need to merge center and right */
1054 if ((center_width != 0 && right_width != 0) &&
1055 (center_xpos + center_width + space_width > right_xpos)) {
1056 /* replace the former separator '\0' of center and
1057 right string with a space */
1058 *(--format_align->right) = ' ';
1059 /* move the center string to the right after merge */
1060 format_align->right = format_align->center;
1061 /* calculate the new width and position of the merged string */
1062 right_width = center_width + space_width + right_width;
1063 right_xpos = (display->getwidth() - right_width);
1064 /* there is no centered string anymore */
1065 center_width = 0;
1067 /* there is no right string, move center to right */
1068 if ((center_width != 0 && right_width == 0) &&
1069 (center_xpos + center_width > right_xpos)) {
1070 /* move the center string to the right string */
1071 format_align->right = format_align->center;
1072 /* calculate the new width and position of the string */
1073 right_width = center_width;
1074 right_xpos = (display->getwidth() - right_width);
1075 /* there is no centered string anymore */
1076 center_width = 0;
1079 /* CASE 3: left and right overlap
1080 There is no center string anymore, either there never
1081 was one or it has been merged in case 1 or 2 */
1082 /* there is a left string, need to merge left and right */
1083 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1084 (left_xpos + left_width + space_width > right_xpos)) {
1085 /* replace the former separator '\0' of left and
1086 right string with a space */
1087 *(--format_align->right) = ' ';
1088 /* calculate the new width and position of the string */
1089 left_width = left_width + space_width + right_width;
1090 /* there is no right string anymore */
1091 right_width = 0;
1093 /* there is no left string, move right to left */
1094 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1095 (left_width > right_xpos)) {
1096 /* move the right string to the left string */
1097 format_align->left = format_align->right;
1098 /* calculate the new width and position of the string */
1099 left_width = right_width;
1100 /* there is no right string anymore */
1101 right_width = 0;
1104 ypos = (line * string_height);
1107 if (scroll && ((left_width > scroll_width) ||
1108 (center_width > scroll_width) ||
1109 (right_width > scroll_width)))
1111 display->puts_scroll(0, line,
1112 (unsigned char *)format_align->left);
1114 else
1116 #ifdef HAVE_LCD_BITMAP
1117 /* clear the line first */
1118 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1119 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1120 display->set_drawmode(DRMODE_SOLID);
1121 #endif
1123 /* Nasty hack: we output an empty scrolling string,
1124 which will reset the scroller for that line */
1125 display->puts_scroll(0, line, (unsigned char *)"");
1127 /* print aligned strings */
1128 if (left_width != 0)
1130 display->putsxy(left_xpos, ypos,
1131 (unsigned char *)format_align->left);
1133 if (center_width != 0)
1135 display->putsxy(center_xpos, ypos,
1136 (unsigned char *)format_align->center);
1138 if (right_width != 0)
1140 display->putsxy(right_xpos, ypos,
1141 (unsigned char *)format_align->right);
1146 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode)
1148 struct wps_data *data = gwps->data;
1149 struct screen *display = gwps->display;
1151 if (!data || !display || !gwps->state)
1152 return false;
1154 unsigned flags;
1155 char linebuf[MAX_PATH];
1157 struct align_pos align;
1158 align.left = NULL;
1159 align.center = NULL;
1160 align.right = NULL;
1163 struct skin_token_list *viewport_list;
1165 bool update_line, new_subline_refresh;
1167 /* reset to first subline if refresh all flag is set */
1168 if (refresh_mode == WPS_REFRESH_ALL)
1170 struct skin_line *line;
1171 struct skin_viewport *skin_viewport = find_viewport(VP_DEFAULT_LABEL, data);
1173 if (!(skin_viewport->hidden_flags & VP_NEVER_VISIBLE))
1175 display->set_viewport(&skin_viewport->vp);
1176 display->clear_viewport();
1179 for (viewport_list = data->viewports;
1180 viewport_list; viewport_list = viewport_list->next)
1182 skin_viewport =
1183 (struct skin_viewport *)viewport_list->token->value.data;
1184 for(line = skin_viewport->lines; line; line = line->next)
1186 line->curr_subline = NULL;
1191 #ifdef HAVE_LCD_CHARCELLS
1192 int i;
1193 for (i = 0; i < 8; i++)
1195 if (data->wps_progress_pat[i] == 0)
1196 data->wps_progress_pat[i] = display->get_locked_pattern();
1198 #endif
1200 /* disable any viewports which are conditionally displayed.
1201 * If we are only refreshing the peak meter then don't change the viewport
1202 * enabled flags as this will stop scrolling. viewports cant be
1203 * toggled in this refresh mode anyway (FS#10215)*/
1204 if (refresh_mode != WPS_REFRESH_PEAK_METER)
1206 for (viewport_list = data->viewports;
1207 viewport_list; viewport_list = viewport_list->next)
1209 struct skin_viewport *skin_viewport =
1210 (struct skin_viewport *)viewport_list->token->value.data;
1211 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1213 continue;
1215 if (skin_viewport->hidden_flags&VP_DRAW_HIDEABLE)
1217 if (skin_viewport->hidden_flags&VP_DRAW_HIDDEN)
1218 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1219 else
1220 skin_viewport->hidden_flags |= VP_DRAW_HIDDEN;
1224 for (viewport_list = data->viewports;
1225 viewport_list; viewport_list = viewport_list->next)
1227 struct skin_viewport *skin_viewport =
1228 (struct skin_viewport *)viewport_list->token->value.data;
1229 unsigned vp_refresh_mode = refresh_mode;
1230 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)
1231 skin_viewport->vp.fg_pattern = skin_viewport->start_fgcolour;
1232 skin_viewport->vp.bg_pattern = skin_viewport->start_bgcolour;
1233 #endif
1234 display->set_viewport(&skin_viewport->vp);
1236 int hidden_vp = 0;
1238 #ifdef HAVE_LCD_BITMAP
1239 /* Set images to not to be displayed */
1240 struct skin_token_list *imglist = data->images;
1241 while (imglist)
1243 struct gui_img *img = (struct gui_img *)imglist->token->value.data;
1244 img->display = -1;
1245 imglist = imglist->next;
1247 #endif
1248 /* dont redraw the viewport if its disabled */
1249 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1250 { /* don't draw anything into this one */
1251 vp_refresh_mode = 0; hidden_vp = true;
1253 else if ((skin_viewport->hidden_flags&VP_DRAW_HIDDEN))
1255 if (!(skin_viewport->hidden_flags&VP_DRAW_WASHIDDEN))
1256 display->scroll_stop(&skin_viewport->vp);
1257 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1258 continue;
1260 else if (((skin_viewport->hidden_flags&
1261 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1262 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1264 vp_refresh_mode = WPS_REFRESH_ALL;
1265 skin_viewport->hidden_flags = VP_DRAW_HIDEABLE;
1268 if (vp_refresh_mode == WPS_REFRESH_ALL)
1270 display->clear_viewport();
1273 /* loop over the lines for this viewport */
1274 struct skin_line *line;
1275 /* %V() doesnt eat the \n which means the first line of text
1276 * is actually going to be one line down. so set line_count to -1
1277 * unless we are using the default viewport which doesnt have this problem */
1278 int line_count = skin_viewport->label==VP_DEFAULT_LABEL?0:-1;
1280 for (line = skin_viewport->lines; line; line = line->next, line_count++)
1282 struct skin_subline *subline;
1283 memset(linebuf, 0, sizeof(linebuf));
1284 update_line = false;
1286 /* get current subline for the line */
1287 new_subline_refresh = update_curr_subline(gwps, line);
1288 subline = line->curr_subline;
1289 flags = line->curr_subline->line_type;
1291 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1292 || new_subline_refresh || hidden_vp)
1294 /* get_line tells us if we need to update the line */
1295 update_line = get_line(gwps, subline, &align,
1296 linebuf, sizeof(linebuf), vp_refresh_mode);
1298 #ifdef HAVE_LCD_BITMAP
1299 /* peakmeter */
1300 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1302 if (!data->peak_meter_enabled)
1304 peak_meter_enable(false);
1306 else
1308 /* the peakmeter should be alone on its line */
1309 update_line = false;
1311 int h = font_get(skin_viewport->vp.font)->height;
1312 int peak_meter_y = line_count* h;
1314 /* The user might decide to have the peak meter in the last
1315 line so that it is only displayed if no status bar is
1316 visible. If so we neither want do draw nor enable the
1317 peak meter. */
1318 if (peak_meter_y + h <= skin_viewport->vp.y+skin_viewport->vp.height) {
1319 peak_meter_enable(true);
1320 peak_meter_screen(gwps->display, 0, peak_meter_y,
1321 MIN(h, skin_viewport->vp.y+skin_viewport->vp.height - peak_meter_y));
1326 #else /* HAVE_LCD_CHARCELL */
1328 /* progressbar */
1329 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1331 if (data->full_line_progressbar)
1332 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1333 else
1334 draw_player_progress(gwps);
1336 #endif
1338 if (line_count>= 0 && update_line && !hidden_vp &&
1339 /* conditionals clear the line which means if the %Vd is put into the default
1340 viewport there will be a blank line.
1341 To get around this we dont allow any actual drawing to happen in the
1342 deault vp if other vp's are defined */
1343 ((skin_viewport->label != VP_DEFAULT_LABEL && viewport_list->next) ||
1344 !viewport_list->next))
1346 if (flags & WPS_REFRESH_SCROLL)
1348 /* if the line is a scrolling one we don't want to update
1349 too often, so that it has the time to scroll */
1350 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1351 write_line(display, &align, line_count, true);
1353 else
1354 write_line(display, &align, line_count, false);
1357 #ifdef HAVE_LCD_BITMAP
1358 /* progressbar */
1359 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1361 struct skin_token_list *bar = gwps->data->progressbars;
1362 while (bar)
1364 struct progressbar *thisbar = (struct progressbar*)bar->token->value.data;
1365 if (thisbar->vp == &skin_viewport->vp && thisbar->draw)
1367 draw_progressbar(gwps, thisbar);
1369 bar = bar->next;
1372 /* Now display any images in this viewport */
1373 if (!hidden_vp)
1374 wps_display_images(gwps, &skin_viewport->vp);
1375 #endif
1378 /* Restore the default viewport */
1379 display->set_viewport(NULL);
1381 display->update();
1383 return true;
1386 bool skin_has_sbs(enum screen_type screen, struct wps_data *data)
1388 (void)screen;
1389 (void)data;
1390 bool draw = false;
1391 #ifdef HAVE_LCD_BITMAP
1392 if (data->wps_sb_tag)
1393 draw = data->show_sb_on_wps;
1394 else if (statusbar_position(screen) != STATUSBAR_OFF)
1395 draw = true;
1396 #endif
1397 return draw;
1400 /* do the button loop as often as required for the peak meters to update
1401 * with a good refresh rate.
1402 * gwps is really gwps[NB_SCREENS]! don't wrap this if FOR_NB_SCREENS()
1404 int skin_wait_for_action(struct gui_wps *gwps, int context, int timeout)
1406 (void)gwps; /* silence charcell warning */
1407 int button = ACTION_NONE;
1408 #ifdef HAVE_LCD_BITMAP
1409 int i;
1410 /* when the peak meter is enabled we want to have a
1411 few extra updates to make it look smooth. On the
1412 other hand we don't want to waste energy if it
1413 isn't displayed */
1414 bool pm=false;
1415 FOR_NB_SCREENS(i)
1417 if(gwps[i].data->peak_meter_enabled)
1418 pm = true;
1421 if (pm) {
1422 long next_refresh = current_tick;
1423 long next_big_refresh = current_tick + timeout;
1424 button = BUTTON_NONE;
1425 while (TIME_BEFORE(current_tick, next_big_refresh)) {
1426 button = get_action(context,TIMEOUT_NOBLOCK);
1427 if (button != ACTION_NONE) {
1428 break;
1430 peak_meter_peek();
1431 sleep(0); /* Sleep until end of current tick. */
1433 if (TIME_AFTER(current_tick, next_refresh)) {
1434 FOR_NB_SCREENS(i)
1436 if(gwps[i].data->peak_meter_enabled)
1437 skin_update(&gwps[i], WPS_REFRESH_PEAK_METER);
1438 next_refresh += HZ / PEAK_METER_FPS;
1445 /* The peak meter is disabled
1446 -> no additional screen updates needed */
1447 else
1448 #endif
1450 button = get_action(context, timeout);
1452 return button;