skin_engine: Allow the %St() (setting) skin tag be used as a bar
[maemo-rb.git] / apps / gui / skin_engine / skin_display.c
blob4f491dea24700ecca474b2c372eaf0db5574829e
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 "core_alloc.h"
26 #include "misc.h"
27 #include "font.h"
28 #include "system.h"
29 #include "rbunicode.h"
30 #include "sound.h"
31 #include "powermgmt.h"
32 #ifdef DEBUG
33 #include "debug.h"
34 #endif
35 #include "action.h"
36 #include "abrepeat.h"
37 #include "lang.h"
38 #include "language.h"
39 #include "statusbar.h"
40 #include "settings.h"
41 #include "scrollbar.h"
42 #include "screen_access.h"
43 #include "playlist.h"
44 #include "audio.h"
45 #include "tagcache.h"
46 #include "list.h"
47 #include "option_select.h"
49 #ifdef HAVE_LCD_BITMAP
50 #include "peakmeter.h"
51 /* Image stuff */
52 #include "bmp.h"
53 #ifdef HAVE_ALBUMART
54 #include "albumart.h"
55 #endif
56 #endif
58 #include "cuesheet.h"
59 #if CONFIG_CODEC == SWCODEC
60 #include "playback.h"
61 #endif
62 #include "backdrop.h"
63 #include "viewport.h"
64 #if CONFIG_TUNER
65 #include "radio.h"
66 #include "tuner.h"
67 #endif
68 #include "root_menu.h"
71 #include "wps_internals.h"
72 #include "skin_engine.h"
73 #include "statusbar-skinned.h"
74 #include "skin_display.h"
76 void skin_render(struct gui_wps *gwps, unsigned refresh_mode);
78 /* update a skinned screen, update_type is WPS_REFRESH_* values.
79 * Usually it should only be WPS_REFRESH_NON_STATIC
80 * A full update will be done if required (skin_do_full_update() == true)
82 void skin_update(enum skinnable_screens skin, enum screen_type screen,
83 unsigned int update_type)
85 struct gui_wps *gwps = skin_get_gwps(skin, screen);
86 /* This maybe shouldnt be here,
87 * This is also safe for skined screen which dont use the id3 */
88 struct mp3entry *id3 = skin_get_global_state()->id3;
89 bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
90 if (cuesheet_update)
91 skin_request_full_update(skin);
93 skin_render(gwps, skin_do_full_update(skin, screen) ?
94 SKIN_REFRESH_ALL : update_type);
97 #ifdef HAVE_LCD_BITMAP
99 void draw_progressbar(struct gui_wps *gwps, int line, struct progressbar *pb)
101 struct screen *display = gwps->display;
102 struct viewport *vp = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->vp);
103 struct wps_state *state = skin_get_global_state();
104 struct mp3entry *id3 = state->id3;
105 int x = pb->x, y = pb->y, width = pb->width, height = pb->height;
106 unsigned long length, end;
107 int flags = HORIZONTAL;
109 if (height < 0)
110 height = font_get(vp->font)->height;
112 if (y < 0)
114 int line_height = font_get(vp->font)->height;
115 /* center the pb in the line, but only if the line is higher than the pb */
116 int center = (line_height-height)/2;
117 /* if Y was not set calculate by font height,Y is -line_number-1 */
118 y = line*line_height + (0 > center ? 0 : center);
121 if (pb->type == SKIN_TOKEN_VOLUMEBAR)
123 int minvol = sound_min(SOUND_VOLUME);
124 int maxvol = sound_max(SOUND_VOLUME);
125 length = maxvol-minvol;
126 end = global_settings.volume-minvol;
128 else if (pb->type == SKIN_TOKEN_BATTERY_PERCENTBAR)
130 length = 100;
131 end = battery_level();
133 else if (pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR ||
134 pb->type == SKIN_TOKEN_PEAKMETER_RIGHTBAR)
136 int left, right, val;
137 peak_meter_current_vals(&left, &right);
138 val = pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR ? left : right;
139 length = MAX_PEAK;
140 end = peak_meter_scale_value(val, length);
142 else if (pb->type == SKIN_TOKEN_LIST_SCROLLBAR)
144 int val, min, max;
145 skinlist_get_scrollbar(&val, &min, &max);
146 end = val - min;
147 length = max - min;
149 else if (pb->type == SKIN_TOKEN_SETTINGBAR)
151 int val, count;
152 get_setting_info_for_bar(pb->setting_id, &count, &val);
153 length = count - 1;
154 end = val;
156 #if CONFIG_TUNER
157 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
159 #ifdef HAVE_RADIO_RSSI
160 if (pb->type == SKIN_TOKEN_TUNER_RSSI_BAR)
162 int val = tuner_get(RADIO_RSSI);
163 int min = tuner_get(RADIO_RSSI_MIN);
164 int max = tuner_get(RADIO_RSSI_MAX);
165 end = val - min;
166 length = max - min;
168 else
169 #endif
171 int min = fm_region_data[global_settings.fm_region].freq_min;
172 end = radio_current_frequency() - min;
173 length = fm_region_data[global_settings.fm_region].freq_max - min;
176 #endif
177 else if (id3 && id3->length)
179 length = id3->length;
180 end = id3->elapsed + state->ff_rewind_count;
182 else
184 length = 1;
185 end = 0;
188 if (!pb->horizontal)
190 /* we want to fill upwards which is technically inverted. */
191 flags = INVERTFILL;
194 if (pb->invert_fill_direction)
196 flags ^= INVERTFILL;
199 if (pb->nofill)
201 flags |= INNER_NOFILL;
204 if (SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider))
206 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider);
207 /* clear the slider */
208 screen_clear_area(display, x, y, width, height);
210 /* shrink the bar so the slider is inside bounds */
211 if (flags&HORIZONTAL)
213 width -= img->bm.width;
214 x += img->bm.width / 2;
216 else
218 height -= img->bm.height;
219 y += img->bm.height / 2;
223 if (SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->backdrop))
225 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->backdrop);
226 img->bm.data = core_get_data(img->buflib_handle);
227 display->bmp_part(&img->bm, 0, 0, x, y, width, height);
228 flags |= DONT_CLEAR_EXCESS;
231 if (!pb->nobar)
233 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->image);
234 if (img)
236 char *img_data = core_get_data(img->buflib_handle);
237 img->bm.data = img_data;
238 gui_bitmap_scrollbar_draw(display, &img->bm,
239 x, y, width, height,
240 length, 0, end, flags);
242 else
243 gui_scrollbar_draw(display, x, y, width, height,
244 length, 0, end, flags);
247 if (SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider))
249 int xoff = 0, yoff = 0;
250 int w = width, h = height;
251 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider);
252 img->bm.data = core_get_data(img->buflib_handle);
254 if (flags&HORIZONTAL)
256 w = img->bm.width;
257 xoff = width * end / length;
258 if (flags&INVERTFILL)
259 xoff = width - xoff;
260 xoff -= w / 2;
262 else
264 h = img->bm.height;
265 yoff = height * end / length;
266 if (flags&INVERTFILL)
267 yoff = height - yoff;
268 yoff -= h / 2;
270 display->bmp_part(&img->bm, 0, 0, x + xoff, y + yoff, w, h);
273 if (pb->type == SKIN_TOKEN_PROGRESSBAR)
275 if (id3 && id3->length)
277 #ifdef AB_REPEAT_ENABLE
278 if (ab_repeat_mode_enabled())
279 ab_draw_markers(display, id3->length, x, y, width, height);
280 #endif
282 if (id3->cuesheet)
283 cue_draw_markers(display, id3->cuesheet, id3->length,
284 x, y+1, width, height-2);
286 #if 0 /* disable for now CONFIG_TUNER */
287 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
289 presets_draw_markers(display, x, y, width, height);
291 #endif
295 /* clears the area where the image was shown */
296 void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
298 if(!gwps)
299 return;
300 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
301 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
302 gwps->display->set_drawmode(DRMODE_SOLID);
305 void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
307 struct screen *display = gwps->display;
308 img->bm.data = core_get_data(img->buflib_handle);
309 display->set_drawmode(DRMODE_SOLID);
311 display->bmp_part(&img->bm, 0, img->subimage_height * subimage,
312 img->x, img->y, img->bm.width, img->subimage_height);
316 void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
318 if(!gwps || !gwps->data || !gwps->display)
319 return;
320 (void)vp;
321 struct wps_data *data = gwps->data;
322 struct screen *display = gwps->display;
323 struct skin_token_list *list = SKINOFFSETTOPTR(get_skin_buffer(data), data->images);
325 while (list)
327 struct wps_token *token = SKINOFFSETTOPTR(get_skin_buffer(data), list->token);
328 struct gui_img *img = (struct gui_img*)SKINOFFSETTOPTR(get_skin_buffer(data), token->value.data);
329 if (img->using_preloaded_icons && img->display >= 0)
331 screen_put_icon(display, img->x, img->y, img->display);
333 else if (img->loaded)
335 if (img->display >= 0)
337 wps_draw_image(gwps, img, img->display);
340 list = SKINOFFSETTOPTR(get_skin_buffer(data), list->next);
342 #ifdef HAVE_ALBUMART
343 /* now draw the AA */
344 struct skin_albumart *aa = SKINOFFSETTOPTR(get_skin_buffer(data), data->albumart);
345 if (aa && SKINOFFSETTOPTR(get_skin_buffer(data), aa->vp) == vp
346 && aa->draw_handle >= 0)
348 draw_album_art(gwps, aa->draw_handle, false);
349 aa->draw_handle = -1;
351 #endif
353 display->set_drawmode(DRMODE_SOLID);
356 #endif /* HAVE_LCD_BITMAP */
358 /* Evaluate the conditional that is at *token_index and return whether a skip
359 has ocurred. *token_index is updated with the new position.
361 int evaluate_conditional(struct gui_wps *gwps, int offset,
362 struct conditional *conditional, int num_options)
364 if (!gwps)
365 return false;
367 char result[128];
368 const char *value;
370 int intval = num_options < 2 ? 2 : num_options;
371 /* get_token_value needs to know the number of options in the enum */
372 value = get_token_value(gwps, SKINOFFSETTOPTR(get_skin_buffer(gwps->data), conditional->token),
373 offset, result, sizeof(result), &intval);
375 /* intval is now the number of the enum option we want to read,
376 starting from 1. If intval is -1, we check if value is empty. */
377 if (intval == -1)
379 if (num_options == 1) /* so %?AA<true> */
380 intval = (value && *value) ? 1 : 0; /* returned as 0 for true, -1 for false */
381 else
382 intval = (value && *value) ? 1 : num_options;
384 else if (intval > num_options || intval < 1)
385 intval = num_options;
387 return intval -1;
391 /* Display a line appropriately according to its alignment format.
392 format_align contains the text, separated between left, center and right.
393 line is the index of the line on the screen.
394 scroll indicates whether the line is a scrolling one or not.
396 void write_line(struct screen *display, struct align_pos *format_align,
397 int line, bool scroll, unsigned style)
399 #ifndef HAVE_LCD_BITMAP
400 (void)style;
401 #endif
402 int left_width = 0;
403 int center_width = 0, center_xpos;
404 int right_width = 0, right_xpos;
405 int space_width;
406 int string_height;
407 int scroll_width;
408 int viewport_width = display->getwidth();
410 /* calculate different string sizes and positions */
411 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
412 if (format_align->left != 0) {
413 display->getstringsize((unsigned char *)format_align->left,
414 &left_width, &string_height);
417 if (format_align->right != 0) {
418 display->getstringsize((unsigned char *)format_align->right,
419 &right_width, &string_height);
422 if (format_align->center != 0) {
423 display->getstringsize((unsigned char *)format_align->center,
424 &center_width, &string_height);
427 right_xpos = (viewport_width - right_width);
428 center_xpos = (viewport_width - center_width) / 2;
430 scroll_width = viewport_width;
432 /* Checks for overlapping strings.
433 If needed the overlapping strings will be merged, separated by a
434 space */
436 /* CASE 1: left and centered string overlap */
437 /* there is a left string, need to merge left and center */
438 if ((left_width != 0 && center_width != 0) &&
439 (left_width + space_width > center_xpos)) {
440 /* replace the former separator '\0' of left and
441 center string with a space */
442 *(--format_align->center) = ' ';
443 /* calculate the new width and position of the merged string */
444 left_width = left_width + space_width + center_width;
445 /* there is no centered string anymore */
446 center_width = 0;
448 /* there is no left string, move center to left */
449 if ((left_width == 0 && center_width != 0) &&
450 (left_width > center_xpos)) {
451 /* move the center string to the left string */
452 format_align->left = format_align->center;
453 /* calculate the new width and position of the string */
454 left_width = center_width;
455 /* there is no centered string anymore */
456 center_width = 0;
459 /* CASE 2: centered and right string overlap */
460 /* there is a right string, need to merge center and right */
461 if ((center_width != 0 && right_width != 0) &&
462 (center_xpos + center_width + space_width > right_xpos)) {
463 /* replace the former separator '\0' of center and
464 right string with a space */
465 *(--format_align->right) = ' ';
466 /* move the center string to the right after merge */
467 format_align->right = format_align->center;
468 /* calculate the new width and position of the merged string */
469 right_width = center_width + space_width + right_width;
470 right_xpos = (viewport_width - right_width);
471 /* there is no centered string anymore */
472 center_width = 0;
474 /* there is no right string, move center to right */
475 if ((center_width != 0 && right_width == 0) &&
476 (center_xpos + center_width > right_xpos)) {
477 /* move the center string to the right string */
478 format_align->right = format_align->center;
479 /* calculate the new width and position of the string */
480 right_width = center_width;
481 right_xpos = (viewport_width - right_width);
482 /* there is no centered string anymore */
483 center_width = 0;
486 /* CASE 3: left and right overlap
487 There is no center string anymore, either there never
488 was one or it has been merged in case 1 or 2 */
489 /* there is a left string, need to merge left and right */
490 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
491 (left_width + space_width > right_xpos)) {
492 /* replace the former separator '\0' of left and
493 right string with a space */
494 *(--format_align->right) = ' ';
495 /* calculate the new width and position of the string */
496 left_width = left_width + space_width + right_width;
497 /* there is no right string anymore */
498 right_width = 0;
500 /* there is no left string, move right to left */
501 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
502 (left_width > right_xpos)) {
503 /* move the right string to the left string */
504 format_align->left = format_align->right;
505 /* calculate the new width and position of the string */
506 left_width = right_width;
507 /* there is no right string anymore */
508 right_width = 0;
511 if (scroll && ((left_width > scroll_width) ||
512 (center_width > scroll_width) ||
513 (right_width > scroll_width)))
515 #ifdef HAVE_LCD_BITMAP
516 display->puts_scroll_style(0, line,
517 (unsigned char *)format_align->left, style);
518 #else
519 display->puts_scroll(0, line,
520 (unsigned char *)format_align->left);
521 #endif
523 else
525 #ifdef HAVE_LCD_BITMAP
526 /* clear the line first */
527 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
528 display->fillrect(0, line*string_height, viewport_width, string_height);
529 display->set_drawmode(DRMODE_SOLID);
530 #endif
532 /* Nasty hack: we output an empty scrolling string,
533 which will reset the scroller for that line */
534 display->puts_scroll(0, line, (unsigned char *)"");
535 #ifdef HAVE_LCD_BITMAP
536 style |= STYLE_XY_PIXELS;
537 line *= string_height;
538 /* print aligned strings */
539 if (left_width != 0)
541 display->puts_style_xyoffset(0, line,
542 (unsigned char *)format_align->left, style, 0, 0);
545 if (center_width != 0)
547 display->puts_style_xyoffset((viewport_width-center_width)/2, line,
548 (unsigned char *)format_align->center, style, 0, 0);
550 if (right_width != 0)
552 display->puts_style_xyoffset(viewport_width-right_width, line,
553 (unsigned char *)format_align->right, style, 0, 0);
555 #else
556 if (left_width != 0)
558 display->putsxy(0, line,
559 (unsigned char *)format_align->left);
561 if (center_width != 0)
563 display->putsxy(center_xpos, line,
564 (unsigned char *)format_align->center);
566 if (right_width != 0)
568 display->putsxy(right_xpos, line,
569 (unsigned char *)format_align->right);
571 #endif
575 #ifdef HAVE_LCD_BITMAP
576 void draw_peakmeters(struct gui_wps *gwps, int line_number,
577 struct viewport *viewport)
579 struct wps_data *data = gwps->data;
580 if (!data->peak_meter_enabled)
582 peak_meter_enable(false);
584 else
586 int h = font_get(viewport->font)->height;
587 int peak_meter_y = line_number * h;
589 /* The user might decide to have the peak meter in the last
590 line so that it is only displayed if no status bar is
591 visible. If so we neither want do draw nor enable the
592 peak meter. */
593 if (peak_meter_y + h <= viewport->y+viewport->height) {
594 peak_meter_enable(true);
595 peak_meter_screen(gwps->display, 0, peak_meter_y,
596 MIN(h, viewport->y+viewport->height - peak_meter_y));
601 bool skin_has_sbs(enum screen_type screen, struct wps_data *data)
603 (void)screen;
604 (void)data;
605 bool draw = false;
606 #ifdef HAVE_LCD_BITMAP
607 if (data->wps_sb_tag)
608 draw = data->show_sb_on_wps;
609 else if (statusbar_position(screen) != STATUSBAR_OFF)
610 draw = true;
611 #endif
612 return draw;
614 #endif
616 /* do the button loop as often as required for the peak meters to update
617 * with a good refresh rate.
619 int skin_wait_for_action(enum skinnable_screens skin, int context, int timeout)
621 (void)skin; /* silence charcell warning */
622 int button = ACTION_NONE;
623 #ifdef HAVE_LCD_BITMAP
624 /* when the peak meter is enabled we want to have a
625 few extra updates to make it look smooth. On the
626 other hand we don't want to waste energy if it
627 isn't displayed */
628 bool pm=false;
629 FOR_NB_SCREENS(i)
631 if(skin_get_gwps(skin, i)->data->peak_meter_enabled)
632 pm = true;
635 if (pm) {
636 long next_refresh = current_tick;
637 long next_big_refresh = current_tick + timeout;
638 button = BUTTON_NONE;
639 while (TIME_BEFORE(current_tick, next_big_refresh)) {
640 button = get_action(context,TIMEOUT_NOBLOCK);
641 if (button != ACTION_NONE) {
642 break;
644 peak_meter_peek();
645 sleep(0); /* Sleep until end of current tick. */
647 if (TIME_AFTER(current_tick, next_refresh)) {
648 FOR_NB_SCREENS(i)
650 if(skin_get_gwps(skin, i)->data->peak_meter_enabled)
651 skin_update(skin, i, SKIN_REFRESH_PEAK_METER);
652 next_refresh += HZ / PEAK_METER_FPS;
659 /* The peak meter is disabled
660 -> no additional screen updates needed */
661 else
662 #endif
664 button = get_action(context, timeout);
666 return button;