[gnome-software] GsHidingBox introduced



commit 22b47f1ae496abe2fa5d01742873559d058cbd36
Author: Rafal Luzynski <digitalfreak lingonborough com>
Date:   Tue Mar 17 01:49:54 2015 +0100

    GsHidingBox introduced
    
    GsHidingBox is a container which displays only as many children
    as its limited size allows. Also implements a minimum subset of
    features of GtkBox but is derived directly from GtkContainer rather
    than GtkBox. Its first application is to hide the popular tiles
    if there is not enough area on the screen to fit them all.
    
    This version allocates the extra space to the children so they
    expand and shrink as the box is resized.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=742211

 po/POTFILES.in           |    1 +
 src/Makefile.am          |    2 +
 src/gs-hiding-box.c      |  463 ++++++++++++++++++++++++++++++++++++++++++++++
 src/gs-hiding-box.h      |   50 +++++
 src/gs-shell-overview.c  |    8 +-
 src/gs-shell-overview.ui |    6 +-
 6 files changed, 522 insertions(+), 8 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index dd1044e..358f343 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -14,6 +14,7 @@ src/gs-category.c
 src/gs-dbus-helper.c
 [type: gettext/glade]src/gs-first-run-dialog.ui
 src/gs-feature-tile.c
+src/gs-hiding-box.c
 src/gs-history-dialog.c
 [type: gettext/glade]src/gs-history-dialog.ui
 src/gs-main.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 899aa53..e6be63e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -125,6 +125,8 @@ gnome_software_SOURCES =                            \
        gs-history-dialog.h                             \
        gs-box.h                                        \
        gs-box.c                                        \
+       gs-hiding-box.h                                 \
+       gs-hiding-box.c                                 \
        gs-page.c                                       \
        gs-page.h                                       \
        gs-plugin.c                                     \
