]>
Commit | Line | Data |
---|---|---|
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 | ||
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, | |
9a175884 VS |
35 | unsigned *minimal_width, |
36 | unsigned *minimal_height); | |
d920a32a CB |
37 | |
38 | struct 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 | ||
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, | |
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 | ||
160 | static void | |
161 | layout_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 | ||
233 | static void | |
947fa16c | 234 | box_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 | ||
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 | |
9a175884 | 281 | box_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 | ||
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 | } | |
d920a32a CB |
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 | ||
9a175884 VS |
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 | ||
d920a32a CB |
370 | static 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. */ | |
379 | static grub_gui_box_t | |
380 | box_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. */ | |
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 | } |