]> git.proxmox.com Git - grub2.git/blob - gfxmenu/gui_box.c
Merge mainline into bidi
[grub2.git] / gfxmenu / gui_box.c
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
25 struct component_node
26 {
27 grub_gui_component_t component;
28 struct component_node *next;
29 struct component_node *prev;
30 };
31
32 typedef struct grub_gui_box *grub_gui_box_t;
33
34 typedef void (*layout_func_t) (grub_gui_box_t self, int modify_layout,
35 unsigned *minimal_width,
36 unsigned *minimal_height);
37
38 struct grub_gui_box
39 {
40 struct grub_gui_container container;
41
42 grub_gui_container_t parent;
43 grub_video_rect_t bounds;
44 char *id;
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
54 static void
55 box_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
73 static const char *
74 box_get_id (void *vself)
75 {
76 grub_gui_box_t self = vself;
77 return self->id;
78 }
79
80 static int
81 box_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
87 static void
88 layout_horizontally (grub_gui_box_t self, int modify_layout,
89 unsigned *min_width, unsigned *min_height)
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;
95 unsigned w = 0, mwfrac = 0, h = 0, x = 0;
96 grub_fixed_signed_t wfrac = 0;
97 int bogus_frac = 0;
98
99 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
100 {
101 grub_gui_component_t c = cur->component;
102 unsigned mw = 0, mh = 0;
103
104 if (c->ops->get_minimal_size)
105 c->ops->get_minimal_size (c, &mw, &mh);
106
107 if (c->h > (signed) h)
108 h = c->h;
109 if (mh > h)
110 h = mh;
111 wfrac += c->wfrac;
112 w += c->w;
113 if (mw - c->w > 0)
114 mwfrac += mw - c->w;
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)
122 *min_width = grub_fixed_sfs_divide (w, GRUB_FIXED_1 - wfrac);
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 {
136 grub_video_rect_t r;
137 grub_gui_component_t c = cur->component;
138 unsigned mw = 0, mh = 0;
139
140 r.x = x;
141 r.y = 0;
142 r.height = h;
143
144 if (c->ops->get_minimal_size)
145 c->ops->get_minimal_size (c, &mw, &mh);
146
147 r.width = c->w;
148 if (!bogus_frac)
149 r.width += grub_fixed_sfs_multiply (self->bounds.width, c->wfrac);
150
151 if (r.width < mw)
152 r.width = mw;
153
154 c->ops->set_bounds (c, &r);
155
156 x += r.width;
157 }
158 }
159
160 static void
161 layout_vertically (grub_gui_box_t self, int modify_layout,
162 unsigned *min_width, unsigned *min_height)
163 {
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. */
166
167 struct component_node *cur;
168 unsigned h = 0, mhfrac = 0, w = 0, y = 0;
169 grub_fixed_signed_t hfrac = 0;
170 int bogus_frac = 0;
171
172 for (cur = self->chead.next; cur != &self->ctail; cur = cur->next)
173 {
174 grub_gui_component_t c = cur->component;
175 unsigned mw = 0, mh = 0;
176
177 if (c->ops->get_minimal_size)
178 c->ops->get_minimal_size (c, &mw, &mh);
179
180 if (c->w > (signed) w)
181 w = c->w;
182 if (mw > w)
183 w = mw;
184 hfrac += c->hfrac;
185 h += c->h;
186 if (mh - c->h > 0)
187 mhfrac += mh - c->h;
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)
195 *min_height = grub_fixed_sfs_divide (h, GRUB_FIXED_1 - hfrac);
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 {
209 grub_video_rect_t r;
210 grub_gui_component_t c = cur->component;
211 unsigned mw = 0, mh = 0;
212
213 r.x = 0;
214 r.y = y;
215 r.width = w;
216
217 if (c->ops->get_minimal_size)
218 c->ops->get_minimal_size (c, &mw, &mh);
219
220 r.height = c->h;
221 if (!bogus_frac)
222 r.height += grub_fixed_sfs_multiply (self->bounds.height, c->hfrac);
223
224 if (r.height < mh)
225 r.height = mh;
226
227 c->ops->set_bounds (c, &r);
228
229 y += r.height;
230 }
231 }
232
233 static void
234 box_paint (void *vself, const grub_video_rect_t *region)
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;
244 comp->ops->paint (comp, region);
245 }
246 grub_gui_restore_viewport (&vpsave);
247 }
248
249 static void
250 box_set_parent (void *vself, grub_gui_container_t parent)
251 {
252 grub_gui_box_t self = vself;
253 self->parent = parent;
254 }
255
256 static grub_gui_container_t
257 box_get_parent (void *vself)
258 {
259 grub_gui_box_t self = vself;
260 return self->parent;
261 }
262
263 static void
264 box_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
271 static void
272 box_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. */
280 static void
281 box_get_minimal_size (void *vself, unsigned *width, unsigned *height)
282 {
283 grub_gui_box_t self = vself;
284 self->layout_func (self, 0, width, height); /* Just calculate the size. */
285 }
286
287 static grub_err_t
288 box_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 }
303
304 return grub_errno;
305 }
306
307 static void
308 box_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
326 static void
327 box_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
346 static void
347 box_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
356 static 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
370 static struct grub_gui_container_ops box_ops =
371 {
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. */
379 static grub_gui_box_t
380 box_new (layout_func_t layout_func)
381 {
382 grub_gui_box_t box;
383 box = grub_zalloc (sizeof (*box));
384 if (! box)
385 return 0;
386 box->container.ops = &box_ops;
387 box->container.component.ops = &box_comp_ops;
388 box->chead.next = &box->ctail;
389 box->ctail.prev = &box->chead;
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. */
398 grub_gui_container_t
399 grub_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. */
408 grub_gui_container_t
409 grub_gui_vbox_new (void)
410 {
411 return (grub_gui_container_t) box_new (layout_vertically);
412 }