[gtk+/wip/csoriano/pathbar-prototype: 97/124] Add GtkHidingBox



commit ccfb68d22f739a67d5e99f584bad7a94962a0f2e
Author: Carlos Soriano <csoriano gnome org>
Date:   Wed Nov 4 14:36:29 2015 +0100

    Add GtkHidingBox
    
    We didn't have a way to hide children given the allocation size
    available set by the parent.
    This is an use case for the GtkPathBar, which will be rewrote in future
    patches where we will want to use a composite widget instead of a all
    custom allocation widget.
    
    For that, implement a container which hides widgets when the allocated
    size is smaller than the requested size by its children.
    The code is made by Rafał Lużyński for gnome-software and is adapted to
    Gtk+ standards.
    
    Now will follow a few patches improving the code and adding support
    for some features needed by the GtkPathBar.

 gtk/Makefile.am           |    2 +
 gtk/gtkhidingbox.c        |  477 +++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkhidingboxprivate.h |   69 +++++++
 3 files changed, 548 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 180c423..a93ea35 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -486,6 +486,7 @@ gtk_private_h_sources =             \
        gtkgestureswipeprivate.h        \
        gtkgesturezoomprivate.h \
        gtkheaderbarprivate.h   \
+       gtkhidingboxprivate.h   \
        gtkhslaprivate.h        \
        gtkiconcache.h          \
        gtkiconhelperprivate.h  \
@@ -778,6 +779,7 @@ gtk_base_c_sources =                \
        gtkglarea.c             \
        gtkgrid.c               \
        gtkheaderbar.c          \
+       gtkhidingbox.c          \
        gtkhsla.c               \
        gtkicon.c               \
        gtkiconcache.c          \
