]> git.proxmox.com Git - grub2.git/blame - gfxmenu/gui_box.c
merge mainline into rescue-efi
[grub2.git] / gfxmenu / gui_box.c
CommitLineData
d920a32a
CB
1/* gui_box.c - GUI container that stack components. */
2/*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008,2009 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/mm.h>
21#include <grub/misc.h>
22#include <grub/gui.h>
23#include <grub/gui_string_util.h>
24
25struct component_node
26{
27 grub_gui_component_t component;
28 struct component_node *next;
29 struct component_node *prev;
30};
31
32typedef struct grub_gui_box *grub_gui_box_t;
33
34typedef void (*layout_func_t) (grub_gui_box_t self, int modify_layout,
9a175884
VS
35 unsigned *minimal_width,
36 unsigned *minimal_height);
d920a32a
CB
37
38struct grub_gui_box
39{
9a175884 40 struct grub_gui_container container;
d920a32a
CB
41
42 grub_gui_container_t parent;
43 grub_video_rect_t bounds;
44 char *id;
d920a32a
CB
45
46 /* Doubly linked list of components with dummy head & tail nodes. */
47 struct component_node chead;
48 struct component_node ctail;
49
50 /* The layout function: differs for vertical and horizontal boxes. */
51 layout_func_t layout_func;
52};
53
54static void
55box_destroy (void *vself)
56{
57 grub_gui_box_t self = vself;
58 struct component_node *cur;
59 struct component_node *next;
60 for (cur = self->chead.next; cur != &self->ctail; cur = next)
61 {
62 /* Copy the 'next' pointer, since we need it for the next iteration,
63 and we're going to free the memory it is stored in. */
64 next = cur->next;
65 /* Destroy the child component. */
66 cur->component->ops->destroy (cur->component);
67 /* Free the linked list node. */
68 grub_free (cur);
69 }
70 grub_free (self);
71}
72
73static const char *
74box_get_id (void *vself)
75{
76 grub_gui_box_t self = vself;
77 return self->id;
78}
79
80static int
81box_is_instance (void *vself __attribute__((unused)), const char *type)
82{
83 return (grub_strcmp (type, "component") == 0
84 || grub_strcmp (type, "container") == 0);
85}
86
87static void
88layout_horizontally (grub_gui_box_t self, int modify_layout,
9a175884 89 unsigned *min_width, unsigned *min_height)
d920a32a
CB
90{
91 /* Start at the left (chead) and set the x coordinates as we go right. */
92 /* All components have their width set to the box's width. */
93
94 struct component_node *cur;
9a175884 95 unsigned w = 0, mwfrac = 0, h = 0, x = 0;
b9da1700 96 grub_fixed_signed_t wfrac = 0;
9a175884
VS
97 int bogus_frac = 0;
98
d920a32a
CB
99 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
100 {
101 grub_gui_component_t c = cur->component;
9a175884
VS
102 unsigned mw = 0, mh = 0;
103
104 if (c->ops->get_minimal_size)
105 c->ops->get_minimal_size (c, &mw, &mh);
106
b9da1700 107 if (c->h > (signed) h)
9a175884
VS
108 h = c->h;
109 if (mh > h)
110 h = mh;
b9da1700
VS
111 wfrac += c->wfrac;
112 w += c->w;
113 if (mw - c->w > 0)
114 mwfrac += mw - c->w;
9a175884
VS
115 }
116 if (wfrac > GRUB_FIXED_1 || (w > 0 && wfrac == GRUB_FIXED_1))
117 bogus_frac = 1;
118
119 if (min_width)
120 {
121 if (wfrac < GRUB_FIXED_1)
b9da1700 122 *min_width = grub_fixed_sfs_divide (w, GRUB_FIXED_1 - wfrac);
9a175884
VS
123 else
124 *min_width = w;
125 if (*min_width < w + mwfrac)
126 *min_width = w + mwfrac;
127 }
128 if (min_height)
129 *min_height = h;
130
131 if (!modify_layout)
132 return;
133
134 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
135 {
d920a32a 136 grub_video_rect_t r;
9a175884
VS
137 grub_gui_component_t c = cur->component;
138 unsigned mw = 0, mh = 0;
d920a32a 139
9a175884
VS
140 r.x = x;
141 r.y = 0;
9a175884 142 r.height = h;
d920a32a 143
9a175884
VS
144 if (c->ops->get_minimal_size)
145 c->ops->get_minimal_size (c, &mw, &mh);
d920a32a 146
b9da1700
VS
147 r.width = c->w;
148 if (!bogus_frac)
149 r.width += grub_fixed_sfs_multiply (self->bounds.width, c->wfrac);
9a175884
VS
150
151 if (r.width < mw)
152 r.width = mw;
153
154 c->ops->set_bounds (c, &r);
d920a32a
CB
155
156 x += r.width;
157 }
d920a32a
CB
158}
159
160static void
161layout_vertically (grub_gui_box_t self, int modify_layout,
9a175884 162 unsigned *min_width, unsigned *min_height)
d920a32a 163{
9a175884
VS
164 /* Start at the top (chead) and set the y coordinates as we go rdown. */
165 /* All components have their height set to the box's height. */
d920a32a
CB
166
167 struct component_node *cur;
9a175884 168 unsigned h = 0, mhfrac = 0, w = 0, y = 0;
b9da1700 169 grub_fixed_signed_t hfrac = 0;
9a175884
VS
170 int bogus_frac = 0;
171
d920a32a
CB
172 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
173 {
174 grub_gui_component_t c = cur->component;
9a175884
VS
175 unsigned mw = 0, mh = 0;
176
177 if (c->ops->get_minimal_size)
178 c->ops->get_minimal_size (c, &mw, &mh);
179
b9da1700 180 if (c->w > (signed) w)
9a175884
VS
181 w = c->w;
182 if (mw > w)
183 w = mw;
b9da1700
VS
184 hfrac += c->hfrac;
185 h += c->h;
186 if (mh - c->h > 0)
187 mhfrac += mh - c->h;
9a175884
VS
188 }
189 if (hfrac > GRUB_FIXED_1 || (h > 0 && hfrac == GRUB_FIXED_1))
190 bogus_frac = 1;
191
192 if (min_height)
193 {
194 if (hfrac < GRUB_FIXED_1)
b9da1700 195 *min_height = grub_fixed_sfs_divide (h, GRUB_FIXED_1 - hfrac);
9a175884
VS
196 else
197 *min_height = h;
198 if (*min_height < h + mhfrac)
199 *min_height = h + mhfrac;
200 }
201 if (min_width)
202 *min_width = w;
203
204 if (!modify_layout)
205 return;
206
207 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
208 {
d920a32a 209 grub_video_rect_t r;
9a175884
VS
210 grub_gui_component_t c = cur->component;
211 unsigned mw = 0, mh = 0;
d920a32a 212
9a175884
VS
213 r.x = 0;
214 r.y = y;
215 r.width = w;
d920a32a 216
9a175884
VS
217 if (c->ops->get_minimal_size)
218 c->ops->get_minimal_size (c, &mw, &mh);
d920a32a 219
b9da1700
VS
220 r.height = c->h;
221 if (!bogus_frac)
222 r.height += grub_fixed_sfs_multiply (self->bounds.height, c->hfrac);
9a175884
VS
223
224 if (r.height < mh)
225 r.height = mh;
226
227 c->ops->set_bounds (c, &r);
d920a32a
CB
228
229 y += r.height;
230 }
d920a32a
CB
231}
232
233static void
947fa16c 234box_paint (void *vself, const grub_video_rect_t *region)
d920a32a
CB
235{
236 grub_gui_box_t self = vself;
237 struct component_node *cur;
238 grub_video_rect_t vpsave;
239
240 grub_gui_set_viewport (&self->bounds, &vpsave);
241 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
242 {
243 grub_gui_component_t comp = cur->component;
947fa16c 244 comp->ops->paint (comp, region);
d920a32a
CB
245 }
246 grub_gui_restore_viewport (&vpsave);
247}
248
249static void
250box_set_parent (void *vself, grub_gui_container_t parent)
251{
252 grub_gui_box_t self = vself;
253 self->parent = parent;
254}
255
256static grub_gui_container_t
257box_get_parent (void *vself)
258{
259 grub_gui_box_t self = vself;
260 return self->parent;
261}
262
263static void
264box_set_bounds (void *vself, const grub_video_rect_t *bounds)
265{
266 grub_gui_box_t self = vself;
267 self->bounds = *bounds;
268 self->layout_func (self, 1, 0, 0); /* Relayout the children. */
269}
270
271static void
272box_get_bounds (void *vself, grub_video_rect_t *bounds)
273{
274 grub_gui_box_t self = vself;
275 *bounds = self->bounds;
276}
277
278/* The box's preferred size is based on the preferred sizes
279 of its children. */
280static void
9a175884 281box_get_minimal_size (void *vself, unsigned *width, unsigned *height)
d920a32a
CB
282{
283 grub_gui_box_t self = vself;
284 self->layout_func (self, 0, width, height); /* Just calculate the size. */
d920a32a
CB
285}
286
287static grub_err_t
288box_set_property (void *vself, const char *name, const char *value)
289{
290 grub_gui_box_t self = vself;
291 if (grub_strcmp (name, "id") == 0)
292 {
293 grub_free (self->id);
294 if (value)
295 {
296 self->id = grub_strdup (value);
297 if (! self->id)
298 return grub_errno;
299 }
300 else
301 self->id = 0;
302 }
d920a32a
CB
303
304 return grub_errno;
305}
306
307static void
308box_add (void *vself, grub_gui_component_t comp)
309{
310 grub_gui_box_t self = vself;
311 struct component_node *node;
312 node = grub_malloc (sizeof (*node));
313 if (! node)
314 return; /* Note: probably should handle the error. */
315 node->component = comp;
316 /* Insert the node before the tail. */
317 node->prev = self->ctail.prev;
318 node->prev->next = node;
319 node->next = &self->ctail;
320 node->next->prev = node;
321
322 comp->ops->set_parent (comp, (grub_gui_container_t) self);
323 self->layout_func (self, 1, 0, 0); /* Relayout the children. */
324}
325
326static void
327box_remove (void *vself, grub_gui_component_t comp)
328{
329 grub_gui_box_t self = vself;
330 struct component_node *cur;
331 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
332 {
333 if (cur->component == comp)
334 {
335 /* Unlink 'cur' from the list. */
336 cur->prev->next = cur->next;
337 cur->next->prev = cur->prev;
338 /* Free the node's memory (but don't destroy the component). */
339 grub_free (cur);
340 /* Must not loop again, since 'cur' would be dereferenced! */
341 return;
342 }
343 }
344}
345
346static void
347box_iterate_children (void *vself,
348 grub_gui_component_callback cb, void *userdata)
349{
350 grub_gui_box_t self = vself;
351 struct component_node *cur;
352 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
353 cb (cur->component, userdata);
354}
355
9a175884
VS
356static struct grub_gui_component_ops box_comp_ops =
357 {
358 .destroy = box_destroy,
359 .get_id = box_get_id,
360 .is_instance = box_is_instance,
361 .paint = box_paint,
362 .set_parent = box_set_parent,
363 .get_parent = box_get_parent,
364 .set_bounds = box_set_bounds,
365 .get_bounds = box_get_bounds,
366 .get_minimal_size = box_get_minimal_size,
367 .set_property = box_set_property
368 };
369
d920a32a
CB
370static struct grub_gui_container_ops box_ops =
371{
d920a32a
CB
372 .add = box_add,
373 .remove = box_remove,
374 .iterate_children = box_iterate_children
375};
376
377/* Box constructor. Specify the appropriate layout function to create
378 a horizontal or vertical stacking box. */
379static grub_gui_box_t
380box_new (layout_func_t layout_func)
381{
382 grub_gui_box_t box;
9a175884 383 box = grub_zalloc (sizeof (*box));
d920a32a
CB
384 if (! box)
385 return 0;
9a175884
VS
386 box->container.ops = &box_ops;
387 box->container.component.ops = &box_comp_ops;
d920a32a 388 box->chead.next = &box->ctail;
d920a32a 389 box->ctail.prev = &box->chead;
d920a32a
CB
390 box->layout_func = layout_func;
391 return box;
392}
393
394/* Create a new container that stacks its child components horizontally,
395 from left to right. Each child get a width corresponding to its
396 preferred width. The height of each child is set the maximum of the
397 preferred heights of all children. */
398grub_gui_container_t
399grub_gui_hbox_new (void)
400{
401 return (grub_gui_container_t) box_new (layout_horizontally);
402}
403
404/* Create a new container that stacks its child components verticallyj,
405 from top to bottom. Each child get a height corresponding to its
406 preferred height. The width of each child is set the maximum of the
407 preferred widths of all children. */
408grub_gui_container_t
409grub_gui_vbox_new (void)
410{
411 return (grub_gui_container_t) box_new (layout_vertically);
412}