pass pointers around instead of coping whole structs
[maemo-rb.git] / apps / gui / skin_engine / skin_display.c
blob08f363d1651504b17ec7c8d662fe116ab3420529
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 && 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, max;
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 start_item = cur_pos + viewer->start_offset;
222 max = start_item+radio_preset_count();
224 else
225 #endif
227 cur_pos = playlist_get_display_index();
228 max = playlist_amount()+1;
229 start_item = MAX(0, cur_pos + viewer->start_offset);
232 gwps->display->set_viewport(viewer->vp);
233 for(i=start_item; (i-start_item)<lines && i<max; i++)
235 int line;
236 #if CONFIG_TUNER
237 if (current_screen() == GO_TO_FM)
239 pid3 = NULL;
240 line = TRACK_HAS_INFO;
241 filename = "";
243 else
244 #endif
246 filename = playlist_peek(i-cur_pos);
247 if (i == cur_pos)
249 pid3 = state->id3;
251 else if (i == cur_pos+1)
253 pid3 = state->nid3;
255 #if CONFIG_CODEC == SWCODEC
256 else if (i>cur_pos)
258 #ifdef HAVE_TC_RAMCACHE
259 if (tagcache_fill_tags(&viewer->tempid3, filename))
261 pid3 = &viewer->tempid3;
263 else
264 #endif
265 if (!audio_peek_track(&pid3, i-cur_pos))
266 pid3 = NULL;
268 #endif
269 else
271 pid3 = NULL;
273 line = pid3 ? TRACK_HAS_INFO : TRACK_HAS_NO_INFO;
275 int j = 0, cur_string = 0;
276 unsigned int line_len = 0;
277 buf[0] = '\0';
278 while (j < viewer->lines[line].count && line_len < sizeof(buf))
280 const char *out = NULL;
281 token.type = viewer->lines[line].tokens[j];
282 token.value.i = 0;
283 token.next = false;
284 out = get_id3_token(&token, pid3, tempbuf, sizeof(tempbuf), -1, NULL);
285 #if CONFIG_TUNER
286 if (!out)
287 out = get_radio_token(&token, i-cur_pos,
288 tempbuf, sizeof(tempbuf), -1, NULL);
289 #endif
290 if (out)
292 line_len = strlcat(buf, out, sizeof(buf));
293 j++;
294 continue;
297 switch (viewer->lines[line].tokens[j])
299 case WPS_TOKEN_ALIGN_CENTER:
300 case WPS_TOKEN_ALIGN_LEFT:
301 case WPS_TOKEN_ALIGN_LEFT_RTL:
302 case WPS_TOKEN_ALIGN_RIGHT:
303 case WPS_TOKEN_ALIGN_RIGHT_RTL:
304 alignment = viewer->lines[line].tokens[j];
305 tempbuf[0] = '\0';
306 break;
307 case WPS_TOKEN_STRING:
308 case WPS_TOKEN_CHARACTER:
309 snprintf(tempbuf, sizeof(tempbuf), "%s",
310 viewer->lines[line].strings[cur_string]);
311 cur_string++;
312 break;
313 case WPS_TOKEN_PLAYLIST_POSITION:
314 snprintf(tempbuf, sizeof(tempbuf), "%d", i);
315 break;
316 case WPS_TOKEN_FILE_NAME:
317 get_dir(tempbuf, sizeof(tempbuf), filename, 0);
318 break;
319 case WPS_TOKEN_FILE_PATH:
320 snprintf(tempbuf, sizeof(tempbuf), "%s", filename);
321 break;
322 default:
323 tempbuf[0] = '\0';
324 break;
326 if (tempbuf[0])
328 line_len = strlcat(buf, tempbuf, sizeof(buf));
330 j++;
333 int vpwidth = viewer->vp->width;
334 length = gwps->display->getstringsize(buf, NULL, NULL);
335 if (viewer->lines[line].scroll && length >= vpwidth)
337 gwps->display->puts_scroll(0, (i-start_item), buf );
339 else
341 if (length >= vpwidth)
342 x = 0;
343 else
345 switch (alignment)
347 case WPS_TOKEN_ALIGN_CENTER:
348 x = (vpwidth-length)/2;
349 break;
350 case WPS_TOKEN_ALIGN_LEFT_RTL:
351 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
353 x = vpwidth - length;
354 break;
356 case WPS_TOKEN_ALIGN_LEFT:
357 x = 0;
358 break;
359 case WPS_TOKEN_ALIGN_RIGHT_RTL:
360 if (lang_is_rtl() && VP_IS_RTL(viewer->vp))
362 x = 0;
363 break;
365 case WPS_TOKEN_ALIGN_RIGHT:
366 x = vpwidth - length;
367 break;
368 default:
369 x = 0;
370 break;
373 gwps->display->putsxy(x, (i-start_item)*line_height, buf );
379 /* clears the area where the image was shown */
380 static void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
382 if(!gwps)
383 return;
384 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
385 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
386 gwps->display->set_drawmode(DRMODE_SOLID);
389 static void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
391 struct screen *display = gwps->display;
392 if(img->always_display)
393 display->set_drawmode(DRMODE_FG);
394 else
395 display->set_drawmode(DRMODE_SOLID);
397 #if LCD_DEPTH > 1
398 if(img->bm.format == FORMAT_MONO) {
399 #endif
400 display->mono_bitmap_part(img->bm.data,
401 0, img->subimage_height * subimage,
402 img->bm.width, img->x,
403 img->y, img->bm.width,
404 img->subimage_height);
405 #if LCD_DEPTH > 1
406 } else {
407 display->transparent_bitmap_part((fb_data *)img->bm.data,
408 0, img->subimage_height * subimage,
409 STRIDE(display->screen_type,
410 img->bm.width, img->bm.height),
411 img->x, img->y, img->bm.width,
412 img->subimage_height);
414 #endif
417 static void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
419 if(!gwps || !gwps->data || !gwps->display)
420 return;
422 struct wps_data *data = gwps->data;
423 struct screen *display = gwps->display;
424 struct skin_token_list *list = data->images;
426 while (list)
428 struct gui_img *img = (struct gui_img*)list->token->value.data;
429 if (img->loaded)
431 if (img->display >= 0)
433 wps_draw_image(gwps, img, img->display);
435 else if (img->always_display && img->vp == vp)
437 wps_draw_image(gwps, img, 0);
440 list = list->next;
442 #ifdef HAVE_ALBUMART
443 /* now draw the AA */
444 if (data->albumart && data->albumart->vp == vp
445 && data->albumart->draw)
447 int handle = playback_current_aa_hid(data->playback_aa_slot);
448 #if CONFIG_TUNER
449 if (in_radio_screen())
451 struct dim dim = {data->albumart->width, data->albumart->height};
452 handle = radio_get_art_hid(&dim);
454 #endif
455 draw_album_art(gwps, handle, false);
456 data->albumart->draw = false;
458 #endif
460 display->set_drawmode(DRMODE_SOLID);
463 #else /* HAVE_LCD_CHARCELL */
465 static bool draw_player_progress(struct gui_wps *gwps)
467 struct wps_state *state = gwps->state;
468 struct screen *display = gwps->display;
469 unsigned char progress_pattern[7];
470 int pos = 0;
471 int i;
473 int elapsed, length;
474 if (LIKELY(state->id3))
476 elapsed = state->id3->elapsed;
477 length = state->id3->length;
479 else
481 elapsed = 0;
482 length = 0;
485 if (length)
486 pos = 36 * (elapsed + state->ff_rewind_count) / length;
488 for (i = 0; i < 7; i++, pos -= 5)
490 if (pos <= 0)
491 progress_pattern[i] = 0x1fu;
492 else if (pos >= 5)
493 progress_pattern[i] = 0x00u;
494 else
495 progress_pattern[i] = 0x1fu >> pos;
498 display->define_pattern(gwps->data->wps_progress_pat[0], progress_pattern);
499 return true;
502 static void draw_player_fullbar(struct gui_wps *gwps, char* buf, int buf_size)
504 static const unsigned char numbers[10][4] = {
505 {0x0e, 0x0a, 0x0a, 0x0e}, /* 0 */
506 {0x04, 0x0c, 0x04, 0x04}, /* 1 */
507 {0x0e, 0x02, 0x04, 0x0e}, /* 2 */
508 {0x0e, 0x02, 0x06, 0x0e}, /* 3 */
509 {0x08, 0x0c, 0x0e, 0x04}, /* 4 */
510 {0x0e, 0x0c, 0x02, 0x0c}, /* 5 */
511 {0x0e, 0x08, 0x0e, 0x0e}, /* 6 */
512 {0x0e, 0x02, 0x04, 0x08}, /* 7 */
513 {0x0e, 0x0e, 0x0a, 0x0e}, /* 8 */
514 {0x0e, 0x0e, 0x02, 0x0e}, /* 9 */
517 struct wps_state *state = gwps->state;
518 struct screen *display = gwps->display;
519 struct wps_data *data = gwps->data;
520 unsigned char progress_pattern[7];
521 char timestr[10];
522 int time;
523 int time_idx = 0;
524 int pos = 0;
525 int pat_idx = 1;
526 int digit, i, j;
527 bool softchar;
529 int elapsed, length;
530 if (LIKELY(state->id3))
532 elapsed = state->id3->elapsed;
533 length = state->id3->length;
535 else
537 elapsed = 0;
538 length = 0;
541 if (buf_size < 34) /* worst case: 11x UTF-8 char + \0 */
542 return;
544 time = elapsed + state->ff_rewind_count;
545 if (length)
546 pos = 55 * time / length;
548 memset(timestr, 0, sizeof(timestr));
549 format_time(timestr, sizeof(timestr)-2, time);
550 timestr[strlen(timestr)] = ':'; /* always safe */
552 for (i = 0; i < 11; i++, pos -= 5)
554 softchar = false;
555 memset(progress_pattern, 0, sizeof(progress_pattern));
557 if ((digit = timestr[time_idx]))
559 softchar = true;
560 digit -= '0';
562 if (timestr[time_idx + 1] == ':') /* ones, left aligned */
564 memcpy(progress_pattern, numbers[digit], 4);
565 time_idx += 2;
567 else /* tens, shifted right */
569 for (j = 0; j < 4; j++)
570 progress_pattern[j] = numbers[digit][j] >> 1;
572 if (time_idx > 0) /* not the first group, add colon in front */
574 progress_pattern[1] |= 0x10u;
575 progress_pattern[3] |= 0x10u;
577 time_idx++;
580 if (pos >= 5)
581 progress_pattern[5] = progress_pattern[6] = 0x1fu;
584 if (pos > 0 && pos < 5)
586 softchar = true;
587 progress_pattern[5] = progress_pattern[6] = (~0x1fu >> pos) & 0x1fu;
590 if (softchar && pat_idx < 8)
592 display->define_pattern(data->wps_progress_pat[pat_idx],
593 progress_pattern);
594 buf = utf8encode(data->wps_progress_pat[pat_idx], buf);
595 pat_idx++;
597 else if (pos <= 0)
598 buf = utf8encode(' ', buf);
599 else
600 buf = utf8encode(0xe115, buf); /* 2/7 _ */
602 *buf = '\0';
605 #endif /* HAVE_LCD_CHARCELL */
607 /* Return the index to the end token for the conditional token at index.
608 The conditional token can be either a start token or a separator
609 (i.e. option) token.
611 static int find_conditional_end(struct wps_data *data, int index)
613 int ret = index;
614 while (data->tokens[ret].type != WPS_TOKEN_CONDITIONAL_END)
615 ret = data->tokens[ret].value.i;
617 /* ret now is the index to the end token for the conditional. */
618 return ret;
621 /* Evaluate the conditional that is at *token_index and return whether a skip
622 has ocurred. *token_index is updated with the new position.
624 static bool evaluate_conditional(struct gui_wps *gwps, int *token_index)
626 if (!gwps)
627 return false;
629 struct wps_data *data = gwps->data;
631 int i, cond_end;
632 int cond_index = *token_index;
633 char result[128];
634 const char *value;
635 unsigned char num_options = data->tokens[cond_index].value.i & 0xFF;
636 unsigned char prev_val = (data->tokens[cond_index].value.i & 0xFF00) >> 8;
638 /* treat ?xx<true> constructs as if they had 2 options. */
639 if (num_options < 2)
640 num_options = 2;
642 int intval = num_options;
643 /* get_token_value needs to know the number of options in the enum */
644 value = get_token_value(gwps, &data->tokens[cond_index + 1],
645 result, sizeof(result), &intval);
647 /* intval is now the number of the enum option we want to read,
648 starting from 1. If intval is -1, we check if value is empty. */
649 if (intval == -1)
650 intval = (value && *value) ? 1 : num_options;
651 else if (intval > num_options || intval < 1)
652 intval = num_options;
654 data->tokens[cond_index].value.i = (intval << 8) + num_options;
656 /* skip to the appropriate enum case */
657 int next = cond_index + 2;
658 for (i = 1; i < intval; i++)
660 next = data->tokens[next].value.i;
662 *token_index = next;
664 if (prev_val == intval)
666 /* Same conditional case as previously. Return without clearing the
667 pictures */
668 return false;
671 cond_end = find_conditional_end(data, cond_index + 2);
672 for (i = cond_index + 3; i < cond_end; i++)
674 #ifdef HAVE_LCD_BITMAP
675 /* clear all pictures in the conditional and nested ones */
676 if (data->tokens[i].type == WPS_TOKEN_IMAGE_PRELOAD_DISPLAY)
677 clear_image_pos(gwps, find_image(data->tokens[i].value.i&0xFF, data));
678 else if (data->tokens[i].type == WPS_TOKEN_VOLUMEBAR ||
679 data->tokens[i].type == WPS_TOKEN_PROGRESSBAR ||
680 data->tokens[i].type == WPS_TOKEN_BATTERY_PERCENTBAR )
682 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
683 bar->draw = false;
685 else if (data->tokens[i].type == WPS_TOKEN_PEAKMETER)
687 data->peak_meter_enabled = false;
689 #endif
690 #ifdef HAVE_ALBUMART
691 if (data->albumart && data->tokens[i].type == WPS_TOKEN_ALBUMART_DISPLAY)
693 draw_album_art(gwps,
694 playback_current_aa_hid(data->playback_aa_slot), true);
695 data->albumart->draw = false;
697 #endif
700 return true;
704 /* Read a (sub)line to the given alignment format buffer.
705 linebuf is the buffer where the data is actually stored.
706 align is the alignment format that'll be used to display the text.
707 The return value indicates whether the line needs to be updated.
709 static bool get_line(struct gui_wps *gwps,
710 struct skin_subline *subline,
711 struct align_pos *align,
712 char *linebuf,
713 int linebuf_size,
714 unsigned refresh_mode)
716 struct wps_data *data = gwps->data;
718 char temp_buf[128];
719 char *buf = linebuf; /* will always point to the writing position */
720 char *linebuf_end = linebuf + linebuf_size - 1;
721 bool update = false;
722 int i;
723 (void)refresh_mode; /* silence warning on charcell */
725 /* alignment-related variables */
726 int cur_align;
727 char* cur_align_start;
728 cur_align_start = buf;
729 cur_align = WPS_ALIGN_LEFT;
730 align->left = NULL;
731 align->center = NULL;
732 align->right = NULL;
733 /* Process all tokens of the desired subline */
734 for (i = subline->first_token_idx;
735 i <= subline->last_token_idx; i++)
737 switch(data->tokens[i].type)
739 case WPS_TOKEN_CONDITIONAL:
740 /* place ourselves in the right conditional case */
741 update |= evaluate_conditional(gwps, &i);
742 break;
744 case WPS_TOKEN_CONDITIONAL_OPTION:
745 /* we've finished in the curent conditional case,
746 skip to the end of the conditional structure */
747 i = find_conditional_end(data, i);
748 break;
750 #ifdef HAVE_LCD_BITMAP
751 case WPS_TOKEN_PEAKMETER:
752 data->peak_meter_enabled = true;
753 break;
754 case WPS_TOKEN_VOLUMEBAR:
755 case WPS_TOKEN_BATTERY_PERCENTBAR:
756 case WPS_TOKEN_PROGRESSBAR:
758 struct progressbar *bar = (struct progressbar*)data->tokens[i].value.data;
759 bar->draw = true;
761 break;
762 case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
764 char n = data->tokens[i].value.i & 0xFF;
765 int subimage = data->tokens[i].value.i >> 8;
766 struct gui_img *img = find_image(n, data);
768 if (img && img->loaded)
769 img->display = subimage;
770 break;
772 case WPS_TOKEN_DRAW_INBUILTBAR:
773 gui_statusbar_draw(&(statusbars.statusbars[gwps->display->screen_type]),
774 refresh_mode == WPS_REFRESH_ALL,
775 data->tokens[i].value.data);
776 break;
777 #endif
779 case WPS_TOKEN_ALIGN_LEFT:
780 case WPS_TOKEN_ALIGN_LEFT_RTL:
781 case WPS_TOKEN_ALIGN_CENTER:
782 case WPS_TOKEN_ALIGN_RIGHT:
783 case WPS_TOKEN_ALIGN_RIGHT_RTL:
784 /* remember where the current aligned text started */
785 switch (cur_align)
787 case WPS_ALIGN_LEFT:
788 align->left = cur_align_start;
789 break;
791 case WPS_ALIGN_CENTER:
792 align->center = cur_align_start;
793 break;
795 case WPS_ALIGN_RIGHT:
796 align->right = cur_align_start;
797 break;
799 /* start a new alignment */
800 switch (data->tokens[i].type)
802 case WPS_TOKEN_ALIGN_LEFT:
803 cur_align = WPS_ALIGN_LEFT;
804 break;
805 case WPS_TOKEN_ALIGN_LEFT_RTL:
806 cur_align = lang_is_rtl() ? WPS_ALIGN_RIGHT :
807 WPS_ALIGN_LEFT;
808 break;
809 case WPS_TOKEN_ALIGN_CENTER:
810 cur_align = WPS_ALIGN_CENTER;
811 break;
812 case WPS_TOKEN_ALIGN_RIGHT:
813 cur_align = WPS_ALIGN_RIGHT;
814 break;
815 case WPS_TOKEN_ALIGN_RIGHT_RTL:
816 cur_align = lang_is_rtl() ? WPS_ALIGN_LEFT :
817 WPS_ALIGN_RIGHT;
818 break;
819 default:
820 break;
822 *buf++ = 0;
823 cur_align_start = buf;
824 break;
825 case WPS_VIEWPORT_ENABLE:
827 char label = data->tokens[i].value.i;
828 char temp = VP_DRAW_HIDEABLE;
829 /* viewports are allowed to share id's so find and enable
830 * all of them */
831 struct skin_token_list *list = data->viewports;
832 while (list)
834 struct skin_viewport *vp =
835 (struct skin_viewport *)list->token->value.data;
836 if (vp->label == label)
838 if (vp->hidden_flags&VP_DRAW_WASHIDDEN)
839 temp |= VP_DRAW_WASHIDDEN;
840 vp->hidden_flags = temp;
842 list = list->next;
845 break;
846 #ifdef HAVE_LCD_BITMAP
847 case WPS_TOKEN_UIVIEWPORT_ENABLE:
848 sb_set_info_vp(gwps->display->screen_type,
849 data->tokens[i].value.i|VP_INFO_LABEL);
850 break;
851 case WPS_VIEWPORT_CUSTOMLIST:
852 draw_playlist_viewer_list(gwps, data->tokens[i].value.data);
853 break;
854 #endif
855 default:
857 /* get the value of the tag and copy it to the buffer */
858 const char *value = get_token_value(gwps, &data->tokens[i],
859 temp_buf, sizeof(temp_buf), NULL);
860 if (value)
862 update = true;
863 while (*value && (buf < linebuf_end))
864 *buf++ = *value++;
866 break;
871 /* close the current alignment */
872 switch (cur_align)
874 case WPS_ALIGN_LEFT:
875 align->left = cur_align_start;
876 break;
878 case WPS_ALIGN_CENTER:
879 align->center = cur_align_start;
880 break;
882 case WPS_ALIGN_RIGHT:
883 align->right = cur_align_start;
884 break;
887 return update;
889 static void get_subline_timeout(struct gui_wps *gwps, struct skin_subline *subline)
891 struct wps_data *data = gwps->data;
892 int i;
893 subline->time_mult = DEFAULT_SUBLINE_TIME_MULTIPLIER;
895 for (i = subline->first_token_idx;
896 i <= subline->last_token_idx; i++)
898 switch(data->tokens[i].type)
900 case WPS_TOKEN_CONDITIONAL:
901 /* place ourselves in the right conditional case */
902 evaluate_conditional(gwps, &i);
903 break;
905 case WPS_TOKEN_CONDITIONAL_OPTION:
906 /* we've finished in the curent conditional case,
907 skip to the end of the conditional structure */
908 i = find_conditional_end(data, i);
909 break;
911 case WPS_TOKEN_SUBLINE_TIMEOUT:
912 subline->time_mult = data->tokens[i].value.i;
913 break;
915 default:
916 break;
921 /* Calculates which subline should be displayed for the specified line
922 Returns true iff the subline must be refreshed */
923 static bool update_curr_subline(struct gui_wps *gwps, struct skin_line *line)
925 /* shortcut this whole thing if we need to reset the line completly */
926 if (line->curr_subline == NULL)
928 line->subline_expire_time = current_tick;
929 line->curr_subline = &line->sublines;
930 if (!line->curr_subline->next)
932 line->subline_expire_time += 100*HZ;
934 else
936 get_subline_timeout(gwps, line->curr_subline);
937 line->subline_expire_time += TIMEOUT_UNIT*line->curr_subline->time_mult;
939 return true;
941 /* if time to advance to next sub-line */
942 if (TIME_AFTER(current_tick, line->subline_expire_time - 1))
944 /* if there is only one subline, there is no need to search for a new one */
945 if (&line->sublines == line->curr_subline &&
946 line->curr_subline->next == NULL)
948 line->subline_expire_time += 100 * HZ;
949 return false;
951 if (line->curr_subline->next)
952 line->curr_subline = line->curr_subline->next;
953 else
954 line->curr_subline = &line->sublines;
955 get_subline_timeout(gwps, line->curr_subline);
956 line->subline_expire_time = current_tick + TIMEOUT_UNIT*line->curr_subline->time_mult;
957 return true;
959 return false;
962 /* Display a line appropriately according to its alignment format.
963 format_align contains the text, separated between left, center and right.
964 line is the index of the line on the screen.
965 scroll indicates whether the line is a scrolling one or not.
967 static void write_line(struct screen *display,
968 struct align_pos *format_align,
969 int line,
970 bool scroll)
972 int left_width = 0, left_xpos;
973 int center_width = 0, center_xpos;
974 int right_width = 0, right_xpos;
975 int ypos;
976 int space_width;
977 int string_height;
978 int scroll_width;
980 /* calculate different string sizes and positions */
981 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
982 if (format_align->left != 0) {
983 display->getstringsize((unsigned char *)format_align->left,
984 &left_width, &string_height);
987 if (format_align->right != 0) {
988 display->getstringsize((unsigned char *)format_align->right,
989 &right_width, &string_height);
992 if (format_align->center != 0) {
993 display->getstringsize((unsigned char *)format_align->center,
994 &center_width, &string_height);
997 left_xpos = 0;
998 right_xpos = (display->getwidth() - right_width);
999 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
1001 scroll_width = display->getwidth() - left_xpos;
1003 /* Checks for overlapping strings.
1004 If needed the overlapping strings will be merged, separated by a
1005 space */
1007 /* CASE 1: left and centered string overlap */
1008 /* there is a left string, need to merge left and center */
1009 if ((left_width != 0 && center_width != 0) &&
1010 (left_xpos + left_width + space_width > center_xpos)) {
1011 /* replace the former separator '\0' of left and
1012 center string with a space */
1013 *(--format_align->center) = ' ';
1014 /* calculate the new width and position of the merged string */
1015 left_width = left_width + space_width + center_width;
1016 /* there is no centered string anymore */
1017 center_width = 0;
1019 /* there is no left string, move center to left */
1020 if ((left_width == 0 && center_width != 0) &&
1021 (left_xpos + left_width > center_xpos)) {
1022 /* move the center string to the left string */
1023 format_align->left = format_align->center;
1024 /* calculate the new width and position of the string */
1025 left_width = center_width;
1026 /* there is no centered string anymore */
1027 center_width = 0;
1030 /* CASE 2: centered and right string overlap */
1031 /* there is a right string, need to merge center and right */
1032 if ((center_width != 0 && right_width != 0) &&
1033 (center_xpos + center_width + space_width > right_xpos)) {
1034 /* replace the former separator '\0' of center and
1035 right string with a space */
1036 *(--format_align->right) = ' ';
1037 /* move the center string to the right after merge */
1038 format_align->right = format_align->center;
1039 /* calculate the new width and position of the merged string */
1040 right_width = center_width + space_width + right_width;
1041 right_xpos = (display->getwidth() - right_width);
1042 /* there is no centered string anymore */
1043 center_width = 0;
1045 /* there is no right string, move center to right */
1046 if ((center_width != 0 && right_width == 0) &&
1047 (center_xpos + center_width > right_xpos)) {
1048 /* move the center string to the right string */
1049 format_align->right = format_align->center;
1050 /* calculate the new width and position of the string */
1051 right_width = center_width;
1052 right_xpos = (display->getwidth() - right_width);
1053 /* there is no centered string anymore */
1054 center_width = 0;
1057 /* CASE 3: left and right overlap
1058 There is no center string anymore, either there never
1059 was one or it has been merged in case 1 or 2 */
1060 /* there is a left string, need to merge left and right */
1061 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
1062 (left_xpos + left_width + space_width > right_xpos)) {
1063 /* replace the former separator '\0' of left and
1064 right string with a space */
1065 *(--format_align->right) = ' ';
1066 /* calculate the new width and position of the string */
1067 left_width = left_width + space_width + right_width;
1068 /* there is no right string anymore */
1069 right_width = 0;
1071 /* there is no left string, move right to left */
1072 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
1073 (left_width > right_xpos)) {
1074 /* move the right string to the left string */
1075 format_align->left = format_align->right;
1076 /* calculate the new width and position of the string */
1077 left_width = right_width;
1078 /* there is no right string anymore */
1079 right_width = 0;
1082 ypos = (line * string_height);
1085 if (scroll && ((left_width > scroll_width) ||
1086 (center_width > scroll_width) ||
1087 (right_width > scroll_width)))
1089 display->puts_scroll(0, line,
1090 (unsigned char *)format_align->left);
1092 else
1094 #ifdef HAVE_LCD_BITMAP
1095 /* clear the line first */
1096 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
1097 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
1098 display->set_drawmode(DRMODE_SOLID);
1099 #endif
1101 /* Nasty hack: we output an empty scrolling string,
1102 which will reset the scroller for that line */
1103 display->puts_scroll(0, line, (unsigned char *)"");
1105 /* print aligned strings */
1106 if (left_width != 0)
1108 display->putsxy(left_xpos, ypos,
1109 (unsigned char *)format_align->left);
1111 if (center_width != 0)
1113 display->putsxy(center_xpos, ypos,
1114 (unsigned char *)format_align->center);
1116 if (right_width != 0)
1118 display->putsxy(right_xpos, ypos,
1119 (unsigned char *)format_align->right);
1124 static bool skin_redraw(struct gui_wps *gwps, unsigned refresh_mode)
1126 struct wps_data *data = gwps->data;
1127 struct screen *display = gwps->display;
1129 if (!data || !display || !gwps->state)
1130 return false;
1132 unsigned flags;
1133 char linebuf[MAX_PATH];
1135 struct align_pos align;
1136 align.left = NULL;
1137 align.center = NULL;
1138 align.right = NULL;
1141 struct skin_token_list *viewport_list;
1143 bool update_line, new_subline_refresh;
1145 /* reset to first subline if refresh all flag is set */
1146 if (refresh_mode == WPS_REFRESH_ALL)
1148 struct skin_line *line;
1149 struct skin_viewport *skin_viewport = find_viewport(VP_DEFAULT_LABEL, data);
1151 if (!(skin_viewport->hidden_flags & VP_NEVER_VISIBLE))
1153 display->set_viewport(&skin_viewport->vp);
1154 display->clear_viewport();
1157 for (viewport_list = data->viewports;
1158 viewport_list; viewport_list = viewport_list->next)
1160 skin_viewport =
1161 (struct skin_viewport *)viewport_list->token->value.data;
1162 for(line = skin_viewport->lines; line; line = line->next)
1164 line->curr_subline = NULL;
1169 #ifdef HAVE_LCD_CHARCELLS
1170 int i;
1171 for (i = 0; i < 8; i++)
1173 if (data->wps_progress_pat[i] == 0)
1174 data->wps_progress_pat[i] = display->get_locked_pattern();
1176 #endif
1178 /* disable any viewports which are conditionally displayed.
1179 * If we are only refreshing the peak meter then don't change the viewport
1180 * enabled flags as this will stop scrolling. viewports cant be
1181 * toggled in this refresh mode anyway (FS#10215)*/
1182 if (refresh_mode != WPS_REFRESH_PEAK_METER)
1184 for (viewport_list = data->viewports;
1185 viewport_list; viewport_list = viewport_list->next)
1187 struct skin_viewport *skin_viewport =
1188 (struct skin_viewport *)viewport_list->token->value.data;
1189 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1191 continue;
1193 if (skin_viewport->hidden_flags&VP_DRAW_HIDEABLE)
1195 if (skin_viewport->hidden_flags&VP_DRAW_HIDDEN)
1196 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1197 else
1198 skin_viewport->hidden_flags |= VP_DRAW_HIDDEN;
1202 int viewport_count = 0;
1203 for (viewport_list = data->viewports;
1204 viewport_list; viewport_list = viewport_list->next, viewport_count++)
1206 struct skin_viewport *skin_viewport =
1207 (struct skin_viewport *)viewport_list->token->value.data;
1208 unsigned vp_refresh_mode = refresh_mode;
1210 display->set_viewport(&skin_viewport->vp);
1212 int hidden_vp = 0;
1214 #ifdef HAVE_LCD_BITMAP
1215 /* Set images to not to be displayed */
1216 struct skin_token_list *imglist = data->images;
1217 while (imglist)
1219 struct gui_img *img = (struct gui_img *)imglist->token->value.data;
1220 img->display = -1;
1221 imglist = imglist->next;
1223 #endif
1224 /* dont redraw the viewport if its disabled */
1225 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
1226 { /* don't draw anything into this one */
1227 vp_refresh_mode = 0; hidden_vp = true;
1229 else if ((skin_viewport->hidden_flags&VP_DRAW_HIDDEN))
1231 if (!(skin_viewport->hidden_flags&VP_DRAW_WASHIDDEN))
1232 display->scroll_stop(&skin_viewport->vp);
1233 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
1234 continue;
1236 else if (((skin_viewport->hidden_flags&
1237 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
1238 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
1240 vp_refresh_mode = WPS_REFRESH_ALL;
1241 skin_viewport->hidden_flags = VP_DRAW_HIDEABLE;
1244 if (vp_refresh_mode == WPS_REFRESH_ALL)
1246 display->clear_viewport();
1249 /* loop over the lines for this viewport */
1250 struct skin_line *line;
1251 int line_count = 0;
1253 for (line = skin_viewport->lines; line; line = line->next, line_count++)
1255 struct skin_subline *subline;
1256 memset(linebuf, 0, sizeof(linebuf));
1257 update_line = false;
1259 /* get current subline for the line */
1260 new_subline_refresh = update_curr_subline(gwps, line);
1261 subline = line->curr_subline;
1262 flags = line->curr_subline->line_type;
1264 if (vp_refresh_mode == WPS_REFRESH_ALL || (flags & vp_refresh_mode)
1265 || new_subline_refresh || hidden_vp)
1267 /* get_line tells us if we need to update the line */
1268 update_line = get_line(gwps, subline, &align,
1269 linebuf, sizeof(linebuf), vp_refresh_mode);
1271 #ifdef HAVE_LCD_BITMAP
1272 /* peakmeter */
1273 if (flags & vp_refresh_mode & WPS_REFRESH_PEAK_METER)
1275 if (!data->peak_meter_enabled)
1277 peak_meter_enable(false);
1279 else
1281 /* the peakmeter should be alone on its line */
1282 update_line = false;
1284 int h = font_get(skin_viewport->vp.font)->height;
1285 int peak_meter_y = line_count* h;
1287 /* The user might decide to have the peak meter in the last
1288 line so that it is only displayed if no status bar is
1289 visible. If so we neither want do draw nor enable the
1290 peak meter. */
1291 if (peak_meter_y + h <= skin_viewport->vp.y+skin_viewport->vp.height) {
1292 peak_meter_enable(true);
1293 peak_meter_screen(gwps->display, 0, peak_meter_y,
1294 MIN(h, skin_viewport->vp.y+skin_viewport->vp.height - peak_meter_y));
1299 #else /* HAVE_LCD_CHARCELL */
1301 /* progressbar */
1302 if (flags & vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1304 if (data->full_line_progressbar)
1305 draw_player_fullbar(gwps, linebuf, sizeof(linebuf));
1306 else
1307 draw_player_progress(gwps);
1309 #endif
1311 if (update_line && !hidden_vp &&
1312 /* conditionals clear the line which means if the %Vd is put into the default
1313 viewport there will be a blank line.
1314 To get around this we dont allow any actual drawing to happen in the
1315 deault vp if other vp's are defined */
1316 ((skin_viewport->label != VP_DEFAULT_LABEL && viewport_list->next) ||
1317 !viewport_list->next))
1319 if (flags & WPS_REFRESH_SCROLL)
1321 /* if the line is a scrolling one we don't want to update
1322 too often, so that it has the time to scroll */
1323 if ((vp_refresh_mode & WPS_REFRESH_SCROLL) || new_subline_refresh)
1324 write_line(display, &align, line_count, true);
1326 else
1327 write_line(display, &align, line_count, false);
1330 #ifdef HAVE_LCD_BITMAP
1331 /* progressbar */
1332 if (vp_refresh_mode & WPS_REFRESH_PLAYER_PROGRESS)
1334 struct skin_token_list *bar = gwps->data->progressbars;
1335 while (bar)
1337 struct progressbar *thisbar = (struct progressbar*)bar->token->value.data;
1338 if (thisbar->vp == &skin_viewport->vp && thisbar->draw)
1340 draw_progressbar(gwps, thisbar);
1342 bar = bar->next;
1345 /* Now display any images in this viewport */
1346 if (!hidden_vp)
1347 wps_display_images(gwps, &skin_viewport->vp);
1348 #endif
1351 /* Restore the default viewport */
1352 display->set_viewport(NULL);
1354 display->update();
1356 return true;
1359 bool skin_has_sbs(enum screen_type screen, struct wps_data *data)
1361 (void)screen;
1362 (void)data;
1363 bool draw = false;
1364 #ifdef HAVE_LCD_BITMAP
1365 if (data->wps_sb_tag)
1366 draw = data->show_sb_on_wps;
1367 else if (statusbar_position(screen) != STATUSBAR_OFF)
1368 draw = true;
1369 #endif
1370 return draw;
1373 /* do the button loop as often as required for the peak meters to update
1374 * with a good refresh rate.
1375 * gwps is really gwps[NB_SCREENS]! don't wrap this if FOR_NB_SCREENS()
1377 int skin_wait_for_action(struct gui_wps *gwps, int context, int timeout)
1379 (void)gwps; /* silence charcell warning */
1380 int button = ACTION_NONE;
1381 #ifdef HAVE_LCD_BITMAP
1382 int i;
1383 /* when the peak meter is enabled we want to have a
1384 few extra updates to make it look smooth. On the
1385 other hand we don't want to waste energy if it
1386 isn't displayed */
1387 bool pm=false;
1388 FOR_NB_SCREENS(i)
1390 if(gwps[i].data->peak_meter_enabled)
1391 pm = true;
1394 if (pm) {
1395 long next_refresh = current_tick;
1396 long next_big_refresh = current_tick + timeout;
1397 button = BUTTON_NONE;
1398 while (TIME_BEFORE(current_tick, next_big_refresh)) {
1399 button = get_action(context,TIMEOUT_NOBLOCK);
1400 if (button != ACTION_NONE) {
1401 break;
1403 peak_meter_peek();
1404 sleep(0); /* Sleep until end of current tick. */
1406 if (TIME_AFTER(current_tick, next_refresh)) {
1407 FOR_NB_SCREENS(i)
1409 if(gwps[i].data->peak_meter_enabled)
1410 skin_update(&gwps[i], WPS_REFRESH_PEAK_METER);
1411 next_refresh += HZ / PEAK_METER_FPS;
1418 /* The peak meter is disabled
1419 -> no additional screen updates needed */
1420 else
1421 #endif
1423 button = get_action(context, timeout);
1425 return button;