diff --git a/gtk/gtkhidingbox.c b/gtk/gtkhidingbox.c
new file mode 100644
index 0000000..1d959b5
--- /dev/null
+++ b/gtk/gtkhidingbox.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2015 Rafał Lużyński <digitalfreak lingonborough com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include "gtkhidingboxprivate.h"
+#include "gtkintl.h"
+#include "gtksizerequest.h"
+#include "gtkbuildable.h"
+
+struct _GtkHidingBoxPrivate
+{
+  GList *children;
+  gint16 spacing;
+};
+
+static void
+gtk_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 (GTK_HIDING_BOX (buildable), type);
+}
+
+static void
+gtk_hiding_box_buildable_init (GtkBuildableIface *iface)
+{
+  iface->add_child = gtk_hiding_box_buildable_add_child;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkHidingBox, gtk_hiding_box, GTK_TYPE_CONTAINER,
+                         G_ADD_PRIVATE (GtkHidingBox)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_hiding_box_buildable_init))
+
+enum {
+  PROP_0,
+  PROP_SPACING,
+  LAST_PROP
+};
+
+static void
+gtk_hiding_box_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GtkHidingBox *box = GTK_HIDING_BOX (object);
+
+  switch (prop_id)
+    {
+    case PROP_SPACING:
+      gtk_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
+gtk_hiding_box_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GtkHidingBox *box = GTK_HIDING_BOX (object);
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+
+  switch (prop_id)
+    {
+    case PROP_SPACING:
+      g_value_set_int (value, priv->spacing);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_hiding_box_add (GtkContainer *container,
+                    GtkWidget    *widget)
+{
+  GtkHidingBox *box = GTK_HIDING_BOX (container);
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+
+  priv->children = g_list_append (priv->children, widget);
+  gtk_widget_set_parent (widget, GTK_WIDGET (box));
+}
+
+static void
+gtk_hiding_box_remove (GtkContainer *container,
+                       GtkWidget    *widget)
+{
+  GList *child;
+  GtkHidingBox *box = GTK_HIDING_BOX (container);
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+
+  for (child = priv->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);
+          priv->children = g_list_delete_link (priv->children, child);
+
+          if (was_visible)
+            gtk_widget_queue_resize (GTK_WIDGET (container));
+
+          break;
+        }
+    }
+}
+
+static void
+gtk_hiding_box_forall (GtkContainer *container,
+                       gboolean      include_internals,
+                       GtkCallback   callback,
+                       gpointer      callback_data)
+{
+  GtkHidingBox *box = GTK_HIDING_BOX (container);
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+  GtkWidget *child;
+  GList *children;
+
+  children = priv->children;
+  while (children)
+    {
+      child = children->data;
+      children = children->next;
+      (* callback) (child, callback_data);
+    }
+}
+
+static void
+gtk_hiding_box_size_allocate (GtkWidget     *widget,
+                              GtkAllocation *allocation)
+{
+  GtkHidingBox *box = GTK_HIDING_BOX (widget);
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+  gint nvis_children;
+
+  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 = priv->spacing;
+  gint children_size;
+  GtkAllocation clip, child_clip;
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  nvis_children = 0;
+  for (child = priv->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 = priv->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 ("GtkHidingBox 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 ("GtkHidingBox 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), 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 = priv->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;
+    }
+
+  /*
+   * 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 (i = 0, child = priv->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
+gtk_hiding_box_get_preferred_width (GtkWidget *widget,
+                                    gint      *min,
+                                    gint      *nat)
+{
+  GtkHidingBox *box = GTK_HIDING_BOX (widget);
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+  gint cm, cn;
+  gint m, n;
+  GList *child;
+  gint nvis_children;
+  gboolean have_min = FALSE;
+
+  m = n = nvis_children = 0;
+  for (child = priv->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 (priv->spacing && nvis_children > 1)
+    n += priv->spacing * (nvis_children - 1);
+
+  if (min)
+    *min = m;
+  if (nat)
+    *nat = n;
+}
+
+static void
+gtk_hiding_box_get_preferred_height (GtkWidget *widget,
+                                     gint      *min,
+                                     gint      *nat)
+{
+  GtkHidingBox *box = GTK_HIDING_BOX (widget);
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+  gint m, n;
+  gint cm, cn;
+  GList *child;
+
+  m = n = 0;
+  for (child = priv->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
+gtk_hiding_box_init (GtkHidingBox *box)
+{
+  GtkHidingBoxPrivate *priv = gtk_hiding_box_get_instance_private (box);
+
+  gtk_widget_set_has_window (GTK_WIDGET (box), FALSE);
+  gtk_widget_set_redraw_on_allocate (GTK_WIDGET (box), FALSE);
+
+  priv->spacing = 0;
+}
+
+static void
+gtk_hiding_box_class_init (GtkHidingBoxClass *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 = gtk_hiding_box_set_property;
+  object_class->get_property = gtk_hiding_box_get_property;
+
+  widget_class->size_allocate = gtk_hiding_box_size_allocate;
+  widget_class->get_preferred_width = gtk_hiding_box_get_preferred_width;
+  widget_class->get_preferred_height = gtk_hiding_box_get_preferred_height;
+
+  container_class->add = gtk_hiding_box_add;
+  container_class->remove = gtk_hiding_box_remove;
+  container_class->forall = gtk_hiding_box_forall;
+
+  g_object_class_install_property (object_class,
+                                   PROP_SPACING,
+                                   g_param_spec_int ("spacing",
+                                                     /* TRANSLATORS: Here are 2 strings the same as in 
gtk/gtkbox.c
+                                                        in GTK+ project. Please use the same translation. */
+                                                     _("Spacing"),
+                                                     _("The amount of space between children"),
+                                                     0, G_MAXINT, 0,
+                                                     G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
+}
+
+/**
+ * gtk_hiding_box_new:
+ *
+ * Creates a new #GtkHidingBox.
+ *
+ * Returns: a new #GtkHidingBox.
+ **/
+GtkWidget *
+gtk_hiding_box_new (void)
+{
+  return g_object_new (GTK_TYPE_HIDING_BOX, NULL);
+}
+
+/**
+ * gtk_hiding_box_set_spacing:
+ * @box: a #GtkHidingBox
+ * @spacing: the number of pixels to put between children
+ *
+ * Sets the #GtkHidingBox:spacing property of @box, which is the
+ * number of pixels to place between children of @box.
+ */
+void
+gtk_hiding_box_set_spacing (GtkHidingBox *box,
+                            gint          spacing)
+{
+  GtkHidingBoxPrivate *priv ;
+
+  g_return_if_fail (GTK_IS_HIDING_BOX (box));
+
+  priv = gtk_hiding_box_get_instance_private (box);
+
+  if (priv->spacing != spacing)
+    {
+      priv->spacing = spacing;
+
+      g_object_notify (G_OBJECT (box), "spacing");
+
+      gtk_widget_queue_resize (GTK_WIDGET (box));
+    }
+}
+
+/**
+ * gtk_hiding_box_get_spacing:
+ * @box: a #GtkHidingBox
+ *
+ * Gets the value set by gtk_hiding_box_set_spacing().
+ *
+ * Returns: spacing between children
+ **/
+gint
+gtk_hiding_box_get_spacing (GtkHidingBox *box)
+{
+  GtkHidingBoxPrivate *priv ;
+
+  g_return_val_if_fail (GTK_IS_HIDING_BOX (box), 0);
+
+  priv = gtk_hiding_box_get_instance_private (box);
+
+  return priv->spacing;
+}
+
diff --git a/gtk/gtkhidingboxprivate.h b/gtk/gtkhidingboxprivate.h
new file mode 100644
index 0000000..89f360b
--- /dev/null
+++ b/gtk/gtkhidingboxprivate.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 Rafał Lużyński <digitalfreak lingonborough com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GTK_HIDING_BOX_PRIVATE_H__
+#define __GTK_HIDING_BOX_PRIVATE_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkcontainer.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_HIDING_BOX            (gtk_hiding_box_get_type())
+#define GTK_HIDING_BOX(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_HIDING_BOX, 
GtkHidingBox))
+#define GTK_HIDING_BOX_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_HIDING_BOX, 
GtkHidingBoxClass))
+#define GTK_IS_HIDING_BOX(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_HIDING_BOX))
+#define GTK_IS_HIDING_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_HIDING_BOX)
+#define GTK_HIDING_BOX_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_HIDING_BOX, 
GtkHidingBoxClass))
+
+typedef struct _GtkHidingBox GtkHidingBox;
+typedef struct _GtkHidingBoxClass GtkHidingBoxClass;
+typedef struct _GtkHidingBoxPrivate GtkHidingBoxPrivate;
+
+struct _GtkHidingBoxClass
+{
+  GtkContainerClass parent;
+
+  /* Padding for future expansion */
+  gpointer reserved[10];
+};
+
+struct _GtkHidingBox
+{
+  GtkContainer parent_instance;
+};
+
+GDK_AVAILABLE_IN_3_20
+GType             gtk_hiding_box_get_type         (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_20
+GtkWidget        *gtk_hiding_box_new              (void);
+GDK_AVAILABLE_IN_3_20
+void              gtk_hiding_box_set_spacing      (GtkHidingBox      *box,
+                                                   gint               spacing);
+GDK_AVAILABLE_IN_3_20
+gint              gtk_hiding_box_get_spacing      (GtkHidingBox      *box);
+
+G_END_DECLS
+
+#endif /* GTK_HIDING_BOX_PRIVATE_H_ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]