Make skin_render_playlistviewer() noinline. This function uses lots of stack (around...
[kugel-rb.git] / apps / gui / skin_engine / skin_render.c
blobbd86c0bdf8218a8952467d775899021372642a0f
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id: skin_parser.c 26752 2010-06-10 21:22:16Z bieber $
10 * Copyright (C) 2010 Jonathan Gordon
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdbool.h>
26 #include <ctype.h>
27 #include "strlcat.h"
29 #include "config.h"
30 #include "kernel.h"
31 #ifdef HAVE_ALBUMART
32 #include "albumart.h"
33 #endif
34 #include "skin_display.h"
35 #include "skin_engine.h"
36 #include "skin_parser.h"
37 #include "tag_table.h"
38 #include "skin_scan.h"
39 #if CONFIG_TUNER
40 #include "radio.h"
41 #endif
42 #include "viewport.h"
43 #include "cuesheet.h"
44 #include "language.h"
45 #include "playback.h"
46 #include "playlist.h"
47 #include "root_menu.h"
48 #include "misc.h"
51 #define MAX_LINE 1024
53 struct skin_draw_info {
54 struct gui_wps *gwps;
55 struct skin_viewport *skin_vp;
56 int line_number;
57 unsigned long refresh_type;
59 char* cur_align_start;
60 struct align_pos align;
61 bool no_line_break;
62 bool line_scrolls;
63 bool force_redraw;
65 char *buf;
66 size_t buf_size;
68 int offset; /* used by the playlist viewer */
71 typedef bool (*skin_render_func)(struct skin_element* alternator, struct skin_draw_info *info);
72 bool skin_render_alternator(struct skin_element* alternator, struct skin_draw_info *info);
74 #ifdef HAVE_LCD_BITMAP
75 static void skin_render_playlistviewer(struct playlistviewer* viewer,
76 struct gui_wps *gwps,
77 struct skin_viewport* skin_viewport,
78 unsigned long refresh_type);
79 #endif
81 static bool do_non_text_tags(struct gui_wps *gwps, struct skin_draw_info *info,
82 struct skin_element *element, struct viewport* vp)
84 #ifndef HAVE_LCD_BITMAP
85 (void)vp; /* silence warnings */
86 #endif
87 struct wps_token *token = (struct wps_token *)element->data;
88 struct wps_data *data = gwps->data;
89 bool do_refresh = (element->tag->flags & info->refresh_type) > 0;
90 switch (token->type)
92 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1))
93 case SKIN_TOKEN_VIEWPORT_FGCOLOUR:
95 struct viewport_colour *col = token->value.data;
96 col->vp->fg_pattern = col->colour;
98 break;
99 case SKIN_TOKEN_VIEWPORT_BGCOLOUR:
101 struct viewport_colour *col = token->value.data;
102 col->vp->bg_pattern = col->colour;
104 break;
105 #endif
106 case SKIN_TOKEN_VIEWPORT_ENABLE:
108 char *label = token->value.data;
109 char temp = VP_DRAW_HIDEABLE;
110 struct skin_element *viewport = gwps->data->tree;
111 while (viewport)
113 struct skin_viewport *skinvp = (struct skin_viewport*)viewport->data;
114 if (skinvp->label && !skinvp->is_infovp &&
115 !strcmp(skinvp->label, label))
117 if (skinvp->hidden_flags&VP_DRAW_HIDDEN)
119 temp |= VP_DRAW_WASHIDDEN;
121 skinvp->hidden_flags = temp;
123 viewport = viewport->next;
126 break;
127 #ifdef HAVE_LCD_BITMAP
128 case SKIN_TOKEN_UIVIEWPORT_ENABLE:
129 sb_set_info_vp(gwps->display->screen_type,
130 token->value.data);
131 break;
132 case SKIN_TOKEN_PEAKMETER:
133 data->peak_meter_enabled = true;
134 if (do_refresh)
135 draw_peakmeters(gwps, info->line_number, vp);
136 break;
137 #endif
138 case SKIN_TOKEN_VOLUMEBAR:
139 case SKIN_TOKEN_BATTERY_PERCENTBAR:
140 case SKIN_TOKEN_PROGRESSBAR:
142 #ifdef HAVE_LCD_BITMAP
143 struct progressbar *bar = (struct progressbar*)token->value.data;
144 if (do_refresh)
145 draw_progressbar(gwps, info->line_number, bar);
146 #else /* HAVE_LCD_CHARCELL */
147 if (do_refresh)
149 if (data->full_line_progressbar)
150 draw_player_fullbar(gwps, info->buf, info->buf_size);
151 else
152 draw_player_progress(gwps);
154 #endif
156 break;
157 #ifdef HAVE_LCD_BITMAP
158 case SKIN_TOKEN_IMAGE_DISPLAY_LISTICON:
159 case SKIN_TOKEN_IMAGE_PRELOAD_DISPLAY:
161 struct image_display *id = token->value.data;
162 const char* label = id->label;
163 struct gui_img *img = find_image(label, data);
164 if (img && img->loaded)
166 if (id->token == NULL)
168 img->display = id->subimage;
170 else
172 char buf[16];
173 const char *out;
174 int a = img->num_subimages;
175 out = get_token_value(gwps, id->token, info->offset,
176 buf, sizeof(buf), &a);
178 /* NOTE: get_token_value() returns values starting at 1! */
179 if (a == -1)
180 a = (out && *out) ? 1 : 2;
181 if (token->type == SKIN_TOKEN_IMAGE_DISPLAY_LISTICON)
182 a -= 2; /* 2 is added in statusbar-skinned.c! */
183 else
184 a--;
185 a += id->offset;
187 /* Clear the image, as in conditionals */
188 clear_image_pos(gwps, img);
190 /* If the token returned a value which is higher than
191 * the amount of subimages, don't draw it. */
192 if (a >= 0 && a < img->num_subimages)
194 img->display = a;
198 break;
200 #ifdef HAVE_ALBUMART
201 case SKIN_TOKEN_ALBUMART_DISPLAY:
202 /* now draw the AA */
203 if (do_refresh && data->albumart)
205 int handle = playback_current_aa_hid(data->playback_aa_slot);
206 #if CONFIG_TUNER
207 if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
209 struct dim dim = {data->albumart->width, data->albumart->height};
210 handle = radio_get_art_hid(&dim);
212 #endif
213 data->albumart->draw_handle = handle;
215 break;
216 #endif
217 case SKIN_TOKEN_DRAW_INBUILTBAR:
218 gui_statusbar_draw(&(statusbars.statusbars[gwps->display->screen_type]),
219 info->refresh_type == SKIN_REFRESH_ALL,
220 token->value.data);
221 break;
222 case SKIN_TOKEN_VIEWPORT_CUSTOMLIST:
223 if (do_refresh)
224 skin_render_playlistviewer(token->value.data, gwps,
225 info->skin_vp, info->refresh_type);
226 break;
228 #endif /* HAVE_LCD_BITMAP */
229 default:
230 return false;
232 return true;
237 static void do_tags_in_hidden_conditional(struct skin_element* branch,
238 struct skin_draw_info *info)
240 #ifdef HAVE_LCD_BITMAP
241 struct gui_wps *gwps = info->gwps;
242 struct wps_data *data = gwps->data;
243 #endif
244 /* Tags here are ones which need to be "turned off" or cleared
245 * if they are in a conditional branch which isnt being used */
246 if (branch->type == LINE_ALTERNATOR)
248 int i;
249 for (i=0; i<branch->children_count; i++)
251 do_tags_in_hidden_conditional(branch->children[i], info);
254 else if (branch->type == LINE && branch->children_count)
256 struct skin_element *child = branch->children[0];
257 struct wps_token *token;
258 while (child)
260 if (child->type == CONDITIONAL)
262 int i;
263 for (i=0; i<child->children_count; i++)
265 do_tags_in_hidden_conditional(child->children[i], info);
267 child = child->next;
268 continue;
270 else if (child->type != TAG || !child->data)
272 child = child->next;
273 continue;
275 token = (struct wps_token *)child->data;
276 #ifdef HAVE_LCD_BITMAP
277 /* clear all pictures in the conditional and nested ones */
278 if (token->type == SKIN_TOKEN_IMAGE_PRELOAD_DISPLAY)
280 struct image_display *id = token->value.data;
281 struct gui_img *img = find_image(id->label, data);
282 clear_image_pos(gwps, img);
284 else if (token->type == SKIN_TOKEN_PEAKMETER)
286 data->peak_meter_enabled = false;
288 else if (token->type == SKIN_TOKEN_VIEWPORT_ENABLE)
290 char *label = token->value.data;
291 struct skin_element *viewport;
292 for (viewport = data->tree;
293 viewport;
294 viewport = viewport->next)
296 struct skin_viewport *skin_viewport = (struct skin_viewport*)viewport->data;
297 if (skin_viewport->label && strcmp(skin_viewport->label, label))
298 continue;
299 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
301 continue;
303 if (skin_viewport->hidden_flags&VP_DRAW_HIDEABLE)
305 if (skin_viewport->hidden_flags&VP_DRAW_HIDDEN)
306 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
307 else
309 gwps->display->set_viewport(&skin_viewport->vp);
310 gwps->display->clear_viewport();
311 gwps->display->scroll_stop(&skin_viewport->vp);
312 gwps->display->set_viewport(&info->skin_vp->vp);
313 skin_viewport->hidden_flags |= VP_DRAW_HIDDEN;
318 #endif
319 #ifdef HAVE_ALBUMART
320 else if (data->albumart && token->type == SKIN_TOKEN_ALBUMART_DISPLAY)
322 draw_album_art(gwps,
323 playback_current_aa_hid(data->playback_aa_slot), true);
325 #endif
326 child = child->next;
331 static void fix_line_alignment(struct skin_draw_info *info, struct skin_element *element)
333 struct align_pos *align = &info->align;
334 char *cur_pos = info->cur_align_start + strlen(info->cur_align_start);
335 switch (element->tag->type)
337 case SKIN_TOKEN_ALIGN_LEFT:
338 *cur_pos = '\0'; cur_pos++; *cur_pos = '\0';
339 align->left = cur_pos;
340 info->cur_align_start = cur_pos;
341 break;
342 case SKIN_TOKEN_ALIGN_LEFT_RTL:
343 *cur_pos = '\0'; cur_pos++; *cur_pos = '\0';
344 if (lang_is_rtl())
345 align->right = cur_pos;
346 else
347 align->left = cur_pos;
348 info->cur_align_start = cur_pos;
349 break;
350 case SKIN_TOKEN_ALIGN_CENTER:
351 *cur_pos = '\0'; cur_pos++; *cur_pos = '\0';
352 align->center = cur_pos;
353 info->cur_align_start = cur_pos;
354 break;
355 case SKIN_TOKEN_ALIGN_RIGHT:
356 *cur_pos = '\0'; cur_pos++; *cur_pos = '\0';
357 align->right = cur_pos;
358 info->cur_align_start = cur_pos;
359 break;
360 case SKIN_TOKEN_ALIGN_RIGHT_RTL:
361 *cur_pos = '\0'; cur_pos++; *cur_pos = '\0';
362 if (lang_is_rtl())
363 align->left = cur_pos;
364 else
365 align->right = cur_pos;
366 info->cur_align_start = cur_pos;
367 break;
368 default:
369 break;
373 /* Draw a LINE element onto the display */
374 static bool skin_render_line(struct skin_element* line, struct skin_draw_info *info)
376 bool needs_update = false;
377 int last_value, value;
379 if (line->children_count == 0)
380 return false; /* empty line, do nothing */
382 struct skin_element *child = line->children[0];
383 struct conditional *conditional;
384 skin_render_func func = skin_render_line;
385 int old_refresh_mode = info->refresh_type;
386 while (child)
388 switch (child->type)
390 case CONDITIONAL:
391 conditional = (struct conditional*)child->data;
392 last_value = conditional->last_value;
393 value = evaluate_conditional(info->gwps, info->offset,
394 conditional, child->children_count);
395 conditional->last_value = value;
396 if (child->children_count == 1)
398 /* special handling so
399 * %?aa<true> and %?<true|false> need special handlng here */
401 if (value == -1) /* tag is false */
403 /* we are in a false branch of a %?aa<true> conditional */
404 if (last_value == 0)
405 do_tags_in_hidden_conditional(child->children[0], info);
406 break;
409 else
411 if (last_value >= 0 && value != last_value && last_value < child->children_count)
412 do_tags_in_hidden_conditional(child->children[last_value], info);
414 if (child->children[value]->type == LINE_ALTERNATOR)
416 func = skin_render_alternator;
418 else if (child->children[value]->type == LINE)
419 func = skin_render_line;
421 if (value != last_value)
423 info->refresh_type = SKIN_REFRESH_ALL;
424 info->force_redraw = true;
427 if (func(child->children[value], info))
428 needs_update = true;
429 else
430 needs_update = needs_update || (last_value != value);
432 info->refresh_type = old_refresh_mode;
433 break;
434 case TAG:
435 if (child->tag->flags & NOBREAK)
436 info->no_line_break = true;
437 if (child->tag->type == SKIN_TOKEN_SUBLINE_SCROLL)
438 info->line_scrolls = true;
440 fix_line_alignment(info, child);
442 if (!child->data)
444 break;
446 if (!do_non_text_tags(info->gwps, info, child, &info->skin_vp->vp))
448 static char tempbuf[128];
449 const char *value = get_token_value(info->gwps, child->data,
450 info->offset, tempbuf,
451 sizeof(tempbuf), NULL);
452 if (value)
454 #if CONFIG_RTC
455 if (child->tag->flags&SKIN_RTC_REFRESH)
456 needs_update = needs_update || info->refresh_type&SKIN_REFRESH_DYNAMIC;
457 #endif
458 needs_update = needs_update ||
459 ((child->tag->flags&info->refresh_type)!=0);
460 strlcat(info->cur_align_start, value,
461 info->buf_size - (info->cur_align_start-info->buf));
464 break;
465 case TEXT:
466 strlcat(info->cur_align_start, child->data,
467 info->buf_size - (info->cur_align_start-info->buf));
468 needs_update = needs_update ||
469 (info->refresh_type&SKIN_REFRESH_STATIC) != 0;
470 break;
471 case COMMENT:
472 default:
473 break;
476 child = child->next;
478 return needs_update;
481 static int get_subline_timeout(struct gui_wps *gwps, struct skin_element* line)
483 struct skin_element *element=line;
484 struct wps_token *token;
485 int retval = DEFAULT_SUBLINE_TIME_MULTIPLIER*TIMEOUT_UNIT;
486 if (element->type == LINE)
488 if (element->children_count == 0)
489 return retval; /* empty line, so force redraw */
490 element = element->children[0];
492 while (element)
494 if (element->type == TAG &&
495 element->tag->type == SKIN_TOKEN_SUBLINE_TIMEOUT )
497 token = element->data;
498 return token->value.i;
500 else if (element->type == CONDITIONAL)
502 struct conditional *conditional = element->data;
503 int val = evaluate_conditional(gwps, 0, conditional,
504 element->children_count);
505 if (val >= 0)
507 retval = get_subline_timeout(gwps, element->children[val]);
508 if (retval >= 0)
509 return retval;
512 element = element->next;
514 return retval;
517 bool skin_render_alternator(struct skin_element* element, struct skin_draw_info *info)
519 bool changed_lines = false;
520 struct line_alternator *alternator = (struct line_alternator*)element->data;
521 unsigned old_refresh = info->refresh_type;
522 if (info->refresh_type == SKIN_REFRESH_ALL)
524 alternator->current_line = element->children_count-1;
525 changed_lines = true;
527 else if (TIME_AFTER(current_tick, alternator->next_change_tick))
529 changed_lines = true;
532 if (changed_lines)
534 struct skin_element *current_line = element->children[alternator->current_line];
535 int start = alternator->current_line;
536 int try_line = start;
537 bool suitable = false;
538 int rettimeout = DEFAULT_SUBLINE_TIME_MULTIPLIER*TIMEOUT_UNIT;
540 /* find a subline which has at least one token in it,
541 * and that line doesnt have a timeout set to 0 through conditionals */
542 do {
543 try_line++;
544 if (try_line >= element->children_count)
545 try_line = 0;
546 if (element->children[try_line]->children_count != 0)
548 current_line = element->children[try_line];
549 rettimeout = get_subline_timeout(info->gwps,
550 current_line->children[0]);
551 if (rettimeout > 0)
553 suitable = true;
557 while (try_line != start && !suitable);
559 if (suitable)
561 alternator->current_line = try_line;
562 alternator->next_change_tick = current_tick + rettimeout;
565 info->refresh_type = SKIN_REFRESH_ALL;
566 info->force_redraw = true;
568 bool ret = skin_render_line(element->children[alternator->current_line], info);
569 info->refresh_type = old_refresh;
570 return changed_lines || ret;
573 static void skin_render_viewport(struct skin_element* viewport, struct gui_wps *gwps,
574 struct skin_viewport* skin_viewport, unsigned long refresh_type)
576 struct screen *display = gwps->display;
577 char linebuf[MAX_LINE];
578 skin_render_func func = skin_render_line;
579 struct skin_element* line = viewport;
580 struct skin_draw_info info = {
581 .gwps = gwps,
582 .buf = linebuf,
583 .buf_size = sizeof(linebuf),
584 .line_number = 0,
585 .no_line_break = false,
586 .line_scrolls = false,
587 .refresh_type = refresh_type,
588 .skin_vp = skin_viewport,
589 .offset = 0
592 struct align_pos * align = &info.align;
593 bool needs_update;
594 #ifdef HAVE_LCD_BITMAP
595 /* Set images to not to be displayed */
596 struct skin_token_list *imglist = gwps->data->images;
597 while (imglist)
599 struct gui_img *img = (struct gui_img *)imglist->token->value.data;
600 img->display = -1;
601 imglist = imglist->next;
603 #endif
605 while (line)
607 linebuf[0] = '\0';
608 info.no_line_break = false;
609 info.line_scrolls = false;
610 info.force_redraw = false;
612 info.cur_align_start = info.buf;
613 align->left = info.buf;
614 align->center = NULL;
615 align->right = NULL;
618 if (line->type == LINE_ALTERNATOR)
619 func = skin_render_alternator;
620 else if (line->type == LINE)
621 func = skin_render_line;
623 needs_update = func(line, &info);
625 /* only update if the line needs to be, and there is something to write */
626 if (refresh_type && needs_update)
628 if (info.line_scrolls)
630 /* if the line is a scrolling one we don't want to update
631 too often, so that it has the time to scroll */
632 if ((refresh_type & SKIN_REFRESH_SCROLL) || info.force_redraw)
633 write_line(display, align, info.line_number, true);
635 else
636 write_line(display, align, info.line_number, false);
638 if (!info.no_line_break)
639 info.line_number++;
640 line = line->next;
642 #ifdef HAVE_LCD_BITMAP
643 wps_display_images(gwps, &skin_viewport->vp);
644 #endif
647 void skin_render(struct gui_wps *gwps, unsigned refresh_mode)
649 struct wps_data *data = gwps->data;
650 struct screen *display = gwps->display;
652 struct skin_element* viewport = data->tree;
653 struct skin_viewport* skin_viewport;
655 int old_refresh_mode = refresh_mode;
657 #ifdef HAVE_LCD_CHARCELLS
658 int i;
659 for (i = 0; i < 8; i++)
661 if (data->wps_progress_pat[i] == 0)
662 data->wps_progress_pat[i] = display->get_locked_pattern();
664 #endif
665 viewport = data->tree;
666 skin_viewport = (struct skin_viewport *)viewport->data;
667 if (skin_viewport->label && viewport->next &&
668 !strcmp(skin_viewport->label,VP_DEFAULT_LABEL))
669 refresh_mode = 0;
671 for (viewport = data->tree;
672 viewport;
673 viewport = viewport->next)
675 /* SETUP */
676 skin_viewport = (struct skin_viewport*)viewport->data;
677 unsigned vp_refresh_mode = refresh_mode;
678 #if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)
679 skin_viewport->vp.fg_pattern = skin_viewport->start_fgcolour;
680 skin_viewport->vp.bg_pattern = skin_viewport->start_bgcolour;
681 #endif
683 /* dont redraw the viewport if its disabled */
684 if (skin_viewport->hidden_flags&VP_NEVER_VISIBLE)
685 { /* don't draw anything into this one */
686 vp_refresh_mode = 0;
688 else if ((skin_viewport->hidden_flags&VP_DRAW_HIDDEN))
690 skin_viewport->hidden_flags |= VP_DRAW_WASHIDDEN;
691 continue;
693 else if (((skin_viewport->hidden_flags&
694 (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE))
695 == (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE)))
697 vp_refresh_mode = SKIN_REFRESH_ALL;
698 skin_viewport->hidden_flags = VP_DRAW_HIDEABLE;
701 display->set_viewport(&skin_viewport->vp);
702 if ((vp_refresh_mode&SKIN_REFRESH_ALL) == SKIN_REFRESH_ALL)
704 display->clear_viewport();
706 /* render */
707 skin_render_viewport(viewport->children[0], gwps,
708 skin_viewport, vp_refresh_mode);
709 refresh_mode = old_refresh_mode;
712 /* Restore the default viewport */
713 display->set_viewport(NULL);
714 display->update();
717 #ifdef HAVE_LCD_BITMAP
718 static __attribute__((noinline)) void skin_render_playlistviewer(struct playlistviewer* viewer,
719 struct gui_wps *gwps,
720 struct skin_viewport* skin_viewport,
721 unsigned long refresh_type)
723 struct screen *display = gwps->display;
724 char linebuf[MAX_LINE];
725 skin_render_func func = skin_render_line;
726 struct skin_element* line;
727 struct skin_draw_info info = {
728 .gwps = gwps,
729 .buf = linebuf,
730 .buf_size = sizeof(linebuf),
731 .line_number = 0,
732 .no_line_break = false,
733 .line_scrolls = false,
734 .refresh_type = refresh_type,
735 .skin_vp = skin_viewport,
736 .offset = viewer->start_offset
739 struct align_pos * align = &info.align;
740 bool needs_update;
741 int cur_pos, start_item, max;
742 int nb_lines = viewport_get_nb_lines(viewer->vp);
743 #if CONFIG_TUNER
744 if (current_screen() == GO_TO_FM)
746 cur_pos = radio_current_preset();
747 start_item = cur_pos + viewer->start_offset;
748 max = start_item+radio_preset_count();
750 else
751 #endif
753 struct cuesheet *cue = skin_get_global_state()->id3 ?
754 skin_get_global_state()->id3->cuesheet : NULL;
755 cur_pos = playlist_get_display_index();
756 max = playlist_amount()+1;
757 if (cue)
758 max += cue->track_count;
759 start_item = MAX(0, cur_pos + viewer->start_offset);
761 if (max-start_item > nb_lines)
762 max = start_item + nb_lines;
764 line = viewer->line;
765 while (start_item < max)
767 linebuf[0] = '\0';
768 info.no_line_break = false;
769 info.line_scrolls = false;
770 info.force_redraw = false;
772 info.cur_align_start = info.buf;
773 align->left = info.buf;
774 align->center = NULL;
775 align->right = NULL;
778 if (line->type == LINE_ALTERNATOR)
779 func = skin_render_alternator;
780 else if (line->type == LINE)
781 func = skin_render_line;
783 needs_update = func(line, &info);
785 /* only update if the line needs to be, and there is something to write */
786 if (refresh_type && needs_update)
788 if (info.line_scrolls)
790 /* if the line is a scrolling one we don't want to update
791 too often, so that it has the time to scroll */
792 if ((refresh_type & SKIN_REFRESH_SCROLL) || info.force_redraw)
793 write_line(display, align, info.line_number, true);
795 else
796 write_line(display, align, info.line_number, false);
798 info.line_number++;
799 info.offset++;
800 start_item++;
803 #endif