summaryrefslogtreecommitdiffstats
path: root/grub-core/gfxmenu/gui_box.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/gfxmenu/gui_box.c')
-rw-r--r--grub-core/gfxmenu/gui_box.c428
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);
+}