1 /* view.c - Graphical menu interface MVC view. */
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/normal.h>
27 #include <grub/video.h>
28 #include <grub/gfxterm.h>
29 #include <grub/bitmap.h>
30 #include <grub/bitmap_scale.h>
31 #include <grub/term.h>
32 #include <grub/gfxwidgets.h>
33 #include <grub/time.h>
34 #include <grub/menu.h>
35 #include <grub/menu_viewer.h>
36 #include <grub/gfxmenu_view.h>
37 #include <grub/gui_string_util.h>
38 #include <grub/icon_manager.h>
39 #include <grub/i18n.h>
42 init_terminal (grub_gfxmenu_view_t view
);
44 init_background (grub_gfxmenu_view_t view
);
45 static grub_gfxmenu_view_t term_view
;
47 /* Create a new view object, loading the theme specified by THEME_PATH and
48 associating MODEL with the view. */
50 grub_gfxmenu_view_new (const char *theme_path
,
51 int width
, int height
)
53 grub_gfxmenu_view_t view
;
54 grub_font_t default_font
;
55 grub_video_rgba_color_t default_fg_color
;
56 grub_video_rgba_color_t default_bg_color
;
58 view
= grub_malloc (sizeof (*view
));
62 while (grub_gfxmenu_timeout_notifications
)
64 struct grub_gfxmenu_timeout_notify
*p
;
65 p
= grub_gfxmenu_timeout_notifications
;
66 grub_gfxmenu_timeout_notifications
= grub_gfxmenu_timeout_notifications
->next
;
72 view
->screen
.width
= width
;
73 view
->screen
.height
= height
;
75 view
->need_to_check_sanity
= 1;
76 view
->terminal_border
= 3;
77 view
->terminal_rect
.width
= view
->screen
.width
* 7 / 10;
78 view
->terminal_rect
.height
= view
->screen
.height
* 7 / 10;
79 view
->terminal_rect
.x
= view
->screen
.x
+ (view
->screen
.width
80 - view
->terminal_rect
.width
) / 2;
81 view
->terminal_rect
.y
= view
->screen
.y
+ (view
->screen
.height
82 - view
->terminal_rect
.height
) / 2;
84 default_font
= grub_font_get ("Unknown Regular 16");
85 default_fg_color
= grub_video_rgba_color_rgb (0, 0, 0);
86 default_bg_color
= grub_video_rgba_color_rgb (255, 255, 255);
90 view
->title_font
= default_font
;
91 view
->message_font
= default_font
;
92 view
->terminal_font_name
= grub_strdup ("Fixed 10");
93 view
->title_color
= default_fg_color
;
94 view
->message_color
= default_bg_color
;
95 view
->message_bg_color
= default_fg_color
;
96 view
->raw_desktop_image
= 0;
97 view
->scaled_desktop_image
= 0;
98 view
->desktop_image_scale_method
= GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH
;
99 view
->desktop_image_h_align
= GRUB_VIDEO_BITMAP_H_ALIGN_CENTER
;
100 view
->desktop_image_v_align
= GRUB_VIDEO_BITMAP_V_ALIGN_CENTER
;
101 view
->desktop_color
= default_bg_color
;
102 view
->terminal_box
= grub_gfxmenu_create_box (0, 0);
103 view
->title_text
= grub_strdup (_("GRUB Boot Menu"));
104 view
->progress_message_text
= 0;
105 view
->theme_path
= 0;
107 /* Set the timeout bar's frame. */
108 view
->progress_message_frame
.width
= view
->screen
.width
* 4 / 5;
109 view
->progress_message_frame
.height
= 50;
110 view
->progress_message_frame
.x
= view
->screen
.x
111 + (view
->screen
.width
- view
->progress_message_frame
.width
) / 2;
112 view
->progress_message_frame
.y
= view
->screen
.y
113 + view
->screen
.height
- 90 - 20 - view
->progress_message_frame
.height
;
115 if (grub_gfxmenu_view_load_theme (view
, theme_path
) != 0)
117 grub_gfxmenu_view_destroy (view
);
124 /* Destroy the view object. All used memory is freed. */
126 grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view
)
130 while (grub_gfxmenu_timeout_notifications
)
132 struct grub_gfxmenu_timeout_notify
*p
;
133 p
= grub_gfxmenu_timeout_notifications
;
134 grub_gfxmenu_timeout_notifications
= grub_gfxmenu_timeout_notifications
->next
;
137 grub_video_bitmap_destroy (view
->raw_desktop_image
);
138 grub_video_bitmap_destroy (view
->scaled_desktop_image
);
139 if (view
->terminal_box
)
140 view
->terminal_box
->destroy (view
->terminal_box
);
141 grub_free (view
->terminal_font_name
);
142 grub_free (view
->title_text
);
143 grub_free (view
->progress_message_text
);
144 grub_free (view
->theme_path
);
146 view
->canvas
->component
.ops
->destroy (view
->canvas
);
151 redraw_background (grub_gfxmenu_view_t view
,
152 const grub_video_rect_t
*bounds
)
154 if (view
->scaled_desktop_image
)
156 struct grub_video_bitmap
*img
= view
->scaled_desktop_image
;
157 grub_video_blit_bitmap (img
, GRUB_VIDEO_BLIT_REPLACE
,
158 bounds
->x
, bounds
->y
,
159 bounds
->x
- view
->screen
.x
,
160 bounds
->y
- view
->screen
.y
,
161 bounds
->width
, bounds
->height
);
165 grub_video_fill_rect (grub_video_map_rgba_color (view
->desktop_color
),
166 bounds
->x
, bounds
->y
,
167 bounds
->width
, bounds
->height
);
172 draw_title (grub_gfxmenu_view_t view
)
174 if (! view
->title_text
)
177 /* Center the title. */
178 int title_width
= grub_font_get_string_width (view
->title_font
,
180 int x
= (view
->screen
.width
- title_width
) / 2;
181 int y
= 40 + grub_font_get_ascent (view
->title_font
);
182 grub_font_draw_string (view
->title_text
,
184 grub_video_map_rgba_color (view
->title_color
),
188 struct progress_value_data
196 struct grub_gfxmenu_timeout_notify
*grub_gfxmenu_timeout_notifications
;
199 update_timeouts (int visible
, int start
, int value
, int end
)
201 struct grub_gfxmenu_timeout_notify
*cur
;
203 for (cur
= grub_gfxmenu_timeout_notifications
; cur
; cur
= cur
->next
)
204 cur
->set_state (cur
->self
, visible
, start
, value
, end
);
208 redraw_timeouts (struct grub_gfxmenu_view
*view
)
210 struct grub_gfxmenu_timeout_notify
*cur
;
212 for (cur
= grub_gfxmenu_timeout_notifications
; cur
; cur
= cur
->next
)
214 grub_video_rect_t bounds
;
215 cur
->self
->ops
->get_bounds (cur
->self
, &bounds
);
216 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED
);
217 grub_gfxmenu_view_redraw (view
, &bounds
);
222 grub_gfxmenu_print_timeout (int timeout
, void *data
)
224 struct grub_gfxmenu_view
*view
= data
;
226 if (view
->first_timeout
== -1)
227 view
->first_timeout
= timeout
;
229 update_timeouts (1, -view
->first_timeout
, -timeout
, 0);
230 redraw_timeouts (view
);
231 grub_video_swap_buffers ();
232 if (view
->double_repaint
)
233 redraw_timeouts (view
);
237 grub_gfxmenu_clear_timeout (void *data
)
239 struct grub_gfxmenu_view
*view
= data
;
241 update_timeouts (0, 1, 0, 0);
242 redraw_timeouts (view
);
243 grub_video_swap_buffers ();
244 if (view
->double_repaint
)
245 redraw_timeouts (view
);
249 update_menu_visit (grub_gui_component_t component
,
252 grub_gfxmenu_view_t view
;
254 if (component
->ops
->is_instance (component
, "list"))
256 grub_gui_list_t list
= (grub_gui_list_t
) component
;
257 list
->ops
->set_view_info (list
, view
);
261 /* Update any boot menu components with the current menu model and
264 update_menu_components (grub_gfxmenu_view_t view
)
266 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
267 update_menu_visit
, view
);
271 refresh_menu_visit (grub_gui_component_t component
,
274 grub_gfxmenu_view_t view
;
276 if (component
->ops
->is_instance (component
, "list"))
278 grub_gui_list_t list
= (grub_gui_list_t
) component
;
279 list
->ops
->refresh_list (list
, view
);
283 /* Refresh list information (useful for submenus) */
285 refresh_menu_components (grub_gfxmenu_view_t view
)
287 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
288 refresh_menu_visit
, view
);
292 draw_message (grub_gfxmenu_view_t view
)
294 char *text
= view
->progress_message_text
;
295 grub_video_rect_t f
= view
->progress_message_frame
;
299 grub_font_t font
= view
->message_font
;
300 grub_video_color_t color
= grub_video_map_rgba_color (view
->message_color
);
303 grub_video_fill_rect (color
,
304 f
.x
-1, f
.y
-1, f
.width
+2, f
.height
+2);
306 grub_video_fill_rect (grub_video_map_rgba_color (view
->message_bg_color
),
307 f
.x
, f
.y
, f
.width
, f
.height
);
309 /* Center the text. */
310 int text_width
= grub_font_get_string_width (font
, text
);
311 int x
= f
.x
+ (f
.width
- text_width
) / 2;
312 int y
= (f
.y
+ (f
.height
- grub_font_get_descent (font
)) / 2
313 + grub_font_get_ascent (font
) / 2);
314 grub_font_draw_string (text
, font
, color
, x
, y
);
318 grub_gfxmenu_view_redraw (grub_gfxmenu_view_t view
,
319 const grub_video_rect_t
*region
)
321 if (grub_video_have_common_points (&view
->terminal_rect
, region
))
322 grub_gfxterm_schedule_repaint ();
324 grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY
);
325 grub_video_area_status_t area_status
;
326 grub_video_get_area_status (&area_status
);
327 if (area_status
== GRUB_VIDEO_AREA_ENABLED
)
328 grub_video_set_region (region
->x
, region
->y
,
329 region
->width
, region
->height
);
331 redraw_background (view
, region
);
333 view
->canvas
->component
.ops
->paint (view
->canvas
, region
);
335 if (grub_video_have_common_points (&view
->progress_message_frame
, region
))
338 if (area_status
== GRUB_VIDEO_AREA_ENABLED
)
339 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED
);
343 grub_gfxmenu_view_draw (grub_gfxmenu_view_t view
)
345 init_terminal (view
);
347 init_background (view
);
349 /* Clear the screen; there may be garbage left over in video memory. */
350 grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
351 view
->screen
.x
, view
->screen
.y
,
352 view
->screen
.width
, view
->screen
.height
);
353 grub_video_swap_buffers ();
354 if (view
->double_repaint
)
355 grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
356 view
->screen
.x
, view
->screen
.y
,
357 view
->screen
.width
, view
->screen
.height
);
359 refresh_menu_components (view
);
360 update_menu_components (view
);
362 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED
);
363 grub_gfxmenu_view_redraw (view
, &view
->screen
);
364 grub_video_swap_buffers ();
365 if (view
->double_repaint
)
367 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED
);
368 grub_gfxmenu_view_redraw (view
, &view
->screen
);
374 redraw_menu_visit (grub_gui_component_t component
,
377 grub_gfxmenu_view_t view
;
379 if (component
->ops
->is_instance (component
, "list"))
381 grub_video_rect_t bounds
;
383 component
->ops
->get_bounds (component
, &bounds
);
384 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED
);
385 grub_gfxmenu_view_redraw (view
, &bounds
);
389 extern int g_menu_update_mode
;
391 static void grub_gfxmenu_update_all(grub_gfxmenu_view_t view
)
393 grub_video_set_area_status(GRUB_VIDEO_AREA_DISABLED
);
394 grub_gfxmenu_view_redraw(view
, &view
->screen
);
398 grub_gfxmenu_redraw_menu (grub_gfxmenu_view_t view
)
400 update_menu_components (view
);
402 if (g_menu_update_mode
)
403 grub_gfxmenu_update_all(view
);
405 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
406 redraw_menu_visit
, view
);
408 grub_video_swap_buffers ();
409 if (view
->double_repaint
)
411 if (g_menu_update_mode
)
412 grub_gfxmenu_update_all(view
);
414 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
415 redraw_menu_visit
, view
);
421 grub_gfxmenu_set_chosen_entry (int entry
, void *data
)
423 grub_gfxmenu_view_t view
= data
;
425 view
->selected
= entry
;
426 grub_gfxmenu_redraw_menu (view
);
432 grub_gfxmenu_draw_terminal_box (void)
434 grub_gfxmenu_box_t term_box
;
436 term_box
= term_view
->terminal_box
;
440 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED
);
442 term_box
->set_content_size (term_box
, term_view
->terminal_rect
.width
,
443 term_view
->terminal_rect
.height
);
445 term_box
->draw (term_box
,
446 term_view
->terminal_rect
.x
- term_box
->get_left_pad (term_box
),
447 term_view
->terminal_rect
.y
- term_box
->get_top_pad (term_box
));
451 get_min_terminal (grub_font_t terminal_font
,
452 unsigned int border_width
,
453 unsigned int *min_terminal_width
,
454 unsigned int *min_terminal_height
)
456 struct grub_font_glyph
*glyph
;
457 glyph
= grub_font_get_glyph (terminal_font
, 'M');
458 *min_terminal_width
= (glyph
? glyph
->device_width
: 8) * 80
460 *min_terminal_height
= grub_font_get_max_char_height (terminal_font
) * 24
465 terminal_sanity_check (grub_gfxmenu_view_t view
)
467 if (!view
->need_to_check_sanity
)
470 /* terminal_font was checked before in the init_terminal function. */
471 grub_font_t terminal_font
= grub_font_get (view
->terminal_font_name
);
473 /* Non-negative numbers below. */
474 int scr_x
= view
->screen
.x
;
475 int scr_y
= view
->screen
.y
;
476 int scr_width
= view
->screen
.width
;
477 int scr_height
= view
->screen
.height
;
478 int term_x
= view
->terminal_rect
.x
;
479 int term_y
= view
->terminal_rect
.y
;
480 int term_width
= view
->terminal_rect
.width
;
481 int term_height
= view
->terminal_rect
.height
;
483 /* Check that border_width isn't too big. */
484 unsigned int border_width
= view
->terminal_border
;
485 unsigned int min_terminal_width
;
486 unsigned int min_terminal_height
;
487 get_min_terminal (terminal_font
, border_width
,
488 &min_terminal_width
, &min_terminal_height
);
489 if (border_width
> 3 && ((int) min_terminal_width
>= scr_width
490 || (int) min_terminal_height
>= scr_height
))
493 get_min_terminal (terminal_font
, border_width
,
494 &min_terminal_width
, &min_terminal_height
);
498 if (term_width
> scr_width
)
499 term_width
= scr_width
;
500 if (term_height
> scr_height
)
501 term_height
= scr_height
;
503 if (scr_width
<= (int) min_terminal_width
504 || scr_height
<= (int) min_terminal_height
)
506 /* The screen resulution is too low. Use all space, except a small border
507 to show the user, that it is a window. Then center the window. */
508 term_width
= scr_width
- 6 * border_width
;
509 term_height
= scr_height
- 6 * border_width
;
510 term_x
= scr_x
+ (scr_width
- term_width
) / 2;
511 term_y
= scr_y
+ (scr_height
- term_height
) / 2;
513 else if (term_width
< (int) min_terminal_width
514 || term_height
< (int) min_terminal_height
)
516 /* The screen resolution is big enough. Make sure, that terminal screen
517 dimensions aren't less than minimal values. Then center the window. */
518 term_width
= (int) min_terminal_width
;
519 term_height
= (int) min_terminal_height
;
520 term_x
= scr_x
+ (scr_width
- term_width
) / 2;
521 term_y
= scr_y
+ (scr_height
- term_height
) / 2;
524 /* At this point w and h are satisfying. */
525 if (term_x
+ term_width
> scr_width
)
526 term_x
= scr_width
- term_width
;
527 if (term_y
+ term_height
> scr_height
)
528 term_y
= scr_height
- term_height
;
530 /* Write down corrected data. */
531 view
->terminal_rect
.x
= (unsigned int) term_x
;
532 view
->terminal_rect
.y
= (unsigned int) term_y
;
533 view
->terminal_rect
.width
= (unsigned int) term_width
;
534 view
->terminal_rect
.height
= (unsigned int) term_height
;
535 view
->terminal_border
= border_width
;
537 view
->need_to_check_sanity
= 0;
541 init_terminal (grub_gfxmenu_view_t view
)
543 grub_font_t terminal_font
;
545 terminal_font
= grub_font_get (view
->terminal_font_name
);
548 grub_error (GRUB_ERR_BAD_FONT
, "no font loaded");
552 /* Check that terminal window size and position are sane. */
553 terminal_sanity_check (view
);
557 /* Note: currently there is no API for changing the gfxterm font
558 on the fly, so whatever font the initially loaded theme specifies
559 will be permanent. */
560 grub_gfxterm_set_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY
,
561 view
->terminal_rect
.x
,
562 view
->terminal_rect
.y
,
563 view
->terminal_rect
.width
,
564 view
->terminal_rect
.height
,
565 view
->double_repaint
,
567 view
->terminal_border
);
568 grub_gfxterm_decorator_hook
= grub_gfxmenu_draw_terminal_box
;
572 init_background (grub_gfxmenu_view_t view
)
574 if (view
->scaled_desktop_image
|| (!view
->raw_desktop_image
))
577 struct grub_video_bitmap
*scaled_bitmap
;
578 if (view
->desktop_image_scale_method
==
579 GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH
)
580 grub_video_bitmap_create_scaled (&scaled_bitmap
,
583 view
->raw_desktop_image
,
584 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST
);
586 grub_video_bitmap_scale_proportional (&scaled_bitmap
,
589 view
->raw_desktop_image
,
590 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST
,
591 view
->desktop_image_scale_method
,
592 view
->desktop_image_v_align
,
593 view
->desktop_image_h_align
);
596 view
->scaled_desktop_image
= scaled_bitmap
;
600 /* FIXME: previously notifications were displayed in special case.
604 /* Sets MESSAGE as the progress message for the view.
605 MESSAGE can be 0, in which case no message is displayed. */
607 set_progress_message (grub_gfxmenu_view_t view
, const char *message
)
609 grub_free (view
->progress_message_text
);
611 view
->progress_message_text
= grub_strdup (message
);
613 view
->progress_message_text
= 0;
617 notify_booting (grub_menu_entry_t entry
, void *userdata
)
619 grub_gfxmenu_view_t view
= (grub_gfxmenu_view_t
) userdata
;
621 char *s
= grub_malloc (100 + grub_strlen (entry
->title
));
625 grub_sprintf (s
, "Booting '%s'", entry
->title
);
626 set_progress_message (view
, s
);
628 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
629 grub_video_swap_buffers ();
630 if (view
->double_repaint
)
631 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
635 notify_fallback (grub_menu_entry_t entry
, void *userdata
)
637 grub_gfxmenu_view_t view
= (grub_gfxmenu_view_t
) userdata
;
639 char *s
= grub_malloc (100 + grub_strlen (entry
->title
));
643 grub_sprintf (s
, "Falling back to '%s'", entry
->title
);
644 set_progress_message (view
, s
);
646 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
647 grub_video_swap_buffers ();
648 if (view
->double_repaint
)
649 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
653 notify_execution_failure (void *userdata
__attribute__ ((unused
)))
658 static struct grub_menu_execute_callback execute_callback
=
660 .notify_booting
= notify_booting
,
661 .notify_fallback
= notify_fallback
,
662 .notify_failure
= notify_execution_failure