diff options
Diffstat (limited to 'src/gs-hiding-box.c')
-rw-r--r-- | src/gs-hiding-box.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/src/gs-hiding-box.c b/src/gs-hiding-box.c new file mode 100644 index 0000000..bada8d8 --- /dev/null +++ b/src/gs-hiding-box.c @@ -0,0 +1,421 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2015 Rafał Lużyński <digitalfreak@lingonborough.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include <glib/gi18n.h> + +#include "gs-hiding-box.h" + +enum { + PROP_0, + PROP_SPACING +}; + +struct _GsHidingBox +{ + GtkContainer parent_instance; + + GList *children; + gint spacing; +}; + +static void +gs_hiding_box_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type) +{ + if (!type) + gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); + else + GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GS_HIDING_BOX (buildable), type); +} + +static void +gs_hiding_box_buildable_init (GtkBuildableIface *iface) +{ + iface->add_child = gs_hiding_box_buildable_add_child; +} + +G_DEFINE_TYPE_WITH_CODE (GsHidingBox, gs_hiding_box, GTK_TYPE_CONTAINER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gs_hiding_box_buildable_init)) + +static void +gs_hiding_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsHidingBox *box = GS_HIDING_BOX (object); + + switch (prop_id) { + case PROP_SPACING: + gs_hiding_box_set_spacing (box, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_hiding_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsHidingBox *box = GS_HIDING_BOX (object); + + switch (prop_id) { + case PROP_SPACING: + g_value_set_int (value, box->spacing); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_hiding_box_add (GtkContainer *container, GtkWidget *widget) +{ + GsHidingBox *box = GS_HIDING_BOX (container); + + box->children = g_list_append (box->children, widget); + gtk_widget_set_parent (widget, GTK_WIDGET (box)); +} + +static void +gs_hiding_box_remove (GtkContainer *container, GtkWidget *widget) +{ + GList *child; + GsHidingBox *box = GS_HIDING_BOX (container); + + for (child = box->children; child != NULL; child = child->next) { + if (child->data == widget) { + gboolean was_visible = gtk_widget_get_visible (widget) && + gtk_widget_get_child_visible (widget); + + gtk_widget_unparent (widget); + box->children = g_list_delete_link (box->children, child); + + if (was_visible) + gtk_widget_queue_resize (GTK_WIDGET (container)); + + break; + } + } +} + +static void +gs_hiding_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GsHidingBox *box = GS_HIDING_BOX (container); + GtkWidget *child; + GList *children; + + children = box->children; + while (children) { + child = children->data; + children = children->next; + (* callback) (child, callback_data); + } +} + +static void +gs_hiding_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + GsHidingBox *box = GS_HIDING_BOX (widget); + gint nvis_children = 0; + + GtkTextDirection direction; + GtkAllocation child_allocation; + GtkRequestedSize *sizes; + + gint size; + gint extra = 0; + gint n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */ + gint x = 0, i; + GList *child; + GtkWidget *child_widget; + gint spacing = box->spacing; + gint children_size; + GtkAllocation clip, child_clip; + + gtk_widget_set_allocation (widget, allocation); + + for (child = box->children; child != NULL; child = child->next) { + if (gtk_widget_get_visible (child->data)) + ++nvis_children; + } + + /* If there is no visible child, simply return. */ + if (nvis_children == 0) + return; + + direction = gtk_widget_get_direction (widget); + sizes = g_newa (GtkRequestedSize, nvis_children); + + size = allocation->width; + children_size = -spacing; + /* Retrieve desired size for visible children. */ + for (i = 0, child = box->children; child != NULL; child = child->next) { + + child_widget = GTK_WIDGET (child->data); + if (!gtk_widget_get_visible (child_widget)) + continue; + + gtk_widget_get_preferred_width_for_height (child_widget, + allocation->height, + &sizes[i].minimum_size, + &sizes[i].natural_size); + + /* Assert the api is working properly */ + if (sizes[i].minimum_size < 0) + g_error ("GsHidingBox child %s minimum width: %d < 0 for height %d", + gtk_widget_get_name (child_widget), + sizes[i].minimum_size, allocation->height); + + if (sizes[i].natural_size < sizes[i].minimum_size) + g_error ("GsHidingBox child %s natural width: %d < minimum %d for height %d", + gtk_widget_get_name (child_widget), + sizes[i].natural_size, sizes[i].minimum_size, + allocation->height); + + children_size += sizes[i].minimum_size + spacing; + if (i > 0 && children_size > allocation->width) + break; + + size -= sizes[i].minimum_size; + sizes[i].data = child_widget; + + i++; + } + nvis_children = i; + + /* Bring children up to size first */ + size = gtk_distribute_natural_allocation (MAX (0, size), (guint) nvis_children, sizes); + /* Only now we can subtract the spacings */ + size -= (nvis_children - 1) * spacing; + + if (nvis_children > 1) { + extra = size / nvis_children; + n_extra_widgets = size % nvis_children; + } + + x = allocation->x; + for (i = 0, child = box->children; child != NULL; child = child->next) { + + child_widget = GTK_WIDGET (child->data); + if (!gtk_widget_get_visible (child_widget)) + continue; + + /* Hide the overflowing children even if they have visible=TRUE */ + if (i >= nvis_children) { + while (child) { + gtk_widget_set_child_visible (child->data, FALSE); + child = child->next; + } + break; + } + + child_allocation.x = x; + child_allocation.y = allocation->y; + child_allocation.width = sizes[i].minimum_size + extra; + child_allocation.height = allocation->height; + if (n_extra_widgets) { + ++child_allocation.width; + --n_extra_widgets; + } + if (direction == GTK_TEXT_DIR_RTL) { + child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width; + } + + /* Let this child be visible */ + gtk_widget_set_child_visible (child_widget, TRUE); + gtk_widget_size_allocate (child_widget, &child_allocation); + x += child_allocation.width + spacing; + ++i; + } + + /* + * The code below is inspired by _gtk_widget_set_simple_clip. + * Note: Here we ignore the "box-shadow" CSS property of the + * hiding box because we don't use it. + */ + clip = *allocation; + if (gtk_widget_get_has_window (widget)) { + clip.x = clip.y = 0; + } + + for (child = box->children; child != NULL; child = child->next) { + child_widget = GTK_WIDGET (child->data); + if (gtk_widget_get_visible (child_widget) && + gtk_widget_get_child_visible (child_widget)) { + gtk_widget_get_clip (child_widget, &child_clip); + gdk_rectangle_union (&child_clip, &clip, &clip); + } + } + + if (gtk_widget_get_has_window (widget)) { + clip.x += allocation->x; + clip.y += allocation->y; + } + gtk_widget_set_clip (widget, &clip); +} + +static void +gs_hiding_box_get_preferred_width (GtkWidget *widget, gint *min, gint *nat) +{ + GsHidingBox *box = GS_HIDING_BOX (widget); + gint cm, cn; + gint m, n; + GList *child; + gint nvis_children; + gboolean have_min = FALSE; + + m = n = nvis_children = 0; + for (child = box->children; child != NULL; child = child->next) { + if (!gtk_widget_is_visible (child->data)) + continue; + + ++nvis_children; + gtk_widget_get_preferred_width (child->data, &cm, &cn); + /* Minimum is a minimum of the first visible child */ + if (!have_min) { + m = cm; + have_min = TRUE; + } + /* Natural is a sum of all visible children */ + n += cn; + } + + /* Natural must also include the spacing */ + if (box->spacing && nvis_children > 1) + n += box->spacing * (nvis_children - 1); + + if (min) + *min = m; + if (nat) + *nat = n; +} + +static void +gs_hiding_box_get_preferred_height (GtkWidget *widget, gint *min, gint *nat) +{ + gint m, n; + gint cm, cn; + GList *child; + + GsHidingBox *box = GS_HIDING_BOX (widget); + m = n = 0; + for (child = box->children; child != NULL; child = child->next) { + if (!gtk_widget_is_visible (child->data)) + continue; + + gtk_widget_get_preferred_height (child->data, &cm, &cn); + m = MAX (m, cm); + n = MAX (n, cn); + } + + if (min) + *min = m; + if (nat) + *nat = n; +} + +static void +gs_hiding_box_init (GsHidingBox *box) +{ + gtk_widget_set_has_window (GTK_WIDGET (box), FALSE); + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (box), FALSE); + + box->spacing = 0; +} + +static void +gs_hiding_box_class_init (GsHidingBoxClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class); + + object_class->set_property = gs_hiding_box_set_property; + object_class->get_property = gs_hiding_box_get_property; + + widget_class->size_allocate = gs_hiding_box_size_allocate; + widget_class->get_preferred_width = gs_hiding_box_get_preferred_width; + widget_class->get_preferred_height = gs_hiding_box_get_preferred_height; + + container_class->add = gs_hiding_box_add; + container_class->remove = gs_hiding_box_remove; + container_class->forall = gs_hiding_box_forall; + + g_object_class_install_property (object_class, + PROP_SPACING, + g_param_spec_int ("spacing", + "Spacing", + "The amount of space between children", + 0, G_MAXINT, 0, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); +} + +/** + * gs_hiding_box_new: + * + * Creates a new #GsHidingBox. + * + * Returns: a new #GsHidingBox. + **/ +GtkWidget * +gs_hiding_box_new (void) +{ + return g_object_new (GS_TYPE_HIDING_BOX, NULL); +} + +/** + * gs_hiding_box_set_spacing: + * @box: a #GsHidingBox + * @spacing: the number of pixels to put between children + * + * Sets the #GsHidingBox:spacing property of @box, which is the + * number of pixels to place between children of @box. + */ +void +gs_hiding_box_set_spacing (GsHidingBox *box, gint spacing) +{ + g_return_if_fail (GS_IS_HIDING_BOX (box)); + + if (box->spacing != spacing) { + box->spacing = spacing; + + g_object_notify (G_OBJECT (box), "spacing"); + + gtk_widget_queue_resize (GTK_WIDGET (box)); + } +} + +/** + * gs_hiding_box_get_spacing: + * @box: a #GsHidingBox + * + * Gets the value set by gs_hiding_box_set_spacing(). + * + * Returns: spacing between children + **/ +gint +gs_hiding_box_get_spacing (GsHidingBox *box) +{ + g_return_val_if_fail (GS_IS_HIDING_BOX (box), 0); + + return box->spacing; +} |