diff options
Diffstat (limited to 'grub-core/gfxmenu/gui_box.c')
-rw-r--r-- | grub-core/gfxmenu/gui_box.c | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/grub-core/gfxmenu/gui_box.c b/grub-core/gfxmenu/gui_box.c new file mode 100644 index 0000000..37bab3f --- /dev/null +++ b/grub-core/gfxmenu/gui_box.c @@ -0,0 +1,428 @@ +/* gui_box.c - GUI container that stack components. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/gui.h> +#include <grub/gui_string_util.h> + +struct component_node +{ + grub_gui_component_t component; + struct component_node *next; + struct component_node *prev; +}; + +typedef struct grub_gui_box *grub_gui_box_t; + +typedef void (*layout_func_t) (grub_gui_box_t self, int modify_layout, + unsigned *minimal_width, + unsigned *minimal_height); + +struct grub_gui_box +{ + struct grub_gui_container container; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + + /* Doubly linked list of components with dummy head & tail nodes. */ + struct component_node chead; + struct component_node ctail; + + /* The layout function: differs for vertical and horizontal boxes. */ + layout_func_t layout_func; +}; + +static void +box_destroy (void *vself) +{ + grub_gui_box_t self = vself; + struct component_node *cur; + struct component_node *next; + for (cur = self->chead.next; cur != &self->ctail; cur = next) + { + /* Copy the 'next' pointer, since we need it for the next iteration, + and we're going to free the memory it is stored in. */ + next = cur->next; + /* Destroy the child component. */ + cur->component->ops->destroy (cur->component); + /* Free the linked list node. */ + grub_free (cur); + } + grub_free (self); +} + +static const char * +box_get_id (void *vself) +{ + grub_gui_box_t self = vself; + return self->id; +} + +static int +box_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return (grub_strcmp (type, "component") == 0 + || grub_strcmp (type, "container") == 0); +} + +static void +layout_horizontally (grub_gui_box_t self, int modify_layout, + unsigned *min_width, unsigned *min_height) +{ + /* Start at the left (chead) and set the x coordinates as we go right. */ + /* All components have their width set to the box's width. */ + + struct component_node *cur; + unsigned w = 0, mwfrac = 0, h = 0, x = 0; + grub_fixed_signed_t wfrac = 0; + int bogus_frac = 0; + + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_gui_component_t c = cur->component; + unsigned mw = 0, mh = 0; + + if (c->ops->get_minimal_size) + c->ops->get_minimal_size (c, &mw, &mh); + + if (c->h > (signed) h) + h = c->h; + if (mh > h) + h = mh; + wfrac += c->wfrac; + w += c->w; + if (mw - c->w > 0) + mwfrac += mw - c->w; + } + if (wfrac > GRUB_FIXED_1 || (w > 0 && wfrac == GRUB_FIXED_1)) + bogus_frac = 1; + + if (min_width) + { + if (wfrac < GRUB_FIXED_1) + *min_width = grub_fixed_sfs_divide (w, GRUB_FIXED_1 - wfrac); + else + *min_width = w; + if (*min_width < w + mwfrac) + *min_width = w + mwfrac; + } + if (min_height) + *min_height = h; + + if (!modify_layout) + return; + + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_video_rect_t r; + grub_gui_component_t c = cur->component; + unsigned mw = 0, mh = 0; + + r.x = x; + r.y = 0; + r.height = h; + + if (c->ops->get_minimal_size) + c->ops->get_minimal_size (c, &mw, &mh); + + r.width = c->w; + if (!bogus_frac) + r.width += grub_fixed_sfs_multiply (self->bounds.width, c->wfrac); + + if (r.width < mw) + r.width = mw; + + c->ops->set_bounds (c, &r); + + x += r.width; + } +} + +static void +layout_vertically (grub_gui_box_t self, int modify_layout, + unsigned *min_width, unsigned *min_height) +{ + /* Start at the top (chead) and set the y coordinates as we go rdown. */ + /* All components have their height set to the box's height. */ + + struct component_node *cur; + unsigned h = 0, mhfrac = 0, w = 0, y = 0; + grub_fixed_signed_t hfrac = 0; + int bogus_frac = 0; + + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_gui_component_t c = cur->component; + unsigned mw = 0, mh = 0; + + if (c->ops->get_minimal_size) + c->ops->get_minimal_size (c, &mw, &mh); + + if (c->w > (signed) w) + w = c->w; + if (mw > w) + w = mw; + hfrac += c->hfrac; + h += c->h; + if (mh - c->h > 0) + mhfrac += mh - c->h; + } + if (hfrac > GRUB_FIXED_1 || (h > 0 && hfrac == GRUB_FIXED_1)) + bogus_frac = 1; + + if (min_height) + { + if (hfrac < GRUB_FIXED_1) + *min_height = grub_fixed_sfs_divide (h, GRUB_FIXED_1 - hfrac); + else + *min_height = h; + if (*min_height < h + mhfrac) + *min_height = h + mhfrac; + } + if (min_width) + *min_width = w; + + if (!modify_layout) + return; + + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_video_rect_t r; + grub_gui_component_t c = cur->component; + unsigned mw = 0, mh = 0; + + r.x = 0; + r.y = y; + r.width = w; + + if (c->ops->get_minimal_size) + c->ops->get_minimal_size (c, &mw, &mh); + + r.height = c->h; + if (!bogus_frac) + r.height += grub_fixed_sfs_multiply (self->bounds.height, c->hfrac); + + if (r.height < mh) + r.height = mh; + + c->ops->set_bounds (c, &r); + + y += r.height; + } +} + +static void +box_paint (void *vself, const grub_video_rect_t *region) +{ + grub_gui_box_t self = vself; + + struct component_node *cur; + grub_video_rect_t vpsave; + + grub_video_area_status_t box_area_status; + grub_video_get_area_status (&box_area_status); + + grub_gui_set_viewport (&self->bounds, &vpsave); + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_gui_component_t comp = cur->component; + grub_video_rect_t r; + comp->ops->get_bounds(comp, &r); + + if (!grub_video_have_common_points (region, &r)) + continue; + + /* Paint the child. */ + if (box_area_status == GRUB_VIDEO_AREA_ENABLED + && grub_video_bounds_inside_region (&r, region)) + grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED); + comp->ops->paint (comp, region); + if (box_area_status == GRUB_VIDEO_AREA_ENABLED) + grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED); + } + grub_gui_restore_viewport (&vpsave); +} + +static void +box_set_parent (void *vself, grub_gui_container_t parent) +{ + grub_gui_box_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +box_get_parent (void *vself) +{ + grub_gui_box_t self = vself; + return self->parent; +} + +static void +box_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + grub_gui_box_t self = vself; + self->bounds = *bounds; + self->layout_func (self, 1, 0, 0); /* Relayout the children. */ +} + +static void +box_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + grub_gui_box_t self = vself; + *bounds = self->bounds; +} + +/* The box's preferred size is based on the preferred sizes + of its children. */ +static void +box_get_minimal_size (void *vself, unsigned *width, unsigned *height) +{ + grub_gui_box_t self = vself; + self->layout_func (self, 0, width, height); /* Just calculate the size. */ +} + +static grub_err_t +box_set_property (void *vself, const char *name, const char *value) +{ + grub_gui_box_t self = vself; + if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + { + self->id = grub_strdup (value); + if (! self->id) + return grub_errno; + } + else + self->id = 0; + } + + return grub_errno; +} + +static void +box_add (void *vself, grub_gui_component_t comp) +{ + grub_gui_box_t self = vself; + struct component_node *node; + node = grub_malloc (sizeof (*node)); + if (! node) + return; /* Note: probably should handle the error. */ + node->component = comp; + /* Insert the node before the tail. */ + node->prev = self->ctail.prev; + node->prev->next = node; + node->next = &self->ctail; + node->next->prev = node; + + comp->ops->set_parent (comp, (grub_gui_container_t) self); + self->layout_func (self, 1, 0, 0); /* Relayout the children. */ +} + +static void +box_remove (void *vself, grub_gui_component_t comp) +{ + grub_gui_box_t self = vself; + struct component_node *cur; + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + if (cur->component == comp) + { + /* Unlink 'cur' from the list. */ + cur->prev->next = cur->next; + cur->next->prev = cur->prev; + /* Free the node's memory (but don't destroy the component). */ + grub_free (cur); + /* Must not loop again, since 'cur' would be dereferenced! */ + return; + } + } +} + +static void +box_iterate_children (void *vself, + grub_gui_component_callback cb, void *userdata) +{ + grub_gui_box_t self = vself; + struct component_node *cur; + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + cb (cur->component, userdata); +} + +static struct grub_gui_component_ops box_comp_ops = + { + .destroy = box_destroy, + .get_id = box_get_id, + .is_instance = box_is_instance, + .paint = box_paint, + .set_parent = box_set_parent, + .get_parent = box_get_parent, + .set_bounds = box_set_bounds, + .get_bounds = box_get_bounds, + .get_minimal_size = box_get_minimal_size, + .set_property = box_set_property + }; + +static struct grub_gui_container_ops box_ops = +{ + .add = box_add, + .remove = box_remove, + .iterate_children = box_iterate_children +}; + +/* Box constructor. Specify the appropriate layout function to create + a horizontal or vertical stacking box. */ +static grub_gui_box_t +box_new (layout_func_t layout_func) +{ + grub_gui_box_t box; + box = grub_zalloc (sizeof (*box)); + if (! box) + return 0; + box->container.ops = &box_ops; + box->container.component.ops = &box_comp_ops; + box->chead.next = &box->ctail; + box->ctail.prev = &box->chead; + box->layout_func = layout_func; + return box; +} + +/* Create a new container that stacks its child components horizontally, + from left to right. Each child get a width corresponding to its + preferred width. The height of each child is set the maximum of the + preferred heights of all children. */ +grub_gui_container_t +grub_gui_hbox_new (void) +{ + return (grub_gui_container_t) box_new (layout_horizontally); +} + +/* Create a new container that stacks its child components verticallyj, + from top to bottom. Each child get a height corresponding to its + preferred height. The width of each child is set the maximum of the + preferred widths of all children. */ +grub_gui_container_t +grub_gui_vbox_new (void) +{ + return (grub_gui_container_t) box_new (layout_vertically); +} |