summaryrefslogtreecommitdiffstats
path: root/src/gs-hiding-box.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gs-hiding-box.c')
-rw-r--r--src/gs-hiding-box.c421
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;
+}