fix FS#11264 - frequency bar and presets not working in the sbs
[kugel-rb.git] / apps / gui / skin_engine / skin_display.c
blobe91d7f2f9dff3356c87e4be77b696ade8208cece
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 else if (id3 && id3->length)
163 length = id3->length;
164 elapsed = id3->elapsed + state->ff_rewind_count;
166 #if CONFIG_TUNER
167 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
169 int min = fm_region_data[global_settings.fm_region].freq_min;
170 elapsed = radio_current_frequency() - min;
171 length = fm_region_data[global_settings.fm_region].freq_max - min;
173 #endif
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 && id3 && id3->length)
190 #ifdef AB_REPEAT_ENABLE
191 if (ab_repeat_mode_enabled())
192 ab_draw_markers(display, id3->length,
193 pb->x, y, pb->width, height);
194 #endif
196 if (id3->cuesheet)
197 cue_draw_markers(display, id3->cuesheet, id3->length,
198 pb->x, y+1, pb->width, height-2);
202 static void draw_playlist_viewer_list(struct gui_wps *gwps,
203 struct playlistviewer *viewer)
205 struct wps_state *state = gwps->state;
206 int lines = viewport_get_nb_lines(viewer->vp);
207 int line_height = font_get(viewer->vp->font)->height;
208 int cur_pos, count;
209 int start_item;
210 int i;
211 struct wps_token token;
212 int x, length, alignment = WPS_TOKEN_ALIGN_LEFT;
214 struct mp3entry *pid3;
215 char buf[MAX_PATH*2], tempbuf[MAX_PATH];
216 const char *filename;
217 #if CONFIG_TUNER
218 if (current_screen() == GO_TO_FM)
220 cur_pos = radio_current_preset();
221 count = radio_preset_count();
223 else
224 #endif
226 cur_pos = playlist_get_display_index();
227 count = playlist_amount()+1;
229 start_item = MAX(0, cur_pos + viewer->start_offset);
231 gwps->display->set_viewport(viewer->vp);
232 for(i=start_item; (i-start_item)<lines && i<count; i++)
234 int line;
235 #if CONFIG_TUNER
236 if (current_screen() == GO_TO_FM)
238 pid3 = NULL;
239 line = TRACK_HAS_INFO;
240 filename = "";
242 else
243 #endif
245 filename = playlist_peek(i-cur_pos);
246 if (i == cur_pos)
248 pid3 = state->id3;
250 else if (i == cur_pos+1)
252 pid3 = state->nid3;
254 #if CONFIG_CODEC == SWCODEC
255 else if (i>cur_pos)
257 #ifdef HAVE_TC_RAMCACHE
258 if (tagcache_fill_tags(&viewer->tempid3, filename))
260 pid3 = &viewer->tempid3;
262 else
263 #endif
264 if (!audio_peek_track(&pid3, i-cur_pos))
265 pid3 = NULL;
267 #endif
268 else
270 pid3 = NULL;
272 line = pid3 ? TRACK_HAS_INFO : TRACK_HAS_NO_INFO;
274 int j = 0, cur_string = 0;
275 unsigned int line_len = 0;
276 buf[0] = '\0';
277 while (j < viewer->lines[line].count && line_len < sizeof(buf))
279 const char *out = NULL;
280 token.type = viewer->lines[line].tokens[j];
281 token.value.i = 0;
282 token.next = false;
283 out = get_id3_token(&token, pid3, tempbuf, sizeof(tempbuf), -1, NULL);
284 #if CONFIG_TUNER
285 if (!out)
286 out = get_radio_token(&token, i-cur_pos,
287 tempbuf, sizeof(tempbuf), -1, NULL);
288 #endif
289 if (out)
291 line_len = strlcat(buf, out, sizeof(buf));
292 j++;
293 continue;
296 switch (viewer->lines[line].tokens[j])
298 case WPS_TOKEN_ALIGN_CENTER:
299 case WPS_TOKEN_ALIGN_LEFT:
300 case WPS_TOKEN_ALIGN_LEFT_RTL:
301 case WPS_TOKEN_ALIGN_RIGHT:
302 case WPS_TOKEN_ALIGN_RIGHT_RTL:
303 alignment = viewer->lines[line].tokens[j];
304 tempbuf[0] = '\0';
305 break;
306 case WPS_TOKEN_STRING:
307 case WPS_TOKEN_CHARACTER:
308 snprintf(tempbuf, sizeof(tempbuf), "%s",
309 viewer->lines[line].strings[cur_string]);
310 cur_string++;
311 break;
312 case WPS_TOKEN_PLAYLIST_POSITION:
313 snprintf(tempbuf, sizeof(tempbuf), "%d", i);
314 break;
315 case WPS_TOKEN_FILE_NAME:
316 get_dir(tempbuf, sizeof(tempbuf), filename, 0);
317 break;
318 case WPS_TOKEN_FILE_PATH:
319 snprintf(tempbuf, sizeof(tempbuf), "%s", filename);
320 break;
321 default:
322 tempbuf[0] = '\0';
323 break;
325 if (tempbuf[0])
327 line_len = strlcat(buf, tempbuf, sizeof(buf));
329 j++;
332 int vpwidth = viewer->vp->width;
333 length = gwps->display->getstringsize(buf, NULL, NULL);
334 if (viewer->lines[line].scroll && length >= vpwidth)
336 gwps->display->puts_scroll(0, (i-start_item), buf );
338 else
340 if (length >= vpwidth)
341 x = 0;
342 else
344 switch (alignment)
346 case WPS_TOKEN_ALIGN_CENTER:
347 x = (vpwidth-length)/2;
348 break;
349 case WPS_TOKEN_ALIGN_LEFT_RTL:
350 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
352 x = vpwidth - length;
353 break;
355 case WPS_TOKEN_ALIGN_LEFT:
356 x = 0;
357 break;
358 case WPS_TOKEN_ALIGN_RIGHT_RTL:
359 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
361 x = 0;
362 break;
364 case WPS_TOKEN_ALIGN_RIGHT:
365 x = vpwidth - length;
366 break;
367 default:
368 x = 0;
369 break;
372 gwps->display->putsxy(x, (i-start_item)*line_height, buf );
378 /* clears the area where the image was shown */
379 static void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
381 if(!gwps)
382 return;
383 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
384 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
385 gwps->display->set_drawmode(DRMODE_SOLID);
388 static void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
390 struct screen *display = gwps->display;
391 if(img->always_display)
392 display->set_drawmode(DRMODE_FG);
393 else
394 display->set_drawmode(DRMODE_SOLID);
396 #if LCD_DEPTH > 1
397 if(img->bm.format == FORMAT_MONO) {
398 #endif
399 display->mono_bitmap_part(img->bm.data,
400 0, img->subimage_height * subimage,
401 img->bm.width, img->x,
402 img->y, img->bm.width,
403 img->subimage_height);
404 #if LCD_DEPTH > 1
405 } else {
406 display->transparent_bitmap_part((fb_data *)img->bm.data,
407 0, img->subimage_height * subimage,
408 STRIDE(display->screen_type,
409 img->bm.width, img->bm.height),
410 img->x, img->y, img->bm.width,
411 img->subimage_height);
413 #endif
416 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
418 if(!gwps || !gwps->data || !gwps->display)
419 return;
421 struct wps_data *data = gwps->data;
422 struct screen *display = gwps->display;
423 struct skin_token_list *list = data->images;
425 while (list)
427 struct gui_img *img = (struct gui_img*)list->token->value.data;
428 if (img->loaded)
430 if (img->display >= 0)
432 wps_draw_image(gwps, img, img->display);
434 else if (img->always_display && img->vp == vp)
436 wps_draw_image(gwps, img, 0);
439 list = list->next;
441 #ifdef HAVE_ALBUMART
442 /* now draw the AA */
443 if (data->albumart && data->albumart->vp == vp
444 && data->albumart->draw)
446 draw_album_art(gwps, playback_current_aa_hid(data->playback_aa_slot),
447 false);
448 data->albumart->draw = false;
450 #endif
452 display->set_drawmode(DRMODE_SOLID);
455 #else /* HAVE_LCD_CHARCELL */
457 static bool draw_player_progress(struct gui_wps *gwps)
459 struct wps_state *state = gwps->state;
460 struct screen *display = gwps->display;
461 unsigned char progress_pattern[7];
462 int pos = 0;
463 int i;
465 int elapsed, length;
466 if (LIKELY(state->id3))
468 elapsed = state->id3->elapsed;
469 length = state->id3->length;
471 else
473 elapsed = 0;
474 length = 0;
477 if (length)
478 pos = 36 * (elapsed + state->ff_rewind_count) / length;
480 for (i = 0; i < 7; i++, pos -= 5)
482 if (pos <= 0)
483 progress_pattern[i] = 0x1fu;
484 else if (pos >= 5)
485 progress_pattern[i] = 0x00u;
486 else
487 progress_pattern[i] = 0x1fu >> pos;
490 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
491 return true;
494 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
496 static const unsigned char numbers[10][4] = {
497 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
498 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
499 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
500 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
501 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
502 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
503 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
504 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
505 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
506 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
509 struct wps_state *state = gwps->state;
510 struct screen *display = gwps->display;
511 struct wps_data *data = gwps->data;
512 unsigned char progress_pattern[7];
513 char timestr[10];
514 int time;
515 int time_idx = 0;
516 int pos = 0;
517 int pat_idx = 1;
518 int digit, i, j;
519 bool softchar;
521 int elapsed, length;
522 if (LIKELY(state->id3))
524 elapsed = state->id3->elapsed;
525 length = state->id3->length;
527 else
529 elapsed = 0;
530 length = 0;
533 if (buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
534 return;
536 time = elapsed + state->ff_rewind_count;
537 if (length)
538 pos = 55 * time / length;
540 memset(timestr, 0, sizeof(timestr));
541 format_time(timestr, sizeof(timestr)-2, time);
542 timestr[strlen(timestr)] = ':'; /* always safe */
544 for (i = 0; i < 11; i++, pos -= 5)
546 softchar = false;
547 memset(progress_pattern, 0, sizeof(progress_pattern));
549 if ((digit = timestr[time_idx]))
551 softchar = true;
552 digit -= '0';
554 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
556 memcpy(progress_pattern, numbers[digit], 4);
557 time_idx += 2;
559 else /* tens, shifted right */
561 for (j = 0; j < 4; j++)
562 progress_pattern[j] = numbers[digit][j] >> 1;
564 if (time_idx > 0) /* not the first group, add colon in front */
566 progress_pattern[1] |= 0x10u;
567 progress_pattern[3] |= 0x10u;
569 time_idx++;
572 if (pos >= 5)
573 progress_pattern[5] = progress_pattern[6] = 0x1fu;
576 if (pos > 0 && pos < 5)
578 softchar = true;
579 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
582 if (softchar && pat_idx < 8)
584 display->define_pattern(data->wps_progress_pat[pat_idx],
585 progress_pattern);
586 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
587 pat_idx++;
589 else if (pos <= 0)
590 buf = utf8encode(' ', buf);
591 else
592 buf = utf8encode(0xe115, buf); /* 2/7 _ */
594 *buf = '\0';
597 #endif /* HAVE_LCD_CHARCELL */
599 /* Return the index to the end token for the conditional token at index.
600 The conditional token can be either a start token or a separator
601 (i.e. option) token.
603 static int find_conditional_end(struct wps_data *data, int index)
605 int ret = index;
606 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
607 ret = data->tokens[ret].value.i;
609 /* ret now is the index to the end token for the conditional. */
610 return ret;
613 /* Evaluate the conditional that is at *token_index and return whether a skip
614 has ocurred. *token_index is updated with the new position.
616 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
618 if (!gwps)
619 return false;
621 struct wps_data *data = gwps->data;
623 int i, cond_end;
624 int cond_index = *token_index;
625 char result[128];
626 const char *value;
627 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
628 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
630 /* treat ?xx<true> constructs as if they had 2 options. */
631 if (num_options < 2)
632 num_options = 2;
634 int intval = num_options;
635 /* get_token_value needs to know the number of options in the enum */
636 value = get_token_value(gwps, &data->tokens[cond_index + 1],
637 result, sizeof(result), &intval);
639 /* intval is now the number of the enum option we want to read,
640 starting from 1. If intval is -1, we check if value is empty. */
641 if (intval == -1)
642 intval = (value && *value) ? 1 : num_options;
643 else if (intval > num_options || intval < 1)
644 intval = num_options;
646 data->tokens[cond_index].value.i = (intval << 8) + num_options;
648 /* skip to the appropriate enum case */
649 int next = cond_index + 2;
650 for (i = 1; i < intval; i++)
652 next = data->tokens[next].value.i;
654 *token_index = next;
656 if (prev_val == intval)
658 /* Same conditional case as previously. Return without clearing the
659 pictures */
660 return false;
663 cond_end = find_conditional_end(data, cond_index + 2);
664 for (i = cond_index + 3; i < cond_end; i++)
666 #ifdef HAVE_LCD_BITMAP
667 /* clear all pictures in the conditional and nested ones */
668 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
669 clear_image_pos(gwps, find_image(data->tokens[i].value.i&0xFF, data));
670 else if (data->tokens[i].type == WPS_TOKEN_VOLUMEBAR ||
671 data->tokens[i].type == WPS_TOKEN_PROGRESSBAR ||
672 data->tokens[i].type == WPS_TOKEN_BATTERY_PERCENTBAR )
674 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
675 bar->draw = false;
677 else if (data->tokens[i].type == WPS_TOKEN_PEAKMETER)
679 data->peak_meter_enabled = false;
681 #endif
682 #ifdef HAVE_ALBUMART
683 if (data->albumart && data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
685 draw_album_art(gwps,
686 playback_current_aa_hid(data->playback_aa_slot), true);
687 data->albumart->draw = false;
689 #endif
692 return true;
696 /* Read a (sub)line to the given alignment format buffer.
697 linebuf is the buffer where the data is actually stored.
698 align is the alignment format that'll be used to display the text.
699 The return value indicates whether the line needs to be updated.
701 static bool get_line(struct gui_wps *gwps,
702 struct skin_subline *subline,
703 struct align_pos *align,
704 char *linebuf,
705 int linebuf_size,
706 unsigned refresh_mode)
708 struct wps_data *data = gwps->data;
710 char temp_buf[128];
711 char *buf = linebuf; /* will always point to the writing position */
712 char *linebuf_end = linebuf + linebuf_size - 1;
713 bool update = false;
714 int i;
715 (void)refresh_mode; /* silence warning on charcell */
717 /* alignment-related variables */
718 int cur_align;
719 char* cur_align_start;
720 cur_align_start = buf;
721 cur_align = WPS_ALIGN_LEFT;
722 align->left = NULL;
723 align->center = NULL;
724 align->right = NULL;
725 /* Process all tokens of the desired subline */
726 for (i = subline->first_token_idx;
727 i <= subline->last_token_idx; i++)
729 switch(data->tokens[i].type)
731 case WPS_TOKEN_CONDITIONAL:
732 /* place ourselves in the right conditional case */
733 update |= evaluate_conditional(gwps, &i);
734 break;
736 case WPS_TOKEN_CONDITIONAL_OPTION:
737 /* we've finished in the curent conditional case,
738 skip to the end of the conditional structure */
739 i = find_conditional_end(data, i);
740 break;
742 #ifdef HAVE_LCD_BITMAP
743 case WPS_TOKEN_PEAKMETER:
744 data->peak_meter_enabled = true;
745 break;
746 case WPS_TOKEN_VOLUMEBAR:
747 case WPS_TOKEN_BATTERY_PERCENTBAR:
748 case WPS_TOKEN_PROGRESSBAR:
750 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
751 bar->draw = true;
753 break;
754 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
756 char n = data->tokens[i].value.i & 0xFF;
757 int subimage = data->tokens[i].value.i >> 8;
758 struct gui_img *img = find_image(n, data);
760 if (img && img->loaded)
761 img->display = subimage;
762 break;
764 case WPS_TOKEN_DRAW_INBUILTBAR:
765 gui_statusbar_draw(&(statusbars.statusbars[gwps->display->screen_type]),
766 refresh_mode == WPS_REFRESH_ALL,
767 data->tokens[i].value.data);
768 break;
769 #endif
771 case WPS_TOKEN_ALIGN_LEFT:
772 case WPS_TOKEN_ALIGN_LEFT_RTL:
773 case WPS_TOKEN_ALIGN_CENTER:
774 case WPS_TOKEN_ALIGN_RIGHT:
775 case WPS_TOKEN_ALIGN_RIGHT_RTL:
776 /* remember where the current aligned text started */
777 switch (cur_align)
779 case WPS_ALIGN_LEFT:
780 align->left = cur_align_start;
781 break;
783 case WPS_ALIGN_CENTER:
784 align->center = cur_align_start;
785 break;
787 case WPS_ALIGN_RIGHT:
788 align->right = cur_align_start;
789 break;
791 /* start a new alignment */
792 switch (data->tokens[i].type)
794 case WPS_TOKEN_ALIGN_LEFT:
795 cur_align = WPS_ALIGN_LEFT;
796 break;
797 case WPS_TOKEN_ALIGN_LEFT_RTL:
798 cur_align = lang_is_rtl() ? WPS_ALIGN_RIGHT :
799 WPS_ALIGN_LEFT;
800 break;
801 case WPS_TOKEN_ALIGN_CENTER:
802 cur_align = WPS_ALIGN_CENTER;
803 break;
804 case WPS_TOKEN_ALIGN_RIGHT:
805 cur_align = WPS_ALIGN_RIGHT;
806 break;
807 case WPS_TOKEN_ALIGN_RIGHT_RTL:
808 cur_align = lang_is_rtl() ? WPS_ALIGN_LEFT :
809 WPS_ALIGN_RIGHT;
810 break;
811 default:
812 break;
814 *buf++ = 0;
815 cur_align_start = buf;
816 break;
817 case WPS_VIEWPORT_ENABLE:
819 char label = data->tokens[i].value.i;
820 char temp = VP_DRAW_HIDEABLE;
821 /* viewports are allowed to share id's so find and enable
822 * all of them */
823 struct skin_token_list *list = data->viewports;
824 while (list)
826 struct skin_viewport *vp =
827 (struct skin_viewport *)list->token->value.data;
828 if (vp->label == label)
830 if (vp->hidden_flags&VP_DRAW_WASHIDDEN)
831 temp |= VP_DRAW_WASHIDDEN;
832 vp->hidden_flags = temp;
834 list = list->next;
837 break;
838 #ifdef HAVE_LCD_BITMAP
839 case WPS_TOKEN_UIVIEWPORT_ENABLE:
840 sb_set_info_vp(gwps->display->screen_type,
841 data->tokens[i].value.i|VP_INFO_LABEL);
842 break;
843 case WPS_VIEWPORT_CUSTOMLIST:
844 draw_playlist_viewer_list(gwps, data->tokens[i].value.data);
845 break;
846 #endif
847 default:
849 /* get the value of the tag and copy it to the buffer */
850 const char *value = get_token_value(gwps, &data->tokens[i],
851 temp_buf, sizeof(temp_buf), NULL);
852 if (value)
854 update = true;
855 while (*value && (buf < linebuf_end))
856 *buf++ = *value++;
858 break;
863 /* close the current alignment */
864 switch (cur_align)
866 case WPS_ALIGN_LEFT:
867 align->left = cur_align_start;
868 break;
870 case WPS_ALIGN_CENTER:
871 align->center = cur_align_start;
872 break;
874 case WPS_ALIGN_RIGHT:
875 align->right = cur_align_start;
876 break;
879 return update;
881 static void get_subline_timeout(struct gui_wps *gwps, struct skin_subline *subline)
883 struct wps_data *data = gwps->data;
884 int i;
885 subline->time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
887 for (i = subline->first_token_idx;
888 i <= subline->last_token_idx; i++)
890 switch(data->tokens[i].type)
892 case WPS_TOKEN_CONDITIONAL:
893 /* place ourselves in the right conditional case */
894 evaluate_conditional(gwps, &i);
895 break;
897 case WPS_TOKEN_CONDITIONAL_OPTION:
898 /* we've finished in the curent conditional case,
899 skip to the end of the conditional structure */
900 i = find_conditional_end(data, i);
901 break;
903 case WPS_TOKEN_SUBLINE_TIMEOUT:
904 subline->time_mult = data->tokens[i].value.i;
905 break;
907 default:
908 break;
913 /* Calculates which subline should be displayed for the specified line
914 Returns true iff the subline must be refreshed */
915 static bool update_curr_subline(struct gui_wps *gwps, struct skin_line *line)
917 /* shortcut this whole thing if we need to reset the line completly */
918 if (line->curr_subline == NULL)
920 line->subline_expire_time = current_tick;
921 line->curr_subline = &line->sublines;
922 if (!line->curr_subline->next)
924 line->subline_expire_time += 100*HZ;
926 else
928 get_subline_timeout(gwps, line->curr_subline);
929 line->subline_expire_time += TIMEOUT_UNIT*line->curr_subline->time_mult;
931 return true;
933 /* if time to advance to next sub-line */
934 if (TIME_AFTER(current_tick, line->subline_expire_time - 1))
936 /* if there is only one subline, there is no need to search for a new one */
937 if (&line->sublines == line->curr_subline &&
938 line->curr_subline->next == NULL)
940 line->subline_expire_time += 100 * HZ;
941 return false;
943 if (line->curr_subline->next)
944 line->curr_subline = line->curr_subline->next;
945 else
946 line->curr_subline = &line->sublines;
947 get_subline_timeout(gwps, line->curr_subline);
948 line->subline_expire_time = current_tick + TIMEOUT_UNIT*line->curr_subline->time_mult;
949 return true;
951 return false;
954 /* Display a line appropriately according to its alignment format.
955 format_align contains the text, separated between left, center and right.
956 line is the index of the line on the screen.
957 scroll indicates whether the line is a scrolling one or not.
959 static void write_line(struct screen *display,
960 struct align_pos *format_align,
961 int line,
962 bool scroll)
964 int left_width = 0, left_xpos;
965 int center_width = 0, center_xpos;
966 int right_width = 0, right_xpos;
967 int ypos;
968 int space_width;
969 int string_height;
970 int scroll_width;
972 /* calculate different string sizes and positions */
973 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
974 if (format_align->left != 0) {
975 display->getstringsize((unsigned char *)format_align->left,
976 &left_width, &string_height);
979 if (format_align->right != 0) {
980 display->getstringsize((unsigned char *)format_align->right,
981 &right_width, &string_height);
984 if (format_align->center != 0) {
985 display->getstringsize((unsigned char *)format_align->center,
986 &center_width, &string_height);
989 left_xpos = 0;
990 right_xpos = (display->getwidth() - right_width);
991 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
993 scroll_width = display->getwidth() - left_xpos;
995 /* Checks for overlapping strings.
996 If needed the overlapping strings will be merged, separated by a
997 space */
999 /* CASE 1: left and centered string overlap */
1000 /* there is a left string, need to merge left and center */
1001 if ((left_width != 0 && center_width != 0) &&
1002 (left_xpos + left_width + space_width > center_xpos)) {
1003 /* replace the former separator '\0' of left and
1004 center string with a space */
1005 *(--format_align->center) = ' ';
1006 /* calculate the new width and position of the merged string */
1007 left_width = left_width + space_width + center_width;
1008 /* there is no centered string anymore */
1009 center_width = 0;
1011 /* there is no left string, move center to left */
1012 if ((left_width == 0 && center_width != 0) &&
1013 (left_xpos + left_width > center_xpos)) {
1014 /* move the center string to the left string */
1015 format_align->left = format_align->center;
1016 /* calculate the new width and position of the string */
1017 left_width = center_width;
1018 /* there is no centered string anymore */
1019 center_width = 0;
1022 /* CASE 2: centered and right string overlap */
1023 /* there is a right string, need to merge center and right */
1024 if ((center_width != 0 && right_width != 0) &&
1025 (center_xpos + center_width + space_width > right_xpos)) {
1026 /* replace the former separator '\0' of center and
1027 right string with a space */
1028 *(--format_align->right) = ' ';
1029 /* move the center string to the right after merge */
1030 format_align->right = format_align->center;
1031 /* calculate the new width and position of the merged string */
1032 right_width = center_width + space_width + right_width;
1033 right_xpos = (display->getwidth() - right_width);
1034 /* there is no centered string anymore */
1035 center_width = 0;
1037 /* there is no right string, move center to right */
1038 if ((center_width != 0 && right_width == 0) &&
1039 (center_xpos + center_width > right_xpos)) {
1040 /* move the center string to the right string */
1041 format_align->right = format_align->center;
1042 /* calculate the new width and position of the string */
1043 right_width = center_width;
1044 right_xpos = (display->getwidth() - right_width);
1045 /* there is no centered string anymore */
1046 center_width = 0;
1049 /* CASE 3: left and right overlap
1050 There is no center string anymore, either there never
1051 was one or it has been merged in case 1 or 2 */
1052 /* there is a left string, need to merge left and right */
1053 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1054 (left_xpos + left_width + space_width > right_xpos)) {
1055 /* replace the former separator '\0' of left and
1056 right string with a space */
1057 *(--format_align->right) = ' ';
1058 /* calculate the new width and position of the string */
1059 left_width = left_width + space_width + right_width;
1060 /* there is no right string anymore */
1061 right_width = 0;
1063 /* there is no left string, move right to left */
1064 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1065 (left_width > right_xpos)) {
1066 /* move the right string to the left string */
1067 format_align->left = format_align->right;
1068 /* calculate the new width and position of the string */
1069 left_width = right_width;
1070 /* there is no right string anymore */
1071 right_width = 0;
1074 ypos = (line * string_height);
1077 if (scroll && ((left_width > scroll_width) ||
1078 (center_width > scroll_width) ||
1079 (right_width > scroll_width)))
1081 display->puts_scroll(0, line,
1082 (unsigned char *)format_align->left);
1084 else
1086 #ifdef HAVE_LCD_BITMAP
1087 /* clear the line first */
1088 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1089 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1090 display->set_drawmode(DRMODE_SOLID);
1091 #endif
1093 /* Nasty hack: we output an empty scrolling string,
1094 which will reset the scroller for that line */
1095 display->puts_scroll(0, line, (unsigned char *)"");
1097 /* print aligned strings */
1098 if (left_width != 0)
1100 display->putsxy(left_xpos, ypos,
1101 (unsigned char *)format_align->left);
1103 if (center_width != 0)
1105 display->putsxy(center_xpos, ypos,
1106 (unsigned char *)format_align->center);
1108 if (right_width != 0)
1110 display->putsxy(right_xpos, ypos,
1111 (unsigned char *)format_align->right);
1116 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode)
1118 struct wps_data *data = gwps->data;
1119 struct screen *display = gwps->display;
1121 if (!data || !display || !gwps->state)
1122 return false;
1124 unsigned flags;
1125 char linebuf[MAX_PATH];
1127 struct align_pos align;
1128 align.left = NULL;
1129 align.center = NULL;
1130 align.right = NULL;
1133 struct skin_token_list *viewport_list;
1135 bool update_line, new_subline_refresh;
1137 /* reset to first subline if refresh all flag is set */
1138 if (refresh_mode == WPS_REFRESH_ALL)
1140 struct skin_line *line;
1141 struct skin_viewport *skin_viewport = find_viewport(VP_DEFAULT_LABEL, data);
1143 if (!(skin_viewport->hidden_flags & VP_NEVER_VISIBLE))
1145 display->set_viewport(&skin_viewport->vp);
1146 display->clear_viewport();
1149 for (viewport_list = data->viewports;
1150 viewport_list; viewport_list = viewport_list->next)
1152 skin_viewport =
1153 (struct skin_viewport *)viewport_list->token->value.data;
1154 for(line = skin_viewport->lines; line; line = line->next)
1156 line->curr_subline = NULL;
1161 #ifdef HAVE_LCD_CHARCELLS
1162 int i;
1163 for (i = 0; i < 8; i++)
1165 if (data->wps_progress_pat[i] == 0)
1166 data->wps_progress_pat[i] = display->get_locked_pattern();
1168 #endif
1170 /* disable any viewports which are conditionally displayed.
1171 * If we are only refreshing the peak meter then don't change the viewport
1172 * enabled flags as this will stop scrolling. viewports cant be
1173 * toggled in this refresh mode anyway (FS#10215)*/
1174 if (refresh_mode != WPS_REFRESH_PEAK_METER)
1176 for (viewport_list = data->viewports;
1177 viewport_list; viewport_list = viewport_list->next)
1179 struct skin_viewport *skin_viewport =
1180 (struct skin_viewport *)viewport_list->token->value.data;
1181 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1183 continue;
1185 if (skin_viewport->hidden_flags&VP_DRAW_HIDEABLE)
1187 if (skin_viewport->hidden_flags&VP_DRAW_HIDDEN)
1188 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1189 else
1190 skin_viewport->hidden_flags |= VP_DRAW_HIDDEN;
1194 int viewport_count = 0;
1195 for (viewport_list = data->viewports;
1196 viewport_list; viewport_list = viewport_list->next, viewport_count++)
1198 struct skin_viewport *skin_viewport =
1199 (struct skin_viewport *)viewport_list->token->value.data;
1200 unsigned vp_refresh_mode = refresh_mode;
1202 display->set_viewport(&skin_viewport->vp);
1204 int hidden_vp = 0;
1206 #ifdef HAVE_LCD_BITMAP
1207 /* Set images to not to be displayed */
1208 struct skin_token_list *imglist = data->images;
1209 while (imglist)
1211 struct gui_img *img = (struct gui_img *)imglist->token->value.data;
1212 img->display = -1;
1213 imglist = imglist->next;
1215 #endif
1216 /* dont redraw the viewport if its disabled */
1217 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1218 { /* don't draw anything into this one */
1219 vp_refresh_mode = 0; hidden_vp = true;
1221 else if ((skin_viewport->hidden_flags&VP_DRAW_HIDDEN))
1223 if (!(skin_viewport->hidden_flags&VP_DRAW_WASHIDDEN))
1224 display->scroll_stop(&skin_viewport->vp);
1225 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1226 continue;
1228 else if (((skin_viewport->hidden_flags&
1229 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1230 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1232 vp_refresh_mode = WPS_REFRESH_ALL;
1233 skin_viewport->hidden_flags = VP_DRAW_HIDEABLE;
1236 if (vp_refresh_mode == WPS_REFRESH_ALL)
1238 display->clear_viewport();
1241 /* loop over the lines for this viewport */
1242 struct skin_line *line;
1243 int line_count = 0;
1245 for (line = skin_viewport->lines; line; line = line->next, line_count++)
1247 struct skin_subline *subline;
1248 memset(linebuf, 0, sizeof(linebuf));
1249 update_line = false;
1251 /* get current subline for the line */
1252 new_subline_refresh = update_curr_subline(gwps, line);
1253 subline = line->curr_subline;
1254 flags = line->curr_subline->line_type;
1256 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1257 || new_subline_refresh || hidden_vp)
1259 /* get_line tells us if we need to update the line */
1260 update_line = get_line(gwps, subline, &align,
1261 linebuf, sizeof(linebuf), vp_refresh_mode);
1263 #ifdef HAVE_LCD_BITMAP
1264 /* peakmeter */
1265 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1267 if (!data->peak_meter_enabled)
1269 peak_meter_enable(false);
1271 else
1273 /* the peakmeter should be alone on its line */
1274 update_line = false;
1276 int h = font_get(skin_viewport->vp.font)->height;
1277 int peak_meter_y = line_count* h;
1279 /* The user might decide to have the peak meter in the last
1280 line so that it is only displayed if no status bar is
1281 visible. If so we neither want do draw nor enable the
1282 peak meter. */
1283 if (peak_meter_y + h <= skin_viewport->vp.y+skin_viewport->vp.height) {
1284 peak_meter_enable(true);
1285 peak_meter_screen(gwps->display, 0, peak_meter_y,
1286 MIN(h, skin_viewport->vp.y+skin_viewport->vp.height - peak_meter_y));
1291 #else /* HAVE_LCD_CHARCELL */
1293 /* progressbar */
1294 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1296 if (data->full_line_progressbar)
1297 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1298 else
1299 draw_player_progress(gwps);
1301 #endif
1303 if (update_line && !hidden_vp &&
1304 /* conditionals clear the line which means if the %Vd is put into the default
1305 viewport there will be a blank line.
1306 To get around this we dont allow any actual drawing to happen in the
1307 deault vp if other vp's are defined */
1308 ((skin_viewport->label != VP_DEFAULT_LABEL && viewport_list->next) ||
1309 !viewport_list->next))
1311 if (flags & WPS_REFRESH_SCROLL)
1313 /* if the line is a scrolling one we don't want to update
1314 too often, so that it has the time to scroll */
1315 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1316 write_line(display, &align, line_count, true);
1318 else
1319 write_line(display, &align, line_count, false);
1322 #ifdef HAVE_LCD_BITMAP
1323 /* progressbar */
1324 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1326 struct skin_token_list *bar = gwps->data->progressbars;
1327 while (bar)
1329 struct progressbar *thisbar = (struct progressbar*)bar->token->value.data;
1330 if (thisbar->vp == &skin_viewport->vp && thisbar->draw)
1332 draw_progressbar(gwps, thisbar);
1334 bar = bar->next;
1337 /* Now display any images in this viewport */
1338 if (!hidden_vp)
1339 wps_display_images(gwps, &skin_viewport->vp);
1340 #endif
1343 /* Restore the default viewport */
1344 display->set_viewport(NULL);
1346 display->update();
1348 return true;
1351 bool skin_has_sbs(enum screen_type screen, struct wps_data *data)
1353 (void)screen;
1354 (void)data;
1355 bool draw = false;
1356 #ifdef HAVE_LCD_BITMAP
1357 if (data->wps_sb_tag)
1358 draw = data->show_sb_on_wps;
1359 else if (statusbar_position(screen) != STATUSBAR_OFF)
1360 draw = true;
1361 #endif
1362 return draw;
1365 /* do the button loop as often as required for the peak meters to update
1366 * with a good refresh rate.
1367 * gwps is really gwps[NB_SCREENS]! don't wrap this if FOR_NB_SCREENS()
1369 int skin_wait_for_action(struct gui_wps *gwps, int context, int timeout)
1371 (void)gwps; /* silence charcell warning */
1372 int button = ACTION_NONE;
1373 #ifdef HAVE_LCD_BITMAP
1374 int i;
1375 /* when the peak meter is enabled we want to have a
1376 few extra updates to make it look smooth. On the
1377 other hand we don't want to waste energy if it
1378 isn't displayed */
1379 bool pm=false;
1380 FOR_NB_SCREENS(i)
1382 if(gwps[i].data->peak_meter_enabled)
1383 pm = true;
1386 if (pm) {
1387 long next_refresh = current_tick;
1388 long next_big_refresh = current_tick + timeout;
1389 button = BUTTON_NONE;
1390 while (TIME_BEFORE(current_tick, next_big_refresh)) {
1391 button = get_action(context,TIMEOUT_NOBLOCK);
1392 if (button != ACTION_NONE) {
1393 break;
1395 peak_meter_peek();
1396 sleep(0); /* Sleep until end of current tick. */
1398 if (TIME_AFTER(current_tick, next_refresh)) {
1399 FOR_NB_SCREENS(i)
1401 if(gwps[i].data->peak_meter_enabled)
1402 skin_update(&gwps[i], WPS_REFRESH_PEAK_METER);
1403 next_refresh += HZ / PEAK_METER_FPS;
1410 /* The peak meter is disabled
1411 -> no additional screen updates needed */
1412 else
1413 #endif
1415 button = get_action(context, timeout);
1417 return button;