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>
40 #include <grub/charset.h>
43 init_terminal (grub_gfxmenu_view_t view
);
45 init_background (grub_gfxmenu_view_t view
);
46 static grub_gfxmenu_view_t term_view
;
48 /* Create a new view object, loading the theme specified by THEME_PATH and
49 associating MODEL with the view. */
51 grub_gfxmenu_view_new (const char *theme_path
,
52 int width
, int height
)
54 grub_gfxmenu_view_t view
;
55 grub_font_t default_font
;
56 grub_video_rgba_color_t default_fg_color
;
57 grub_video_rgba_color_t default_bg_color
;
59 view
= grub_malloc (sizeof (*view
));
63 while (grub_gfxmenu_timeout_notifications
)
65 struct grub_gfxmenu_timeout_notify
*p
;
66 p
= grub_gfxmenu_timeout_notifications
;
67 grub_gfxmenu_timeout_notifications
= grub_gfxmenu_timeout_notifications
->next
;
73 view
->screen
.width
= width
;
74 view
->screen
.height
= height
;
76 view
->need_to_check_sanity
= 1;
77 view
->terminal_border
= 3;
78 view
->terminal_rect
.width
= view
->screen
.width
* 7 / 10;
79 view
->terminal_rect
.height
= view
->screen
.height
* 7 / 10;
80 view
->terminal_rect
.x
= view
->screen
.x
+ (view
->screen
.width
81 - view
->terminal_rect
.width
) / 2;
82 view
->terminal_rect
.y
= view
->screen
.y
+ (view
->screen
.height
83 - view
->terminal_rect
.height
) / 2;
85 default_font
= grub_font_get ("Unknown Regular 16");
86 default_fg_color
= grub_video_rgba_color_rgb (0, 0, 0);
87 default_bg_color
= grub_video_rgba_color_rgb (255, 255, 255);
91 view
->title_font
= default_font
;
92 view
->message_font
= default_font
;
93 view
->terminal_font_name
= grub_strdup ("Fixed 10");
94 view
->title_color
= default_fg_color
;
95 view
->message_color
= default_bg_color
;
96 view
->message_bg_color
= default_fg_color
;
97 view
->raw_desktop_image
= 0;
98 view
->scaled_desktop_image
= 0;
99 view
->desktop_image_scale_method
= GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH
;
100 view
->desktop_image_h_align
= GRUB_VIDEO_BITMAP_H_ALIGN_CENTER
;
101 view
->desktop_image_v_align
= GRUB_VIDEO_BITMAP_V_ALIGN_CENTER
;
102 view
->desktop_color
= default_bg_color
;
103 view
->terminal_box
= grub_gfxmenu_create_box (0, 0);
104 view
->title_text
= grub_strdup (_("GRUB Boot Menu"));
105 view
->progress_message_text
= 0;
106 view
->theme_path
= 0;
108 /* Set the timeout bar's frame. */
109 view
->progress_message_frame
.width
= view
->screen
.width
* 4 / 5;
110 view
->progress_message_frame
.height
= 50;
111 view
->progress_message_frame
.x
= view
->screen
.x
112 + (view
->screen
.width
- view
->progress_message_frame
.width
) / 2;
113 view
->progress_message_frame
.y
= view
->screen
.y
114 + view
->screen
.height
- 90 - 20 - view
->progress_message_frame
.height
;
116 if (grub_gfxmenu_view_load_theme (view
, theme_path
) != 0)
118 grub_gfxmenu_view_destroy (view
);
125 /* Destroy the view object. All used memory is freed. */
127 grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view
)
131 while (grub_gfxmenu_timeout_notifications
)
133 struct grub_gfxmenu_timeout_notify
*p
;
134 p
= grub_gfxmenu_timeout_notifications
;
135 grub_gfxmenu_timeout_notifications
= grub_gfxmenu_timeout_notifications
->next
;
138 grub_video_bitmap_destroy (view
->raw_desktop_image
);
139 grub_video_bitmap_destroy (view
->scaled_desktop_image
);
140 if (view
->terminal_box
)
141 view
->terminal_box
->destroy (view
->terminal_box
);
142 grub_free (view
->terminal_font_name
);
143 grub_free (view
->title_text
);
144 grub_free (view
->progress_message_text
);
145 grub_free (view
->theme_path
);
146 if (view
->menu_title_offset
)
147 grub_free (view
->menu_title_offset
);
149 view
->canvas
->component
.ops
->destroy (view
->canvas
);
154 redraw_background (grub_gfxmenu_view_t view
,
155 const grub_video_rect_t
*bounds
)
157 if (view
->scaled_desktop_image
)
159 struct grub_video_bitmap
*img
= view
->scaled_desktop_image
;
160 grub_video_blit_bitmap (img
, GRUB_VIDEO_BLIT_REPLACE
,
161 bounds
->x
, bounds
->y
,
162 bounds
->x
- view
->screen
.x
,
163 bounds
->y
- view
->screen
.y
,
164 bounds
->width
, bounds
->height
);
168 grub_video_fill_rect (grub_video_map_rgba_color (view
->desktop_color
),
169 bounds
->x
, bounds
->y
,
170 bounds
->width
, bounds
->height
);
175 draw_title (grub_gfxmenu_view_t view
)
177 if (! view
->title_text
)
180 /* Center the title. */
181 int title_width
= grub_font_get_string_width (view
->title_font
,
183 int x
= (view
->screen
.width
- title_width
) / 2;
184 int y
= 40 + grub_font_get_ascent (view
->title_font
);
185 grub_font_draw_string (view
->title_text
,
187 grub_video_map_rgba_color (view
->title_color
),
191 struct progress_value_data
199 struct grub_gfxmenu_timeout_notify
*grub_gfxmenu_timeout_notifications
;
202 update_timeouts (int visible
, int start
, int value
, int end
)
204 struct grub_gfxmenu_timeout_notify
*cur
;
206 for (cur
= grub_gfxmenu_timeout_notifications
; cur
; cur
= cur
->next
)
207 cur
->set_state (cur
->self
, visible
, start
, value
, end
);
211 redraw_timeouts (struct grub_gfxmenu_view
*view
)
213 struct grub_gfxmenu_timeout_notify
*cur
;
215 for (cur
= grub_gfxmenu_timeout_notifications
; cur
; cur
= cur
->next
)
217 grub_video_rect_t bounds
;
218 cur
->self
->ops
->get_bounds (cur
->self
, &bounds
);
219 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED
);
220 grub_gfxmenu_view_redraw (view
, &bounds
);
225 grub_gfxmenu_print_timeout (int timeout
, void *data
)
227 struct grub_gfxmenu_view
*view
= data
;
229 if (view
->first_timeout
== -1)
230 view
->first_timeout
= timeout
;
232 update_timeouts (1, -view
->first_timeout
, -timeout
, 0);
233 redraw_timeouts (view
);
234 grub_video_swap_buffers ();
235 if (view
->double_repaint
)
236 redraw_timeouts (view
);
240 grub_gfxmenu_clear_timeout (void *data
)
242 struct grub_gfxmenu_view
*view
= data
;
244 update_timeouts (0, 1, 0, 0);
245 redraw_timeouts (view
);
246 grub_video_swap_buffers ();
247 if (view
->double_repaint
)
248 redraw_timeouts (view
);
252 update_menu_visit (grub_gui_component_t component
,
255 grub_gfxmenu_view_t view
;
257 if (component
->ops
->is_instance (component
, "list"))
259 grub_gui_list_t list
= (grub_gui_list_t
) component
;
260 list
->ops
->set_view_info (list
, view
);
264 /* Update any boot menu components with the current menu model and
267 update_menu_components (grub_gfxmenu_view_t view
)
269 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
270 update_menu_visit
, view
);
274 refresh_menu_visit (grub_gui_component_t component
,
277 grub_gfxmenu_view_t view
;
279 if (component
->ops
->is_instance (component
, "list"))
281 grub_gui_list_t list
= (grub_gui_list_t
) component
;
282 list
->ops
->refresh_list (list
, view
);
286 /* Refresh list information (useful for submenus) */
288 refresh_menu_components (grub_gfxmenu_view_t view
)
290 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
291 refresh_menu_visit
, view
);
295 draw_message (grub_gfxmenu_view_t view
)
297 char *text
= view
->progress_message_text
;
298 grub_video_rect_t f
= view
->progress_message_frame
;
302 grub_font_t font
= view
->message_font
;
303 grub_video_color_t color
= grub_video_map_rgba_color (view
->message_color
);
306 grub_video_fill_rect (color
,
307 f
.x
-1, f
.y
-1, f
.width
+2, f
.height
+2);
309 grub_video_fill_rect (grub_video_map_rgba_color (view
->message_bg_color
),
310 f
.x
, f
.y
, f
.width
, f
.height
);
312 /* Center the text. */
313 int text_width
= grub_font_get_string_width (font
, text
);
314 int x
= f
.x
+ (f
.width
- text_width
) / 2;
315 int y
= (f
.y
+ (f
.height
- grub_font_get_descent (font
)) / 2
316 + grub_font_get_ascent (font
) / 2);
317 grub_font_draw_string (text
, font
, color
, x
, y
);
321 grub_gfxmenu_view_redraw (grub_gfxmenu_view_t view
,
322 const grub_video_rect_t
*region
)
324 if (grub_video_have_common_points (&view
->terminal_rect
, region
))
325 grub_gfxterm_schedule_repaint ();
327 grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY
);
328 grub_video_area_status_t area_status
;
329 grub_video_get_area_status (&area_status
);
330 if (area_status
== GRUB_VIDEO_AREA_ENABLED
)
331 grub_video_set_region (region
->x
, region
->y
,
332 region
->width
, region
->height
);
334 redraw_background (view
, region
);
336 view
->canvas
->component
.ops
->paint (view
->canvas
, region
);
338 if (grub_video_have_common_points (&view
->progress_message_frame
, region
))
341 if (area_status
== GRUB_VIDEO_AREA_ENABLED
)
342 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED
);
346 grub_gfxmenu_view_draw (grub_gfxmenu_view_t view
)
348 init_terminal (view
);
350 init_background (view
);
352 /* Clear the screen; there may be garbage left over in video memory. */
353 grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
354 view
->screen
.x
, view
->screen
.y
,
355 view
->screen
.width
, view
->screen
.height
);
356 grub_video_swap_buffers ();
357 if (view
->double_repaint
)
358 grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
359 view
->screen
.x
, view
->screen
.y
,
360 view
->screen
.width
, view
->screen
.height
);
362 refresh_menu_components (view
);
363 update_menu_components (view
);
365 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED
);
366 grub_gfxmenu_view_redraw (view
, &view
->screen
);
367 grub_video_swap_buffers ();
368 if (view
->double_repaint
)
370 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED
);
371 grub_gfxmenu_view_redraw (view
, &view
->screen
);
377 redraw_menu_visit (grub_gui_component_t component
,
380 grub_gfxmenu_view_t view
;
382 if (component
->ops
->is_instance (component
, "list"))
384 grub_video_rect_t bounds
;
386 component
->ops
->get_bounds (component
, &bounds
);
387 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED
);
388 grub_gfxmenu_view_redraw (view
, &bounds
);
392 extern int g_menu_update_mode
;
394 static void grub_gfxmenu_update_all(grub_gfxmenu_view_t view
)
396 grub_video_set_area_status(GRUB_VIDEO_AREA_DISABLED
);
397 grub_gfxmenu_view_redraw(view
, &view
->screen
);
401 grub_gfxmenu_redraw_menu (grub_gfxmenu_view_t view
)
403 update_menu_components (view
);
405 if (g_menu_update_mode
)
406 grub_gfxmenu_update_all(view
);
408 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
409 redraw_menu_visit
, view
);
411 grub_video_swap_buffers ();
412 if (view
->double_repaint
)
414 if (g_menu_update_mode
)
415 grub_gfxmenu_update_all(view
);
417 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
418 redraw_menu_visit
, view
);
424 grub_gfxmenu_set_chosen_entry (int entry
, void *data
)
426 grub_gfxmenu_view_t view
= data
;
428 view
->selected
= entry
;
429 grub_gfxmenu_redraw_menu (view
);
435 grub_gfxmenu_scroll_chosen_entry (void *data
, int diren
)
437 grub_gfxmenu_view_t view
= data
;
438 const char *item_title
;
442 if (!view
->menu
->size
)
445 item_title
= grub_menu_get_entry (view
->menu
, view
->selected
)->title
;
446 off
= view
->menu_title_offset
[view
->selected
] + diren
;
447 max
= grub_utf8_get_num_code (item_title
, grub_strlen(item_title
));
449 if (diren
== 1000000)
450 off
= (max
>= 20) ? (max
- 20) : 0;
456 view
->menu_title_offset
[view
->selected
] = off
;
457 grub_gfxmenu_redraw_menu (view
);
461 grub_gfxmenu_draw_terminal_box (void)
463 grub_gfxmenu_box_t term_box
;
465 term_box
= term_view
->terminal_box
;
469 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED
);
471 term_box
->set_content_size (term_box
, term_view
->terminal_rect
.width
,
472 term_view
->terminal_rect
.height
);
474 term_box
->draw (term_box
,
475 term_view
->terminal_rect
.x
- term_box
->get_left_pad (term_box
),
476 term_view
->terminal_rect
.y
- term_box
->get_top_pad (term_box
));
480 get_min_terminal (grub_font_t terminal_font
,
481 unsigned int border_width
,
482 unsigned int *min_terminal_width
,
483 unsigned int *min_terminal_height
)
485 struct grub_font_glyph
*glyph
;
486 glyph
= grub_font_get_glyph (terminal_font
, 'M');
487 *min_terminal_width
= (glyph
? glyph
->device_width
: 8) * 80
489 *min_terminal_height
= grub_font_get_max_char_height (terminal_font
) * 24
494 terminal_sanity_check (grub_gfxmenu_view_t view
)
496 if (!view
->need_to_check_sanity
)
499 /* terminal_font was checked before in the init_terminal function. */
500 grub_font_t terminal_font
= grub_font_get (view
->terminal_font_name
);
502 /* Non-negative numbers below. */
503 int scr_x
= view
->screen
.x
;
504 int scr_y
= view
->screen
.y
;
505 int scr_width
= view
->screen
.width
;
506 int scr_height
= view
->screen
.height
;
507 int term_x
= view
->terminal_rect
.x
;
508 int term_y
= view
->terminal_rect
.y
;
509 int term_width
= view
->terminal_rect
.width
;
510 int term_height
= view
->terminal_rect
.height
;
512 /* Check that border_width isn't too big. */
513 unsigned int border_width
= view
->terminal_border
;
514 unsigned int min_terminal_width
;
515 unsigned int min_terminal_height
;
516 get_min_terminal (terminal_font
, border_width
,
517 &min_terminal_width
, &min_terminal_height
);
518 if (border_width
> 3 && ((int) min_terminal_width
>= scr_width
519 || (int) min_terminal_height
>= scr_height
))
522 get_min_terminal (terminal_font
, border_width
,
523 &min_terminal_width
, &min_terminal_height
);
527 if (term_width
> scr_width
)
528 term_width
= scr_width
;
529 if (term_height
> scr_height
)
530 term_height
= scr_height
;
532 if (scr_width
<= (int) min_terminal_width
533 || scr_height
<= (int) min_terminal_height
)
535 /* The screen resulution is too low. Use all space, except a small border
536 to show the user, that it is a window. Then center the window. */
537 term_width
= scr_width
- 6 * border_width
;
538 term_height
= scr_height
- 6 * border_width
;
539 term_x
= scr_x
+ (scr_width
- term_width
) / 2;
540 term_y
= scr_y
+ (scr_height
- term_height
) / 2;
542 else if (term_width
< (int) min_terminal_width
543 || term_height
< (int) min_terminal_height
)
545 /* The screen resolution is big enough. Make sure, that terminal screen
546 dimensions aren't less than minimal values. Then center the window. */
547 term_width
= (int) min_terminal_width
;
548 term_height
= (int) min_terminal_height
;
549 term_x
= scr_x
+ (scr_width
- term_width
) / 2;
550 term_y
= scr_y
+ (scr_height
- term_height
) / 2;
553 /* At this point w and h are satisfying. */
554 if (term_x
+ term_width
> scr_width
)
555 term_x
= scr_width
- term_width
;
556 if (term_y
+ term_height
> scr_height
)
557 term_y
= scr_height
- term_height
;
559 /* Write down corrected data. */
560 view
->terminal_rect
.x
= (unsigned int) term_x
;
561 view
->terminal_rect
.y
= (unsigned int) term_y
;
562 view
->terminal_rect
.width
= (unsigned int) term_width
;
563 view
->terminal_rect
.height
= (unsigned int) term_height
;
564 view
->terminal_border
= border_width
;
566 view
->need_to_check_sanity
= 0;
570 init_terminal (grub_gfxmenu_view_t view
)
572 grub_font_t terminal_font
;
574 terminal_font
= grub_font_get (view
->terminal_font_name
);
577 grub_error (GRUB_ERR_BAD_FONT
, "no font loaded");
581 /* Check that terminal window size and position are sane. */
582 terminal_sanity_check (view
);
586 /* Note: currently there is no API for changing the gfxterm font
587 on the fly, so whatever font the initially loaded theme specifies
588 will be permanent. */
589 grub_gfxterm_set_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY
,
590 view
->terminal_rect
.x
,
591 view
->terminal_rect
.y
,
592 view
->terminal_rect
.width
,
593 view
->terminal_rect
.height
,
594 view
->double_repaint
,
596 view
->terminal_border
);
597 grub_gfxterm_decorator_hook
= grub_gfxmenu_draw_terminal_box
;
601 init_background (grub_gfxmenu_view_t view
)
603 if (view
->scaled_desktop_image
|| (!view
->raw_desktop_image
))
606 struct grub_video_bitmap
*scaled_bitmap
;
607 if (view
->desktop_image_scale_method
==
608 GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH
)
609 grub_video_bitmap_create_scaled (&scaled_bitmap
,
612 view
->raw_desktop_image
,
613 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST
);
615 grub_video_bitmap_scale_proportional (&scaled_bitmap
,
618 view
->raw_desktop_image
,
619 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST
,
620 view
->desktop_image_scale_method
,
621 view
->desktop_image_v_align
,
622 view
->desktop_image_h_align
);
625 view
->scaled_desktop_image
= scaled_bitmap
;
629 /* FIXME: previously notifications were displayed in special case.
633 /* Sets MESSAGE as the progress message for the view.
634 MESSAGE can be 0, in which case no message is displayed. */
636 set_progress_message (grub_gfxmenu_view_t view
, const char *message
)
638 grub_free (view
->progress_message_text
);
640 view
->progress_message_text
= grub_strdup (message
);
642 view
->progress_message_text
= 0;
646 notify_booting (grub_menu_entry_t entry
, void *userdata
)
648 grub_gfxmenu_view_t view
= (grub_gfxmenu_view_t
) userdata
;
650 char *s
= grub_malloc (100 + grub_strlen (entry
->title
));
654 grub_sprintf (s
, "Booting '%s'", entry
->title
);
655 set_progress_message (view
, s
);
657 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
658 grub_video_swap_buffers ();
659 if (view
->double_repaint
)
660 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
664 notify_fallback (grub_menu_entry_t entry
, void *userdata
)
666 grub_gfxmenu_view_t view
= (grub_gfxmenu_view_t
) userdata
;
668 char *s
= grub_malloc (100 + grub_strlen (entry
->title
));
672 grub_sprintf (s
, "Falling back to '%s'", entry
->title
);
673 set_progress_message (view
, s
);
675 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
676 grub_video_swap_buffers ();
677 if (view
->double_repaint
)
678 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
682 notify_execution_failure (void *userdata
__attribute__ ((unused
)))
687 static struct grub_menu_execute_callback execute_callback
=
689 .notify_booting
= notify_booting
,
690 .notify_fallback
= notify_fallback
,
691 .notify_failure
= notify_execution_failure