1 /* theme_loader.c - Theme file loader for gfxmenu. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <grub/types.h>
21 #include <grub/file.h>
22 #include <grub/misc.h>
26 #include <grub/video.h>
27 #include <grub/gui_string_util.h>
28 #include <grub/bitmap.h>
29 #include <grub/bitmap_scale.h>
30 #include <grub/gfxwidgets.h>
31 #include <grub/gfxmenu_view.h>
34 /* Construct a new box widget using ABSPATTERN to find the pixmap files for
35 it, storing the new box instance at *BOXPTR.
36 PATTERN should be of the form: "(hd0,0)/somewhere/style*.png".
37 The '*' then gets substituted with the various pixmap names that the
40 recreate_box_absolute (grub_gfxmenu_box_t
*boxptr
, const char *abspattern
)
45 grub_gfxmenu_box_t box
;
47 star
= grub_strchr (abspattern
, '*');
49 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
50 "missing `*' in box pixmap pattern `%s'", abspattern
);
52 /* Prefix: Get the part before the '*'. */
53 prefix
= grub_malloc (star
- abspattern
+ 1);
57 grub_memcpy (prefix
, abspattern
, star
- abspattern
);
58 prefix
[star
- abspattern
] = '\0';
60 /* Suffix: Everything after the '*' is the suffix. */
63 box
= grub_gfxmenu_create_box (prefix
, suffix
);
69 (*boxptr
)->destroy (*boxptr
);
75 /* Construct a new box widget using PATTERN to find the pixmap files for it,
76 storing the new widget at *BOXPTR. PATTERN should be of the form:
77 "somewhere/style*.png". The '*' then gets substituted with the various
78 pixmap names that the widget uses.
80 Important! The value of *BOXPTR must be initialized! It must either
81 (1) Be 0 (a NULL pointer), or
82 (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance.
83 In this case, the previous instance is destroyed. */
85 grub_gui_recreate_box (grub_gfxmenu_box_t
*boxptr
,
86 const char *pattern
, const char *theme_dir
)
90 /* Check arguments. */
93 /* If no pixmap pattern is given, then just create an empty box. */
95 (*boxptr
)->destroy (*boxptr
);
96 *boxptr
= grub_gfxmenu_create_box (0, 0);
101 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
102 "styled box missing theme directory");
104 /* Resolve to an absolute path. */
105 abspattern
= grub_resolve_relative_path (theme_dir
, pattern
);
109 /* Create the box. */
110 recreate_box_absolute (boxptr
, abspattern
);
111 grub_free (abspattern
);
115 /* Set the specified property NAME on the view to the given string VALUE.
116 The caller is responsible for the lifetimes of NAME and VALUE. */
118 theme_set_string (grub_gfxmenu_view_t view
,
121 const char *theme_dir
,
122 const char *filename
,
126 if (! grub_strcmp ("title-font", name
))
127 view
->title_font
= grub_font_get (value
);
128 else if (! grub_strcmp ("message-font", name
))
129 view
->message_font
= grub_font_get (value
);
130 else if (! grub_strcmp ("terminal-font", name
))
132 grub_free (view
->terminal_font_name
);
133 view
->terminal_font_name
= grub_strdup (value
);
134 if (! view
->terminal_font_name
)
137 else if (! grub_strcmp ("title-color", name
))
138 grub_gui_parse_color (value
, &view
->title_color
);
139 else if (! grub_strcmp ("message-color", name
))
140 grub_gui_parse_color (value
, &view
->message_color
);
141 else if (! grub_strcmp ("message-bg-color", name
))
142 grub_gui_parse_color (value
, &view
->message_bg_color
);
143 else if (! grub_strcmp ("desktop-image", name
))
145 struct grub_video_bitmap
*raw_bitmap
;
146 struct grub_video_bitmap
*scaled_bitmap
;
148 path
= grub_resolve_relative_path (theme_dir
, value
);
151 if (grub_video_bitmap_load (&raw_bitmap
, path
) != GRUB_ERR_NONE
)
157 grub_video_bitmap_create_scaled (&scaled_bitmap
,
161 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST
);
162 grub_video_bitmap_destroy (raw_bitmap
);
166 return grub_error (grub_errno
, "error scaling desktop image");
169 grub_video_bitmap_destroy (view
->desktop_image
);
170 view
->desktop_image
= scaled_bitmap
;
172 else if (! grub_strcmp ("desktop-color", name
))
173 grub_gui_parse_color (value
, &view
->desktop_color
);
174 else if (! grub_strcmp ("terminal-box", name
))
177 err
= grub_gui_recreate_box (&view
->terminal_box
, value
, theme_dir
);
178 if (err
!= GRUB_ERR_NONE
)
181 else if (! grub_strcmp ("title-text", name
))
183 grub_free (view
->title_text
);
184 view
->title_text
= grub_strdup (value
);
185 if (! view
->title_text
)
190 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
191 "%s:%d:%d unknown property `%s'",
192 filename
, line_num
, col_num
, name
);
204 const char *filename
;
206 grub_gfxmenu_view_t view
;
210 has_more (struct parsebuf
*p
)
212 return p
->pos
< p
->len
;
216 read_char (struct parsebuf
*p
)
221 c
= p
->buf
[p
->pos
++];
238 peek_char (struct parsebuf
*p
)
241 return p
->buf
[p
->pos
];
247 is_whitespace (char c
)
257 skip_whitespace (struct parsebuf
*p
)
259 while (has_more (p
) && is_whitespace(peek_char (p
)))
264 advance_to_next_line (struct parsebuf
*p
)
268 /* Eat characters up to the newline. */
273 while (c
!= -1 && c
!= '\n');
277 is_identifier_char (int c
)
287 read_identifier (struct parsebuf
*p
)
289 /* Index of the first character of the identifier in p->buf. */
291 /* Next index after the last character of the identifer in p->buf. */
296 /* Capture the start of the identifier. */
299 /* Scan for the end. */
300 while (is_identifier_char (peek_char (p
)))
307 return grub_new_substring (p
->buf
, start
, end
);
311 read_expression (struct parsebuf
*p
)
317 if (peek_char (p
) == '"')
319 /* Read as a quoted string.
320 The quotation marks are not included in the expression value. */
321 /* Skip opening quotation mark. */
324 while (has_more (p
) && peek_char (p
) != '"')
327 /* Skip the terminating quotation mark. */
330 else if (peek_char (p
) == '(')
332 /* Read as a parenthesized string -- for tuples/coordinates. */
333 /* The parentheses are included in the expression value. */
341 while (c
!= -1 && c
!= ')');
344 else if (has_more (p
))
346 /* Read as a single word -- for numeric values or words without
349 while (has_more (p
) && ! is_whitespace (peek_char (p
)))
355 /* The end of the theme file has been reached. */
356 grub_error (GRUB_ERR_IO
, "%s:%d:%d expression expected in theme file",
357 p
->filename
, p
->line_num
, p
->col_num
);
361 return grub_new_substring (p
->buf
, start
, end
);
364 /* Read a GUI object specification from the theme file.
365 Any components created will be added to the GUI container PARENT. */
367 read_object (struct parsebuf
*p
, grub_gui_container_t parent
)
369 grub_video_rect_t bounds
;
372 name
= read_identifier (p
);
376 grub_gui_component_t component
= 0;
377 if (grub_strcmp (name
, "label") == 0)
379 component
= grub_gui_label_new ();
381 else if (grub_strcmp (name
, "image") == 0)
383 component
= grub_gui_image_new ();
385 else if (grub_strcmp (name
, "vbox") == 0)
387 component
= (grub_gui_component_t
) grub_gui_vbox_new ();
389 else if (grub_strcmp (name
, "hbox") == 0)
391 component
= (grub_gui_component_t
) grub_gui_hbox_new ();
393 else if (grub_strcmp (name
, "canvas") == 0)
395 component
= (grub_gui_component_t
) grub_gui_canvas_new ();
397 else if (grub_strcmp (name
, "progress_bar") == 0)
399 component
= grub_gui_progress_bar_new ();
401 else if (grub_strcmp (name
, "circular_progress") == 0)
403 component
= grub_gui_circular_progress_new ();
405 else if (grub_strcmp (name
, "boot_menu") == 0)
407 component
= grub_gui_list_new ();
412 grub_error (GRUB_ERR_IO
, "%s:%d:%d unknown object type `%s'",
413 p
->filename
, p
->line_num
, p
->col_num
, name
);
420 /* Inform the component about the theme so it can find its resources. */
421 component
->ops
->set_property (component
, "theme_dir", p
->theme_dir
);
422 component
->ops
->set_property (component
, "theme_path", p
->filename
);
424 /* Add the component as a child of PARENT. */
429 component
->ops
->set_bounds (component
, &bounds
);
430 parent
->ops
->add (parent
, component
);
433 if (read_char (p
) != '{')
435 grub_error (GRUB_ERR_IO
,
436 "%s:%d:%d expected `{' after object type name `%s'",
437 p
->filename
, p
->line_num
, p
->col_num
, name
);
445 /* Check whether the end has been encountered. */
446 if (peek_char (p
) == '}')
448 /* Skip the closing brace. */
453 if (peek_char (p
) == '#')
456 advance_to_next_line (p
);
460 if (peek_char (p
) == '+')
465 /* Check whether this component is a container. */
466 if (component
->ops
->is_instance (component
, "container"))
468 /* Read the sub-object recursively and add it as a child. */
469 if (read_object (p
, (grub_gui_container_t
) component
) != 0)
471 /* After reading the sub-object, resume parsing, expecting
472 another property assignment or sub-object definition. */
477 grub_error (GRUB_ERR_IO
,
478 "%s:%d:%d attempted to add object to non-container",
479 p
->filename
, p
->line_num
, p
->col_num
);
485 property
= read_identifier (p
);
488 grub_error (GRUB_ERR_IO
, "%s:%d:%d identifier expected in theme file",
489 p
->filename
, p
->line_num
, p
->col_num
);
494 if (read_char (p
) != '=')
496 grub_error (GRUB_ERR_IO
,
497 "%s:%d:%d expected `=' after property name `%s'",
498 p
->filename
, p
->line_num
, p
->col_num
, property
);
499 grub_free (property
);
505 value
= read_expression (p
);
508 grub_free (property
);
512 /* Handle the property value. */
513 if (grub_strcmp (property
, "position") == 0)
515 /* Special case for position value. */
519 if (grub_gui_parse_2_tuple (value
, &x
, &y
) == GRUB_ERR_NONE
)
522 component
->ops
->get_bounds (component
, &r
);
525 component
->ops
->set_bounds (component
, &r
);
528 else if (grub_strcmp (property
, "size") == 0)
530 /* Special case for size value. */
534 if (grub_gui_parse_2_tuple (value
, &w
, &h
) == GRUB_ERR_NONE
)
537 component
->ops
->get_bounds (component
, &r
);
540 component
->ops
->set_bounds (component
, &r
);
545 /* General property handling. */
546 component
->ops
->set_property (component
, property
, value
);
550 grub_free (property
);
551 if (grub_errno
!= GRUB_ERR_NONE
)
555 /* Set the object's size to its preferred size unless the user has
556 explicitly specified the size. */
557 component
->ops
->get_bounds (component
, &bounds
);
558 if (bounds
.width
== -1 || bounds
.height
== -1)
560 component
->ops
->get_preferred_size (component
,
561 &bounds
.width
, &bounds
.height
);
562 component
->ops
->set_bounds (component
, &bounds
);
571 read_property (struct parsebuf
*p
)
575 /* Read the property name. */
576 name
= read_identifier (p
);
579 advance_to_next_line (p
);
583 /* Skip whitespace before separator. */
586 /* Read separator. */
587 if (read_char (p
) != ':')
589 grub_error (GRUB_ERR_IO
,
590 "%s:%d:%d missing separator after property name `%s'",
591 p
->filename
, p
->line_num
, p
->col_num
, name
);
595 /* Skip whitespace after separator. */
598 /* Get the value based on its type. */
599 if (peek_char (p
) == '"')
601 /* String value (e.g., '"My string"'). */
602 char *value
= read_expression (p
);
605 grub_error (GRUB_ERR_IO
, "%s:%d:%d missing property value",
606 p
->filename
, p
->line_num
, p
->col_num
);
609 /* If theme_set_string results in an error, grub_errno will be returned
611 theme_set_string (p
->view
, name
, value
, p
->theme_dir
,
612 p
->filename
, p
->line_num
, p
->col_num
);
617 grub_error (GRUB_ERR_IO
,
618 "%s:%d:%d property value invalid; "
619 "enclose literal values in quotes (\")",
620 p
->filename
, p
->line_num
, p
->col_num
);
629 /* Set properties on the view based on settings from the specified
632 grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view
, const char *theme_path
)
638 p
.theme_dir
= grub_get_dirname (theme_path
);
640 file
= grub_file_open (theme_path
);
643 grub_free (p
.theme_dir
);
647 p
.len
= grub_file_size (file
);
648 p
.buf
= grub_malloc (p
.len
);
652 p
.filename
= theme_path
;
655 grub_file_close (file
);
656 grub_free (p
.theme_dir
);
659 if (grub_file_read (file
, p
.buf
, p
.len
) != p
.len
)
662 grub_file_close (file
);
663 grub_free (p
.theme_dir
);
668 view
->canvas
->ops
->component
.destroy (view
->canvas
);
670 view
->canvas
= grub_gui_canvas_new ();
671 ((grub_gui_component_t
) view
->canvas
)
672 ->ops
->set_bounds ((grub_gui_component_t
) view
->canvas
,
675 while (has_more (&p
))
677 /* Skip comments (lines beginning with #). */
678 if (peek_char (&p
) == '#')
680 advance_to_next_line (&p
);
684 /* Find the first non-whitespace character. */
685 skip_whitespace (&p
);
687 /* Handle the content. */
688 if (peek_char (&p
) == '+')
692 read_object (&p
, view
->canvas
);
699 if (grub_errno
!= GRUB_ERR_NONE
)
703 /* Set the new theme path. */
704 grub_free (view
->theme_path
);
705 view
->theme_path
= grub_strdup (theme_path
);
711 view
->canvas
->ops
->component
.destroy (view
->canvas
);
717 grub_file_close (file
);
718 grub_free (p
.theme_dir
);