diff --git a/src/gs-hiding-box.c b/src/gs-hiding-box.c
new file mode 100644
index 0000000..7d153d9
--- /dev/null
+++ b/src/gs-hiding-box.c
@@ -0,0 +1,463 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * 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 <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gs-hiding-box.h"
+
+enum {
+       PROP_0,
+       PROP_SPACING
+};
+
+struct _GsHidingBoxPrivate {
+       GList *children;
+
+       gint16 spacing;
+};
+
+struct _GsHidingBox {
+       GtkContainer parent;
+
+       /*< private >*/
+       GsHidingBoxPrivate *priv;
+};
+
+struct _GsHidingBoxClass {
+       GtkContainerClass parent_class;
+};
+
+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_ADD_PRIVATE (GsHidingBox)
+                        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);
+       GsHidingBoxPrivate *private = box->priv;
+
+       switch (prop_id) {
+       case PROP_SPACING:
+               g_value_set_int (value, private->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->priv->children = g_list_append (box->priv->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->priv->children; child; 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->priv->children = g_list_delete_link (box->priv->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->priv->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);
+       GsHidingBoxPrivate *private = box->priv;
+       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 = private->spacing;
+       gint children_size;
+       GtkAllocation clip, child_clip;
+
+       gtk_widget_set_allocation (widget, allocation);
+
+       nvis_children = 0;
+       for (child = box->priv->children; child; 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 = private->children; child; 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), 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 = private->children; child; 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 - 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 (i = 0, child = private->children; child; 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->priv->children; child; 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->priv->spacing && nvis_children > 1)
+               n += box->priv->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->priv->children; child; 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)
+{
+       GsHidingBoxPrivate *private;
+
+       box->priv = gs_hiding_box_get_instance_private (box);
+       private = box->priv;
+
+       gtk_widget_set_has_window (GTK_WIDGET (box), FALSE);
+       gtk_widget_set_redraw_on_allocate (GTK_WIDGET (box), FALSE);
+
+       private->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",
+                               /* 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));
+}
+
+/**
+ * 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)
+{
+       GsHidingBoxPrivate *private;
+
+       g_return_if_fail (GS_IS_HIDING_BOX (box));
+
+       private = box->priv;
+
+       if (private->spacing != spacing) {
+               private->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->priv->spacing;
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-hiding-box.h b/src/gs-hiding-box.h
new file mode 100644
index 0000000..ff4648a
--- /dev/null
+++ b/src/gs-hiding-box.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * 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 GS_HIDING_BOX_H_
+#define GS_HIDING_BOX_H_
+
+#include <gtk/gtk.h>
+
+#define GS_TYPE_HIDING_BOX             (gs_hiding_box_get_type())
+#define GS_HIDING_BOX(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), GS_TYPE_HIDING_BOX, GsHidingBox))
+#define GS_HIDING_BOX_CLASS(cls)       (G_TYPE_CHECK_CLASS_CAST((cls), GS_TYPE_HIDING_BOX, GsHidingBoxClass))
+#define GS_IS_HIDING_BOX(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), GS_TYPE_HIDING_BOX))
+#define GS_IS_HIDING_BOX_CLASS(cls)    (G_TYPE_CHECK_CLASS_TYPE((cls), GS_TYPE_HIDING_BOX))
+#define GS_HIDING_BOX_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), GS_TYPE_HIDING_BOX, 
GsHidingBoxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GsHidingBox            GsHidingBox;
+typedef struct _GsHidingBoxPrivate     GsHidingBoxPrivate;
+typedef struct _GsHidingBoxClass       GsHidingBoxClass;
+
+GType           gs_hiding_box_get_type         (void);
+GtkWidget      *gs_hiding_box_new              (void);
+void            gs_hiding_box_set_spacing      (GsHidingBox    *box,
+                                                gint            spacing);
+gint            gs_hiding_box_get_spacing      (GsHidingBox    *box);
+
+G_END_DECLS
+
+#endif /* GS_HIDING_BOX_H_ */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-shell-overview.c b/src/gs-shell-overview.c
index cfa42d4..d106a8f 100644
--- a/src/gs-shell-overview.c
+++ b/src/gs-shell-overview.c
@@ -152,7 +152,7 @@ gs_shell_overview_get_popular_cb (GObject *source_object,
                tile = gs_popular_tile_new (app);
                g_signal_connect (tile, "clicked",
                          G_CALLBACK (popular_tile_clicked), shell);
-               gtk_box_pack_start (GTK_BOX (priv->box_popular), tile, TRUE, TRUE, 0);
+               gtk_container_add (GTK_CONTAINER (priv->box_popular), tile);
        }
 
        priv->empty = FALSE;
@@ -209,7 +209,7 @@ gs_shell_overview_get_popular_rotating_cb (GObject *source_object,
                tile = gs_popular_tile_new (app);
                g_signal_connect (tile, "clicked",
                          G_CALLBACK (popular_tile_clicked), shell);
-               gtk_box_pack_start (GTK_BOX (priv->box_popular_rotating), tile, TRUE, TRUE, 0);
+               gtk_container_add (GTK_CONTAINER (priv->box_popular_rotating), tile);
        }
 
        priv->empty = FALSE;
@@ -512,10 +512,10 @@ gs_shell_overview_setup (GsShellOverview *shell_overview,
 
        for (i = 0; i < N_TILES; i++) {
                tile = gs_popular_tile_new (NULL);
-               gtk_box_pack_start (GTK_BOX (priv->box_popular), tile, TRUE, TRUE, 0);
+               gtk_container_add (GTK_CONTAINER (priv->box_popular), tile);
 
                tile = gs_popular_tile_new (NULL);
-               gtk_box_pack_start (GTK_BOX (priv->box_popular_rotating), tile, TRUE, TRUE, 0);
+               gtk_container_add (GTK_CONTAINER (priv->box_popular_rotating), tile);
        }
 
        /* chain up */
diff --git a/src/gs-shell-overview.ui b/src/gs-shell-overview.ui
index 0c0d1e1..2165a02 100644
--- a/src/gs-shell-overview.ui
+++ b/src/gs-shell-overview.ui
@@ -94,13 +94,12 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkBox" id="box_popular">
+                      <object class="GsHidingBox" id="box_popular">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="margin_start">12</property>
                         <property name="margin_end">12</property>
                         <property name="spacing">14</property>
-                        <property name="homogeneous">True</property>
                         <property name="valign">start</property>
                         <accessibility>
                           <relation target="popular_heading" type="labelled-by"/>
@@ -136,13 +135,12 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkBox" id="box_popular_rotating">
+                      <object class="GsHidingBox" id="box_popular_rotating">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="margin_start">12</property>
                         <property name="margin_end">12</property>
                         <property name="spacing">14</property>
-                        <property name="homogeneous">True</property>
                         <property name="valign">start</property>
                         <accessibility>
                           <relation target="popular_rotating_heading" type="labelled-by"/>


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