]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - GRUB2/MOD_SRC/grub-2.04/grub-core/gfxmenu/view.c
support scrolling menu entry's text (#1539)
[Ventoy.git] / GRUB2 / MOD_SRC / grub-2.04 / grub-core / gfxmenu / view.c
1 /* view.c - Graphical menu interface MVC view. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008 Free Software Foundation, Inc.
5 *
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.
10 *
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.
15 *
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/>.
18 */
19
20 #include <grub/types.h>
21 #include <grub/file.h>
22 #include <grub/misc.h>
23 #include <grub/mm.h>
24 #include <grub/err.h>
25 #include <grub/dl.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>
41
42 static void
43 init_terminal (grub_gfxmenu_view_t view);
44 static void
45 init_background (grub_gfxmenu_view_t view);
46 static grub_gfxmenu_view_t term_view;
47
48 /* Create a new view object, loading the theme specified by THEME_PATH and
49 associating MODEL with the view. */
50 grub_gfxmenu_view_t
51 grub_gfxmenu_view_new (const char *theme_path,
52 int width, int height)
53 {
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;
58
59 view = grub_malloc (sizeof (*view));
60 if (! view)
61 return 0;
62
63 while (grub_gfxmenu_timeout_notifications)
64 {
65 struct grub_gfxmenu_timeout_notify *p;
66 p = grub_gfxmenu_timeout_notifications;
67 grub_gfxmenu_timeout_notifications = grub_gfxmenu_timeout_notifications->next;
68 grub_free (p);
69 }
70
71 view->screen.x = 0;
72 view->screen.y = 0;
73 view->screen.width = width;
74 view->screen.height = height;
75
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;
84
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);
88
89 view->canvas = 0;
90
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;
107
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;
115
116 if (grub_gfxmenu_view_load_theme (view, theme_path) != 0)
117 {
118 grub_gfxmenu_view_destroy (view);
119 return 0;
120 }
121
122 return view;
123 }
124
125 /* Destroy the view object. All used memory is freed. */
126 void
127 grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
128 {
129 if (!view)
130 return;
131 while (grub_gfxmenu_timeout_notifications)
132 {
133 struct grub_gfxmenu_timeout_notify *p;
134 p = grub_gfxmenu_timeout_notifications;
135 grub_gfxmenu_timeout_notifications = grub_gfxmenu_timeout_notifications->next;
136 grub_free (p);
137 }
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);
148 if (view->canvas)
149 view->canvas->component.ops->destroy (view->canvas);
150 grub_free (view);
151 }
152
153 static void
154 redraw_background (grub_gfxmenu_view_t view,
155 const grub_video_rect_t *bounds)
156 {
157 if (view->scaled_desktop_image)
158 {
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);
165 }
166 else
167 {
168 grub_video_fill_rect (grub_video_map_rgba_color (view->desktop_color),
169 bounds->x, bounds->y,
170 bounds->width, bounds->height);
171 }
172 }
173
174 static void
175 draw_title (grub_gfxmenu_view_t view)
176 {
177 if (! view->title_text)
178 return;
179
180 /* Center the title. */
181 int title_width = grub_font_get_string_width (view->title_font,
182 view->title_text);
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,
186 view->title_font,
187 grub_video_map_rgba_color (view->title_color),
188 x, y);
189 }
190
191 struct progress_value_data
192 {
193 int visible;
194 int start;
195 int end;
196 int value;
197 };
198
199 struct grub_gfxmenu_timeout_notify *grub_gfxmenu_timeout_notifications;
200
201 static void
202 update_timeouts (int visible, int start, int value, int end)
203 {
204 struct grub_gfxmenu_timeout_notify *cur;
205
206 for (cur = grub_gfxmenu_timeout_notifications; cur; cur = cur->next)
207 cur->set_state (cur->self, visible, start, value, end);
208 }
209
210 static void
211 redraw_timeouts (struct grub_gfxmenu_view *view)
212 {
213 struct grub_gfxmenu_timeout_notify *cur;
214
215 for (cur = grub_gfxmenu_timeout_notifications; cur; cur = cur->next)
216 {
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);
221 }
222 }
223
224 void
225 grub_gfxmenu_print_timeout (int timeout, void *data)
226 {
227 struct grub_gfxmenu_view *view = data;
228
229 if (view->first_timeout == -1)
230 view->first_timeout = timeout;
231
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);
237 }
238
239 void
240 grub_gfxmenu_clear_timeout (void *data)
241 {
242 struct grub_gfxmenu_view *view = data;
243
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);
249 }
250
251 static void
252 update_menu_visit (grub_gui_component_t component,
253 void *userdata)
254 {
255 grub_gfxmenu_view_t view;
256 view = userdata;
257 if (component->ops->is_instance (component, "list"))
258 {
259 grub_gui_list_t list = (grub_gui_list_t) component;
260 list->ops->set_view_info (list, view);
261 }
262 }
263
264 /* Update any boot menu components with the current menu model and
265 theme path. */
266 static void
267 update_menu_components (grub_gfxmenu_view_t view)
268 {
269 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
270 update_menu_visit, view);
271 }
272
273 static void
274 refresh_menu_visit (grub_gui_component_t component,
275 void *userdata)
276 {
277 grub_gfxmenu_view_t view;
278 view = userdata;
279 if (component->ops->is_instance (component, "list"))
280 {
281 grub_gui_list_t list = (grub_gui_list_t) component;
282 list->ops->refresh_list (list, view);
283 }
284 }
285
286 /* Refresh list information (useful for submenus) */
287 static void
288 refresh_menu_components (grub_gfxmenu_view_t view)
289 {
290 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
291 refresh_menu_visit, view);
292 }
293
294 static void
295 draw_message (grub_gfxmenu_view_t view)
296 {
297 char *text = view->progress_message_text;
298 grub_video_rect_t f = view->progress_message_frame;
299 if (! text)
300 return;
301
302 grub_font_t font = view->message_font;
303 grub_video_color_t color = grub_video_map_rgba_color (view->message_color);
304
305 /* Border. */
306 grub_video_fill_rect (color,
307 f.x-1, f.y-1, f.width+2, f.height+2);
308 /* Fill. */
309 grub_video_fill_rect (grub_video_map_rgba_color (view->message_bg_color),
310 f.x, f.y, f.width, f.height);
311
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);
318 }
319
320 void
321 grub_gfxmenu_view_redraw (grub_gfxmenu_view_t view,
322 const grub_video_rect_t *region)
323 {
324 if (grub_video_have_common_points (&view->terminal_rect, region))
325 grub_gfxterm_schedule_repaint ();
326
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);
333
334 redraw_background (view, region);
335 if (view->canvas)
336 view->canvas->component.ops->paint (view->canvas, region);
337 draw_title (view);
338 if (grub_video_have_common_points (&view->progress_message_frame, region))
339 draw_message (view);
340
341 if (area_status == GRUB_VIDEO_AREA_ENABLED)
342 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED);
343 }
344
345 void
346 grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
347 {
348 init_terminal (view);
349
350 init_background (view);
351
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);
361
362 refresh_menu_components (view);
363 update_menu_components (view);
364
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)
369 {
370 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED);
371 grub_gfxmenu_view_redraw (view, &view->screen);
372 }
373
374 }
375
376 static void
377 redraw_menu_visit (grub_gui_component_t component,
378 void *userdata)
379 {
380 grub_gfxmenu_view_t view;
381 view = userdata;
382 if (component->ops->is_instance (component, "list"))
383 {
384 grub_video_rect_t bounds;
385
386 component->ops->get_bounds (component, &bounds);
387 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED);
388 grub_gfxmenu_view_redraw (view, &bounds);
389 }
390 }
391
392 extern int g_menu_update_mode;
393
394 static void grub_gfxmenu_update_all(grub_gfxmenu_view_t view)
395 {
396 grub_video_set_area_status(GRUB_VIDEO_AREA_DISABLED);
397 grub_gfxmenu_view_redraw(view, &view->screen);
398 }
399
400 void
401 grub_gfxmenu_redraw_menu (grub_gfxmenu_view_t view)
402 {
403 update_menu_components (view);
404
405 if (g_menu_update_mode)
406 grub_gfxmenu_update_all(view);
407 else
408 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
409 redraw_menu_visit, view);
410
411 grub_video_swap_buffers ();
412 if (view->double_repaint)
413 {
414 if (g_menu_update_mode)
415 grub_gfxmenu_update_all(view);
416 else
417 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
418 redraw_menu_visit, view);
419 }
420 }
421
422
423 void
424 grub_gfxmenu_set_chosen_entry (int entry, void *data)
425 {
426 grub_gfxmenu_view_t view = data;
427
428 view->selected = entry;
429 grub_gfxmenu_redraw_menu (view);
430
431
432 }
433
434 void
435 grub_gfxmenu_scroll_chosen_entry (void *data, int diren)
436 {
437 grub_gfxmenu_view_t view = data;
438 const char *item_title;
439 int off;
440
441 if (!view->menu->size)
442 return;
443
444 item_title = grub_menu_get_entry (view->menu, view->selected)->title;
445 off = view->menu_title_offset[view->selected] + diren;
446
447 if (off < 0
448 || off > grub_utf8_get_num_code (item_title, grub_strlen(item_title)))
449 return;
450
451 view->menu_title_offset[view->selected] = off;
452 grub_gfxmenu_redraw_menu (view);
453 }
454
455 static void
456 grub_gfxmenu_draw_terminal_box (void)
457 {
458 grub_gfxmenu_box_t term_box;
459
460 term_box = term_view->terminal_box;
461 if (!term_box)
462 return;
463
464 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED);
465
466 term_box->set_content_size (term_box, term_view->terminal_rect.width,
467 term_view->terminal_rect.height);
468
469 term_box->draw (term_box,
470 term_view->terminal_rect.x - term_box->get_left_pad (term_box),
471 term_view->terminal_rect.y - term_box->get_top_pad (term_box));
472 }
473
474 static void
475 get_min_terminal (grub_font_t terminal_font,
476 unsigned int border_width,
477 unsigned int *min_terminal_width,
478 unsigned int *min_terminal_height)
479 {
480 struct grub_font_glyph *glyph;
481 glyph = grub_font_get_glyph (terminal_font, 'M');
482 *min_terminal_width = (glyph? glyph->device_width : 8) * 80
483 + 2 * border_width;
484 *min_terminal_height = grub_font_get_max_char_height (terminal_font) * 24
485 + 2 * border_width;
486 }
487
488 static void
489 terminal_sanity_check (grub_gfxmenu_view_t view)
490 {
491 if (!view->need_to_check_sanity)
492 return;
493
494 /* terminal_font was checked before in the init_terminal function. */
495 grub_font_t terminal_font = grub_font_get (view->terminal_font_name);
496
497 /* Non-negative numbers below. */
498 int scr_x = view->screen.x;
499 int scr_y = view->screen.y;
500 int scr_width = view->screen.width;
501 int scr_height = view->screen.height;
502 int term_x = view->terminal_rect.x;
503 int term_y = view->terminal_rect.y;
504 int term_width = view->terminal_rect.width;
505 int term_height = view->terminal_rect.height;
506
507 /* Check that border_width isn't too big. */
508 unsigned int border_width = view->terminal_border;
509 unsigned int min_terminal_width;
510 unsigned int min_terminal_height;
511 get_min_terminal (terminal_font, border_width,
512 &min_terminal_width, &min_terminal_height);
513 if (border_width > 3 && ((int) min_terminal_width >= scr_width
514 || (int) min_terminal_height >= scr_height))
515 {
516 border_width = 3;
517 get_min_terminal (terminal_font, border_width,
518 &min_terminal_width, &min_terminal_height);
519 }
520
521 /* Sanity checks. */
522 if (term_width > scr_width)
523 term_width = scr_width;
524 if (term_height > scr_height)
525 term_height = scr_height;
526
527 if (scr_width <= (int) min_terminal_width
528 || scr_height <= (int) min_terminal_height)
529 {
530 /* The screen resulution is too low. Use all space, except a small border
531 to show the user, that it is a window. Then center the window. */
532 term_width = scr_width - 6 * border_width;
533 term_height = scr_height - 6 * border_width;
534 term_x = scr_x + (scr_width - term_width) / 2;
535 term_y = scr_y + (scr_height - term_height) / 2;
536 }
537 else if (term_width < (int) min_terminal_width
538 || term_height < (int) min_terminal_height)
539 {
540 /* The screen resolution is big enough. Make sure, that terminal screen
541 dimensions aren't less than minimal values. Then center the window. */
542 term_width = (int) min_terminal_width;
543 term_height = (int) min_terminal_height;
544 term_x = scr_x + (scr_width - term_width) / 2;
545 term_y = scr_y + (scr_height - term_height) / 2;
546 }
547
548 /* At this point w and h are satisfying. */
549 if (term_x + term_width > scr_width)
550 term_x = scr_width - term_width;
551 if (term_y + term_height > scr_height)
552 term_y = scr_height - term_height;
553
554 /* Write down corrected data. */
555 view->terminal_rect.x = (unsigned int) term_x;
556 view->terminal_rect.y = (unsigned int) term_y;
557 view->terminal_rect.width = (unsigned int) term_width;
558 view->terminal_rect.height = (unsigned int) term_height;
559 view->terminal_border = border_width;
560
561 view->need_to_check_sanity = 0;
562 }
563
564 static void
565 init_terminal (grub_gfxmenu_view_t view)
566 {
567 grub_font_t terminal_font;
568
569 terminal_font = grub_font_get (view->terminal_font_name);
570 if (!terminal_font)
571 {
572 grub_error (GRUB_ERR_BAD_FONT, "no font loaded");
573 return;
574 }
575
576 /* Check that terminal window size and position are sane. */
577 terminal_sanity_check (view);
578
579 term_view = view;
580
581 /* Note: currently there is no API for changing the gfxterm font
582 on the fly, so whatever font the initially loaded theme specifies
583 will be permanent. */
584 grub_gfxterm_set_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
585 view->terminal_rect.x,
586 view->terminal_rect.y,
587 view->terminal_rect.width,
588 view->terminal_rect.height,
589 view->double_repaint,
590 terminal_font,
591 view->terminal_border);
592 grub_gfxterm_decorator_hook = grub_gfxmenu_draw_terminal_box;
593 }
594
595 static void
596 init_background (grub_gfxmenu_view_t view)
597 {
598 if (view->scaled_desktop_image || (!view->raw_desktop_image))
599 return;
600
601 struct grub_video_bitmap *scaled_bitmap;
602 if (view->desktop_image_scale_method ==
603 GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH)
604 grub_video_bitmap_create_scaled (&scaled_bitmap,
605 view->screen.width,
606 view->screen.height,
607 view->raw_desktop_image,
608 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
609 else
610 grub_video_bitmap_scale_proportional (&scaled_bitmap,
611 view->screen.width,
612 view->screen.height,
613 view->raw_desktop_image,
614 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST,
615 view->desktop_image_scale_method,
616 view->desktop_image_v_align,
617 view->desktop_image_h_align);
618 if (! scaled_bitmap)
619 return;
620 view->scaled_desktop_image = scaled_bitmap;
621
622 }
623
624 /* FIXME: previously notifications were displayed in special case.
625 Is it necessary?
626 */
627 #if 0
628 /* Sets MESSAGE as the progress message for the view.
629 MESSAGE can be 0, in which case no message is displayed. */
630 static void
631 set_progress_message (grub_gfxmenu_view_t view, const char *message)
632 {
633 grub_free (view->progress_message_text);
634 if (message)
635 view->progress_message_text = grub_strdup (message);
636 else
637 view->progress_message_text = 0;
638 }
639
640 static void
641 notify_booting (grub_menu_entry_t entry, void *userdata)
642 {
643 grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
644
645 char *s = grub_malloc (100 + grub_strlen (entry->title));
646 if (!s)
647 return;
648
649 grub_sprintf (s, "Booting '%s'", entry->title);
650 set_progress_message (view, s);
651 grub_free (s);
652 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
653 grub_video_swap_buffers ();
654 if (view->double_repaint)
655 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
656 }
657
658 static void
659 notify_fallback (grub_menu_entry_t entry, void *userdata)
660 {
661 grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
662
663 char *s = grub_malloc (100 + grub_strlen (entry->title));
664 if (!s)
665 return;
666
667 grub_sprintf (s, "Falling back to '%s'", entry->title);
668 set_progress_message (view, s);
669 grub_free (s);
670 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
671 grub_video_swap_buffers ();
672 if (view->double_repaint)
673 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
674 }
675
676 static void
677 notify_execution_failure (void *userdata __attribute__ ((unused)))
678 {
679 }
680
681
682 static struct grub_menu_execute_callback execute_callback =
683 {
684 .notify_booting = notify_booting,
685 .notify_fallback = notify_fallback,
686 .notify_failure = notify_execution_failure
687 };
688
689 #endif