Fix oops. Too late now.
[maemo-rb.git] / apps / gui / skin_engine / skin_display.c
blob0613f986b33fd8f32012bf5587642f3fafea6cd8
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"
48 #ifdef HAVE_LCD_BITMAP
49 #include "peakmeter.h"
50 /* Image stuff */
51 #include "bmp.h"
52 #ifdef HAVE_ALBUMART
53 #include "albumart.h"
54 #endif
55 #endif
57 #include "cuesheet.h"
58 #if CONFIG_CODEC == SWCODEC
59 #include "playback.h"
60 #endif
61 #include "backdrop.h"
62 #include "viewport.h"
63 #if CONFIG_TUNER
64 #include "radio.h"
65 #include "tuner.h"
66 #endif
67 #include "root_menu.h"
70 #include "wps_internals.h"
71 #include "skin_engine.h"
72 #include "statusbar-skinned.h"
73 #include "skin_display.h"
75 void skin_render(struct gui_wps *gwps, unsigned refresh_mode);
77 /* update a skinned screen, update_type is WPS_REFRESH_* values.
78 * Usually it should only be WPS_REFRESH_NON_STATIC
79 * A full update will be done if required (skin_do_full_update() == true)
81 void skin_update(enum skinnable_screens skin, enum screen_type screen,
82 unsigned int update_type)
84 struct gui_wps *gwps = skin_get_gwps(skin, screen);
85 /* This maybe shouldnt be here,
86 * This is also safe for skined screen which dont use the id3 */
87 struct mp3entry *id3 = skin_get_global_state()->id3;
88 bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
89 if (cuesheet_update)
90 skin_request_full_update(skin);
92 skin_render(gwps, skin_do_full_update(skin, screen) ?
93 SKIN_REFRESH_ALL : update_type);
96 #ifdef HAVE_LCD_BITMAP
98 void draw_progressbar(struct gui_wps *gwps, int line, struct progressbar *pb)
100 struct screen *display = gwps->display;
101 struct viewport *vp = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->vp);
102 struct wps_state *state = skin_get_global_state();
103 struct mp3entry *id3 = state->id3;
104 int x = pb->x, y = pb->y, width = pb->width, height = pb->height;
105 unsigned long length, end;
106 int flags = HORIZONTAL;
108 if (height < 0)
109 height = font_get(vp->font)->height;
111 if (y < 0)
113 int line_height = font_get(vp->font)->height;
114 /* center the pb in the line, but only if the line is higher than the pb */
115 int center = (line_height-height)/2;
116 /* if Y was not set calculate by font height,Y is -line_number-1 */
117 y = line*line_height + (0 > center ? 0 : center);
120 if (pb->type == SKIN_TOKEN_VOLUMEBAR)
122 int minvol = sound_min(SOUND_VOLUME);
123 int maxvol = sound_max(SOUND_VOLUME);
124 length = maxvol-minvol;
125 end = global_settings.volume-minvol;
127 else if (pb->type == SKIN_TOKEN_BATTERY_PERCENTBAR)
129 length = 100;
130 end = battery_level();
132 else if (pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR ||
133 pb->type == SKIN_TOKEN_PEAKMETER_RIGHTBAR)
135 int left, right, val;
136 peak_meter_current_vals(&left, &right);
137 val = pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR ? left : right;
138 length = MAX_PEAK;
139 end = peak_meter_scale_value(val, length);
141 else if (pb->type == SKIN_TOKEN_LIST_SCROLLBAR)
143 int val, min, max;
144 skinlist_get_scrollbar(&val, &min, &max);
145 end = val - min;
146 length = max - min;
148 #if CONFIG_TUNER
149 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
151 #ifdef HAVE_RADIO_RSSI
152 if (pb->type == SKIN_TOKEN_TUNER_RSSI_BAR)
154 int val = tuner_get(RADIO_RSSI);
155 int min = tuner_get(RADIO_RSSI_MIN);
156 int max = tuner_get(RADIO_RSSI_MAX);
157 end = val - min;
158 length = max - min;
160 else
161 #endif
163 int min = fm_region_data[global_settings.fm_region].freq_min;
164 end = radio_current_frequency() - min;
165 length = fm_region_data[global_settings.fm_region].freq_max - min;
168 #endif
169 else if (id3 && id3->length)
171 length = id3->length;
172 end = id3->elapsed + state->ff_rewind_count;
174 else
176 length = 1;
177 end = 0;
180 if (!pb->horizontal)
182 /* we want to fill upwards which is technically inverted. */
183 flags = INVERTFILL;
186 if (pb->invert_fill_direction)
188 flags ^= INVERTFILL;
191 if (pb->nofill)
193 flags |= INNER_NOFILL;
196 if (SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider))
198 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider);
199 /* clear the slider */
200 screen_clear_area(display, x, y, width, height);
202 /* shrink the bar so the slider is inside bounds */
203 if (flags&HORIZONTAL)
205 width -= img->bm.width;
206 x += img->bm.width / 2;
208 else
210 height -= img->bm.height;
211 y += img->bm.height / 2;
215 if (SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->backdrop))
217 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->backdrop);
218 img->bm.data = core_get_data(img->buflib_handle);
219 display->bmp_part(&img->bm, 0, 0, x, y, width, height);
220 flags |= DONT_CLEAR_EXCESS;
223 if (!pb->nobar)
225 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->image);
226 if (img)
228 char *img_data = core_get_data(img->buflib_handle);
229 img->bm.data = img_data;
230 gui_bitmap_scrollbar_draw(display, &img->bm,
231 x, y, width, height,
232 length, 0, end, flags);
234 else
235 gui_scrollbar_draw(display, x, y, width, height,
236 length, 0, end, flags);
239 if (SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider))
241 int xoff = 0, yoff = 0;
242 int w = width, h = height;
243 struct gui_img *img = SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider);
244 img->bm.data = core_get_data(img->buflib_handle);
246 if (flags&HORIZONTAL)
248 w = img->bm.width;
249 xoff = width * end / length;
250 if (flags&INVERTFILL)
251 xoff = width - xoff;
252 xoff -= w / 2;
254 else
256 h = img->bm.height;
257 yoff = height * end / length;
258 if (flags&INVERTFILL)
259 yoff = height - yoff;
260 yoff -= h / 2;
262 display->bmp_part(&img->bm, 0, 0, x + xoff, y + yoff, w, h);
265 if (pb->type == SKIN_TOKEN_PROGRESSBAR)
267 if (id3 && id3->length)
269 #ifdef AB_REPEAT_ENABLE
270 if (ab_repeat_mode_enabled())
271 ab_draw_markers(display, id3->length, x, y, width, height);
272 #endif
274 if (id3->cuesheet)
275 cue_draw_markers(display, id3->cuesheet, id3->length,
276 x, y+1, width, height-2);
278 #if 0 /* disable for now CONFIG_TUNER */
279 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
281 presets_draw_markers(display, x, y, width, height);
283 #endif
287 /* clears the area where the image was shown */
288 void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
290 if(!gwps)
291 return;
292 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
293 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
294 gwps->display->set_drawmode(DRMODE_SOLID);
297 void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
299 struct screen *display = gwps->display;
300 img->bm.data = core_get_data(img->buflib_handle);
301 if(img->always_display)
302 display->set_drawmode(DRMODE_FG);
303 else
304 display->set_drawmode(DRMODE_SOLID);
306 display->bmp_part(&img->bm, 0, img->subimage_height * subimage,
307 img->x, img->y, img->bm.width, img->subimage_height);
311 void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
313 if(!gwps || !gwps->data || !gwps->display)
314 return;
316 struct wps_data *data = gwps->data;
317 struct screen *display = gwps->display;
318 struct skin_token_list *list = SKINOFFSETTOPTR(get_skin_buffer(data), data->images);
320 while (list)
322 struct wps_token *token = SKINOFFSETTOPTR(get_skin_buffer(data), list->token);
323 struct gui_img *img = (struct gui_img*)SKINOFFSETTOPTR(get_skin_buffer(data), token->value.data);
324 if (img->using_preloaded_icons && img->display >= 0)
326 screen_put_icon(display, img->x, img->y, img->display);
328 else if (img->loaded)
330 if (img->display >= 0)
332 wps_draw_image(gwps, img, img->display);
334 else if (img->always_display && SKINOFFSETTOPTR(get_skin_buffer(data), img->vp) == vp)
336 wps_draw_image(gwps, img, 0);
339 list = SKINOFFSETTOPTR(get_skin_buffer(data), list->next);
341 #ifdef HAVE_ALBUMART
342 /* now draw the AA */
343 struct skin_albumart *aa = SKINOFFSETTOPTR(get_skin_buffer(data), data->albumart);
344 if (aa && SKINOFFSETTOPTR(get_skin_buffer(data), aa->vp) == vp
345 && aa->draw_handle >= 0)
347 draw_album_art(gwps, aa->draw_handle, false);
348 aa->draw_handle = -1;
350 #endif
352 display->set_drawmode(DRMODE_SOLID);
355 #endif /* HAVE_LCD_BITMAP */
357 /* Evaluate the conditional that is at *token_index and return whether a skip
358 has ocurred. *token_index is updated with the new position.
360 int evaluate_conditional(struct gui_wps *gwps, int offset,
361 struct conditional *conditional, int num_options)
363 if (!gwps)
364 return false;
366 char result[128];
367 const char *value;
369 int intval = num_options < 2 ? 2 : num_options;
370 /* get_token_value needs to know the number of options in the enum */
371 value = get_token_value(gwps, SKINOFFSETTOPTR(get_skin_buffer(gwps->data), conditional->token),
372 offset, result, sizeof(result), &intval);
374 /* intval is now the number of the enum option we want to read,
375 starting from 1. If intval is -1, we check if value is empty. */
376 if (intval == -1)
378 if (num_options == 1) /* so %?AA<true> */
379 intval = (value && *value) ? 1 : 0; /* returned as 0 for true, -1 for false */
380 else
381 intval = (value && *value) ? 1 : num_options;
383 else if (intval > num_options || intval < 1)
384 intval = num_options;
386 return intval -1;
390 /* Display a line appropriately according to its alignment format.
391 format_align contains the text, separated between left, center and right.
392 line is the index of the line on the screen.
393 scroll indicates whether the line is a scrolling one or not.
395 void write_line(struct screen *display, struct align_pos *format_align,
396 int line, bool scroll, unsigned style)
398 #ifndef HAVE_LCD_BITMAP
399 (void)style;
400 #endif
401 int left_width = 0;
402 int center_width = 0, center_xpos;
403 int right_width = 0, right_xpos;
404 int space_width;
405 int string_height;
406 int scroll_width;
407 int viewport_width = display->getwidth();
409 /* calculate different string sizes and positions */
410 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
411 if (format_align->left != 0) {
412 display->getstringsize((unsigned char *)format_align->left,
413 &left_width, &string_height);
416 if (format_align->right != 0) {
417 display->getstringsize((unsigned char *)format_align->right,
418 &right_width, &string_height);
421 if (format_align->center != 0) {
422 display->getstringsize((unsigned char *)format_align->center,
423 &center_width, &string_height);
426 right_xpos = (viewport_width - right_width);
427 center_xpos = (viewport_width - center_width) / 2;
429 scroll_width = viewport_width;
431 /* Checks for overlapping strings.
432 If needed the overlapping strings will be merged, separated by a
433 space */
435 /* CASE 1: left and centered string overlap */
436 /* there is a left string, need to merge left and center */
437 if ((left_width != 0 && center_width != 0) &&
438 (left_width + space_width > center_xpos)) {
439 /* replace the former separator '\0' of left and
440 center string with a space */
441 *(--format_align->center) = ' ';
442 /* calculate the new width and position of the merged string */
443 left_width = left_width + space_width + center_width;
444 /* there is no centered string anymore */
445 center_width = 0;
447 /* there is no left string, move center to left */
448 if ((left_width == 0 && center_width != 0) &&
449 (left_width > center_xpos)) {
450 /* move the center string to the left string */
451 format_align->left = format_align->center;
452 /* calculate the new width and position of the string */
453 left_width = center_width;
454 /* there is no centered string anymore */
455 center_width = 0;
458 /* CASE 2: centered and right string overlap */
459 /* there is a right string, need to merge center and right */
460 if ((center_width != 0 && right_width != 0) &&
461 (center_xpos + center_width + space_width > right_xpos)) {
462 /* replace the former separator '\0' of center and
463 right string with a space */
464 *(--format_align->right) = ' ';
465 /* move the center string to the right after merge */
466 format_align->right = format_align->center;
467 /* calculate the new width and position of the merged string */
468 right_width = center_width + space_width + right_width;
469 right_xpos = (viewport_width - right_width);
470 /* there is no centered string anymore */
471 center_width = 0;
473 /* there is no right string, move center to right */
474 if ((center_width != 0 && right_width == 0) &&
475 (center_xpos + center_width > right_xpos)) {
476 /* move the center string to the right string */
477 format_align->right = format_align->center;
478 /* calculate the new width and position of the string */
479 right_width = center_width;
480 right_xpos = (viewport_width - right_width);
481 /* there is no centered string anymore */
482 center_width = 0;
485 /* CASE 3: left and right overlap
486 There is no center string anymore, either there never
487 was one or it has been merged in case 1 or 2 */
488 /* there is a left string, need to merge left and right */
489 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
490 (left_width + space_width > right_xpos)) {
491 /* replace the former separator '\0' of left and
492 right string with a space */
493 *(--format_align->right) = ' ';
494 /* calculate the new width and position of the string */
495 left_width = left_width + space_width + right_width;
496 /* there is no right string anymore */
497 right_width = 0;
499 /* there is no left string, move right to left */
500 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
501 (left_width > right_xpos)) {
502 /* move the right string to the left string */
503 format_align->left = format_align->right;
504 /* calculate the new width and position of the string */
505 left_width = right_width;
506 /* there is no right string anymore */
507 right_width = 0;
510 if (scroll && ((left_width > scroll_width) ||
511 (center_width > scroll_width) ||
512 (right_width > scroll_width)))
514 #ifdef HAVE_LCD_BITMAP
515 display->puts_scroll_style(0, line,
516 (unsigned char *)format_align->left, style);
517 #else
518 display->puts_scroll(0, line,
519 (unsigned char *)format_align->left);
520 #endif
522 else
524 #ifdef HAVE_LCD_BITMAP
525 /* clear the line first */
526 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
527 display->fillrect(0, line*string_height, viewport_width, string_height);
528 display->set_drawmode(DRMODE_SOLID);
529 #endif
531 /* Nasty hack: we output an empty scrolling string,
532 which will reset the scroller for that line */
533 display->puts_scroll(0, line, (unsigned char *)"");
534 #ifdef HAVE_LCD_BITMAP
535 style |= STYLE_XY_PIXELS;
536 line *= string_height;
537 /* print aligned strings */
538 if (left_width != 0)
540 display->puts_style_xyoffset(0, line,
541 (unsigned char *)format_align->left, style, 0, 0);
544 if (center_width != 0)
546 display->puts_style_xyoffset((viewport_width-center_width)/2, line,
547 (unsigned char *)format_align->center, style, 0, 0);
549 if (right_width != 0)
551 display->puts_style_xyoffset(viewport_width-right_width, line,
552 (unsigned char *)format_align->right, style, 0, 0);
554 #else
555 if (left_width != 0)
557 display->putsxy(0, line,
558 (unsigned char *)format_align->left);
560 if (center_width != 0)
562 display->putsxy(center_xpos, line,
563 (unsigned char *)format_align->center);
565 if (right_width != 0)
567 display->putsxy(right_xpos, line,
568 (unsigned char *)format_align->right);
570 #endif
574 #ifdef HAVE_LCD_BITMAP
575 void draw_peakmeters(struct gui_wps *gwps, int line_number,
576 struct viewport *viewport)
578 struct wps_data *data = gwps->data;
579 if (!data->peak_meter_enabled)
581 peak_meter_enable(false);
583 else
585 int h = font_get(viewport->font)->height;
586 int peak_meter_y = line_number * h;
588 /* The user might decide to have the peak meter in the last
589 line so that it is only displayed if no status bar is
590 visible. If so we neither want do draw nor enable the
591 peak meter. */
592 if (peak_meter_y + h <= viewport->y+viewport->height) {
593 peak_meter_enable(true);
594 peak_meter_screen(gwps->display, 0, peak_meter_y,
595 MIN(h, viewport->y+viewport->height - peak_meter_y));
600 bool skin_has_sbs(enum screen_type screen, struct wps_data *data)
602 (void)screen;
603 (void)data;
604 bool draw = false;
605 #ifdef HAVE_LCD_BITMAP
606 if (data->wps_sb_tag)
607 draw = data->show_sb_on_wps;
608 else if (statusbar_position(screen) != STATUSBAR_OFF)
609 draw = true;
610 #endif
611 return draw;
613 #endif
615 /* do the button loop as often as required for the peak meters to update
616 * with a good refresh rate.
618 int skin_wait_for_action(enum skinnable_screens skin, int context, int timeout)
620 (void)skin; /* silence charcell warning */
621 int button = ACTION_NONE;
622 #ifdef HAVE_LCD_BITMAP
623 /* when the peak meter is enabled we want to have a
624 few extra updates to make it look smooth. On the
625 other hand we don't want to waste energy if it
626 isn't displayed */
627 bool pm=false;
628 FOR_NB_SCREENS(i)
630 if(skin_get_gwps(skin, i)->data->peak_meter_enabled)
631 pm = true;
634 if (pm) {
635 long next_refresh = current_tick;
636 long next_big_refresh = current_tick + timeout;
637 button = BUTTON_NONE;
638 while (TIME_BEFORE(current_tick, next_big_refresh)) {
639 button = get_action(context,TIMEOUT_NOBLOCK);
640 if (button != ACTION_NONE) {
641 break;
643 peak_meter_peek();
644 sleep(0); /* Sleep until end of current tick. */
646 if (TIME_AFTER(current_tick, next_refresh)) {
647 FOR_NB_SCREENS(i)
649 if(skin_get_gwps(skin, i)->data->peak_meter_enabled)
650 skin_update(skin, i, SKIN_REFRESH_PEAK_METER);
651 next_refresh += HZ / PEAK_METER_FPS;
658 /* The peak meter is disabled
659 -> no additional screen updates needed */
660 else
661 #endif
663 button = get_action(context, timeout);
665 return button;