[gnome-calendar] Add GcalOverflowBin and GcalOverflowBinLayout



commit ca0a5de0f921978b128de4b94e31884a531bf64a
Author: Adrien Plazas <kekun plazas laposte net>
Date:   Thu May 5 10:26:42 2022 +0200

    Add GcalOverflowBin and GcalOverflowBinLayout
    
    This is a bin that purposefully can let its child overflow the
    allocation, based on the size request mode. This will be used
    to allow the event widget to reach smaller widths.

 src/gui/gcal-overflow-bin-layout.c | 228 ++++++++++++++++++++++++++++
 src/gui/gcal-overflow-bin-layout.h |  33 +++++
 src/gui/gcal-overflow-bin.c        | 297 +++++++++++++++++++++++++++++++++++++
 src/gui/gcal-overflow-bin.h        |  44 ++++++
 src/gui/meson.build                |   2 +
 5 files changed, 604 insertions(+)
---
diff --git a/src/gui/gcal-overflow-bin-layout.c b/src/gui/gcal-overflow-bin-layout.c
new file mode 100644
index 00000000..7391f481
--- /dev/null
+++ b/src/gui/gcal-overflow-bin-layout.c
@@ -0,0 +1,228 @@
+/* gcal-overflow-bin-layout.c
+ *
+ * Copyright (C) 2022 Purism SPC
+ *
+ * gnome-calendar 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.
+ *
+ * gnome-calendar 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/**
+ * GcalOverflowBinLayout:
+ *
+ * `GcalOverflowBinLayout` is a `GtkLayoutManager` subclass useful for create "bins" of
+ * widgets.
+ *
+ * `GcalOverflowBinLayout` will stack each child of a widget on top of each other,
+ * using the [property@Gtk.Widget:hexpand], [property@Gtk.Widget:vexpand],
+ * [property@Gtk.Widget:halign], and [property@Gtk.Widget:valign] properties
+ * of each child to determine where they should be positioned.
+ */
+
+#include "gcal-overflow-bin-layout.h"
+
+struct _GcalOverflowBinLayout
+{
+  GtkLayoutManager parent_instance;
+
+  GtkSizeRequestMode request_mode;
+};
+
+G_DEFINE_TYPE (GcalOverflowBinLayout, gcal_overflow_bin_layout, GTK_TYPE_LAYOUT_MANAGER)
+
+/*
+ * GtkLayoutManager overrides
+ */
+
+static void
+gcal_overflow_bin_layout_measure (GtkLayoutManager *layout_manager,
+                                  GtkWidget        *widget,
+                                  GtkOrientation    orientation,
+                                  gint              for_size,
+                                  gint             *minimum,
+                                  gint             *natural,
+                                  gint             *minimum_baseline,
+                                  gint             *natural_baseline)
+{
+  GtkSizeRequestMode request_mode;
+  GtkWidget *child;
+
+  request_mode = gtk_widget_get_request_mode (widget);
+
+  for (child = gtk_widget_get_first_child (widget);
+       child != NULL;
+       child = gtk_widget_get_next_sibling (child))
+    {
+      gint child_min_baseline = -1;
+      gint child_nat_baseline = -1;
+      gint child_min = 0;
+      gint child_nat = 0;
+
+      if (!gtk_widget_should_layout (child))
+        continue;
+
+      gtk_widget_measure (child, orientation, for_size,
+                          &child_min, &child_nat,
+                          &child_min_baseline, &child_nat_baseline);
+
+      *minimum = MAX (*minimum, child_min);
+      *natural = MAX (*natural, child_nat);
+
+      if (child_min_baseline > -1)
+        *minimum_baseline = MAX (*minimum_baseline, child_min_baseline);
+      if (child_nat_baseline > -1)
+        *natural_baseline = MAX (*natural_baseline, child_nat_baseline);
+    }
+
+  switch (request_mode)
+    {
+    case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
+      if (orientation == GTK_ORIENTATION_VERTICAL)
+        {
+          *minimum = 0;
+          *minimum_baseline = -1;
+        }
+      break;
+
+    case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
+      if (orientation == GTK_ORIENTATION_HORIZONTAL)
+        {
+          *minimum = 0;
+          *minimum_baseline = -1;
+        }
+      break;
+
+    case GTK_SIZE_REQUEST_CONSTANT_SIZE:
+      *minimum = 0;
+      *minimum_baseline = -1;
+      break;
+    }
+}
+
+static void
+gcal_overflow_bin_layout_allocate (GtkLayoutManager *layout_manager,
+                                   GtkWidget        *widget,
+                                   gint              width,
+                                   gint              height,
+                                   gint              baseline)
+{
+  GtkWidget *child;
+
+  for (child = gtk_widget_get_first_child (widget);
+       child != NULL;
+       child = gtk_widget_get_next_sibling (child))
+    {
+      GtkSizeRequestMode child_request_mode;
+      gint child_min_height = 0;
+      gint child_min_width = 0;
+
+      if (!gtk_widget_should_layout (child))
+        continue;
+
+      child_request_mode = gtk_widget_get_request_mode (child);
+
+      switch (child_request_mode)
+        {
+        case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
+          gtk_widget_measure (child,
+                              GTK_ORIENTATION_HORIZONTAL,
+                              -1,
+                              &child_min_width, NULL,
+                              NULL, NULL);
+          width = MAX (width, child_min_width);
+
+          gtk_widget_measure (child,
+                              GTK_ORIENTATION_HORIZONTAL,
+                              width,
+                              &child_min_width, NULL,
+                              NULL, NULL);
+          height = MAX (height, child_min_height);
+          break;
+
+        case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
+          gtk_widget_measure (child,
+                              GTK_ORIENTATION_HORIZONTAL,
+                              -1,
+                              &child_min_width, NULL,
+                              NULL, NULL);
+          height = MAX (height, child_min_height);
+
+          gtk_widget_measure (child,
+                              GTK_ORIENTATION_HORIZONTAL,
+                              height,
+                              &child_min_width, NULL,
+                              NULL, NULL);
+          width = MAX (width, child_min_width);
+          break;
+
+        case GTK_SIZE_REQUEST_CONSTANT_SIZE:
+          gtk_widget_measure (child,
+                              GTK_ORIENTATION_HORIZONTAL,
+                              -1,
+                              &child_min_width, NULL,
+                              NULL, NULL);
+          width = MAX (width, child_min_width);
+
+          gtk_widget_measure (child,
+                              GTK_ORIENTATION_HORIZONTAL,
+                              -1,
+                              &child_min_width, NULL,
+                              NULL, NULL);
+          height = MAX (height, child_min_height);
+          break;
+        }
+
+      gtk_widget_allocate (child, width, height, baseline, NULL);
+    }
+}
+
+static GtkSizeRequestMode
+gcal_overflow_bin_layout_get_request_mode (GtkLayoutManager *layout_manager,
+                                           GtkWidget        *widget)
+{
+  GcalOverflowBinLayout *self = GCAL_OVERFLOW_BIN_LAYOUT (layout_manager);
+
+  return self->request_mode;
+}
+
+static void
+gcal_overflow_bin_layout_class_init (GcalOverflowBinLayoutClass *klass)
+{
+  GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);
+
+  layout_manager_class->measure = gcal_overflow_bin_layout_measure;
+  layout_manager_class->allocate = gcal_overflow_bin_layout_allocate;
+  layout_manager_class->get_request_mode = gcal_overflow_bin_layout_get_request_mode;
+}
+
+static void
+gcal_overflow_bin_layout_init (GcalOverflowBinLayout *self)
+{
+  self->request_mode = GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+void
+gcal_overflow_bin_layout_set_request_mode (GcalOverflowBinLayout *self,
+                                           GtkSizeRequestMode     request_mode)
+{
+  g_return_if_fail (GCAL_IS_OVERFLOW_BIN_LAYOUT (self));
+  g_return_if_fail (request_mode >= GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH && request_mode <= 
GTK_SIZE_REQUEST_CONSTANT_SIZE);
+
+  if (self->request_mode == request_mode)
+    return;
+
+  self->request_mode = request_mode;
+
+  gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (self));
+}
diff --git a/src/gui/gcal-overflow-bin-layout.h b/src/gui/gcal-overflow-bin-layout.h
new file mode 100644
index 00000000..18f74d78
--- /dev/null
+++ b/src/gui/gcal-overflow-bin-layout.h
@@ -0,0 +1,33 @@
+/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * gcal-overflow-bin-layout.h
+ * Copyright (C) 2022 Purism SPC
+ *
+ * gnome-calendar 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.
+ *
+ * gnome-calendar 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GCAL_TYPE_OVERFLOW_BIN_LAYOUT (gcal_overflow_bin_layout_get_type ())
+
+G_DECLARE_FINAL_TYPE (GcalOverflowBinLayout, gcal_overflow_bin_layout, GCAL, OVERFLOW_BIN_LAYOUT, 
GtkLayoutManager)
+
+void gcal_overflow_bin_layout_set_request_mode (GcalOverflowBinLayout *self,
+                                                GtkSizeRequestMode     request_mode);
+
+G_END_DECLS
diff --git a/src/gui/gcal-overflow-bin.c b/src/gui/gcal-overflow-bin.c
new file mode 100644
index 00000000..8c80f849
--- /dev/null
+++ b/src/gui/gcal-overflow-bin.c
@@ -0,0 +1,297 @@
+/* gcal-overflow-bin-layout.c
+ *
+ * Copyright (C) 2022 Purism SPC
+ *
+ * gnome-calendar 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.
+ *
+ * gnome-calendar 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, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "gcal-overflow-bin.h"
+#include "gcal-overflow-bin-layout.h"
+
+typedef struct
+{
+  GtkWidget *child;
+  GtkSizeRequestMode request_mode;
+} GcalOverflowBinPrivate;
+
+static void gcal_overflow_bin_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GcalOverflowBin, gcal_overflow_bin, GTK_TYPE_WIDGET,
+                         G_ADD_PRIVATE (GcalOverflowBin)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gcal_overflow_bin_buildable_init))
+
+static GtkBuildableIface *parent_buildable_iface;
+
+enum
+{
+  PROP_0,
+  PROP_CHILD,
+  PROP_REQUEST_MODE,
+  LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+
+/*
+ * GtkBuildable interface
+ */
+
+static void
+gcal_overflow_bin_buildable_add_child (GtkBuildable *buildable,
+                                       GtkBuilder   *builder,
+                                       GObject      *child,
+                                       const char   *type)
+{
+  if (GTK_IS_WIDGET (child))
+    gcal_overflow_bin_set_child (GCAL_OVERFLOW_BIN (buildable), GTK_WIDGET (child));
+  else
+    parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+gcal_overflow_bin_buildable_init (GtkBuildableIface *iface)
+{
+  parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+  iface->add_child = gcal_overflow_bin_buildable_add_child;
+}
+
+
+/*
+ * GtkWidget overrides
+ */
+
+static void
+gcal_overflow_bin_compute_expand (GtkWidget *widget,
+                                  gboolean  *hexpand_p,
+                                  gboolean  *vexpand_p)
+{
+  GtkWidget *child;
+  gboolean hexpand = FALSE;
+  gboolean vexpand = FALSE;
+
+  for (child = gtk_widget_get_first_child (widget);
+       child != NULL;
+       child = gtk_widget_get_next_sibling (child))
+    {
+      hexpand = hexpand || gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL);
+      vexpand = vexpand || gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL);
+    }
+
+  *hexpand_p = hexpand;
+  *vexpand_p = vexpand;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gcal_overflow_bin_dispose (GObject *object)
+{
+  GcalOverflowBin *self = GCAL_OVERFLOW_BIN (object);
+  GcalOverflowBinPrivate *priv = gcal_overflow_bin_get_instance_private (self);
+
+  g_clear_pointer (&priv->child, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gcal_overflow_bin_parent_class)->dispose (object);
+}
+
+static void
+gcal_overflow_bin_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  GcalOverflowBin *self = GCAL_OVERFLOW_BIN (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD:
+      g_value_set_object (value, gcal_overflow_bin_get_child (self));
+      break;
+
+    case PROP_REQUEST_MODE:
+      g_value_set_enum (value, gtk_widget_get_request_mode (GTK_WIDGET (self)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gcal_overflow_bin_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GcalOverflowBin *self = GCAL_OVERFLOW_BIN (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD:
+      gcal_overflow_bin_set_child (self, g_value_get_object (value));
+      break;
+
+    case PROP_REQUEST_MODE:
+      gcal_overflow_bin_set_request_mode (self, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gcal_overflow_bin_class_init (GcalOverflowBinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gcal_overflow_bin_dispose;
+  object_class->get_property = gcal_overflow_bin_get_property;
+  object_class->set_property = gcal_overflow_bin_set_property;
+
+  widget_class->compute_expand = gcal_overflow_bin_compute_expand;
+
+  /**
+   * GcalOverflowBin:child: (attributes org.gtk.Property.get=gcal_overflow_bin_get_child 
org.gtk.Property.set=gcal_overflow_bin_set_child)
+   *
+   * The child widget of the `GcalOverflowBin`.
+   */
+  props[PROP_CHILD] = g_param_spec_object ("child", NULL, NULL,
+                                           GTK_TYPE_WIDGET,
+                                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | 
G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GcalOverflowBin:child: (attributes org.gtk.Property.get=gtk_widget_get_request_mode 
org.gtk.Property.set=gcal_overflow_bin_set_request_mode)
+   *
+   * The size request mode of the `GcalOverflowBin`.
+   */
+  props[PROP_REQUEST_MODE] = g_param_spec_enum ("request-mode", NULL, NULL,
+                                                GTK_TYPE_SIZE_REQUEST_MODE,
+                                                GTK_SIZE_REQUEST_CONSTANT_SIZE,
+                                                G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | 
G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GCAL_TYPE_OVERFLOW_BIN_LAYOUT);
+}
+
+static void
+gcal_overflow_bin_init (GcalOverflowBin *self)
+{
+  GcalOverflowBinPrivate *priv = gcal_overflow_bin_get_instance_private (self);
+
+  priv->request_mode = GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+/**
+ * gcal_overflow_bin_new:
+ *
+ * Creates a new `GcalOverflowBin`.
+ *
+ * Returns: the new created `GcalOverflowBin`
+ */
+GtkWidget *
+gcal_overflow_bin_new (void)
+{
+  return g_object_new (GCAL_TYPE_OVERFLOW_BIN, NULL);
+}
+
+/**
+ * gcal_overflow_bin_get_child: (attributes org.gtk.Method.get_property=child)
+ * @self: a bin
+ *
+ * Gets the child widget of @self.
+ *
+ * Returns: (nullable) (transfer none): the child widget of @self
+ */
+GtkWidget *
+gcal_overflow_bin_get_child (GcalOverflowBin *self)
+{
+  GcalOverflowBinPrivate *priv;
+
+  g_return_val_if_fail (GCAL_IS_OVERFLOW_BIN (self), NULL);
+
+  priv = gcal_overflow_bin_get_instance_private (self);
+
+  return priv->child;
+}
+
+/**
+ * gcal_overflow_bin_set_child: (attributes org.gtk.Method.set_property=child)
+ * @self: a bin
+ * @child: (nullable): the child widget
+ *
+ * Sets the child widget of @self.
+ */
+void
+gcal_overflow_bin_set_child (GcalOverflowBin *self,
+                             GtkWidget       *child)
+{
+  GcalOverflowBinPrivate *priv;
+
+  g_return_if_fail (GCAL_IS_OVERFLOW_BIN (self));
+  g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
+
+  priv = gcal_overflow_bin_get_instance_private (self);
+
+  if (priv->child == child)
+    return;
+
+  g_clear_pointer (&priv->child, gtk_widget_unparent);
+
+  priv->child = child;
+
+  if (priv->child)
+    gtk_widget_set_parent (priv->child, GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD]);
+}
+
+/**
+ * gcal_overflow_bin_set_request_mode: (attributes org.gtk.Method.set_property=request-mode)
+ * @self: a bin
+ * @request_ode: the size request mode
+ *
+ * Sets the size request mode of @self.
+ */
+void
+gcal_overflow_bin_set_request_mode (GcalOverflowBin    *self,
+                                    GtkSizeRequestMode  request_mode)
+{
+  GcalOverflowBinPrivate *priv;
+  GtkLayoutManager *layout_manager;
+
+  g_return_if_fail (GCAL_IS_OVERFLOW_BIN (self));
+  g_return_if_fail (request_mode >= GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH && request_mode <= 
GTK_SIZE_REQUEST_CONSTANT_SIZE);
+
+  priv = gcal_overflow_bin_get_instance_private (self);
+
+  if (priv->request_mode == request_mode)
+    return;
+
+  priv->request_mode = request_mode;
+
+  layout_manager = gtk_widget_get_layout_manager (GTK_WIDGET (self));
+  gcal_overflow_bin_layout_set_request_mode (GCAL_OVERFLOW_BIN_LAYOUT (layout_manager), request_mode);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REQUEST_MODE]);
+}
diff --git a/src/gui/gcal-overflow-bin.h b/src/gui/gcal-overflow-bin.h
new file mode 100644
index 00000000..b492bed7
--- /dev/null
+++ b/src/gui/gcal-overflow-bin.h
@@ -0,0 +1,44 @@
+/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * gcal-overflow-bin-.h
+ * Copyright (C) 2022 Purism SPC
+ *
+ * gnome-calendar 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.
+ *
+ * gnome-calendar 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GCAL_TYPE_OVERFLOW_BIN (gcal_overflow_bin_get_type ())
+
+G_DECLARE_DERIVABLE_TYPE (GcalOverflowBin, gcal_overflow_bin, GCAL, OVERFLOW_BIN, GtkWidget)
+
+struct _GcalOverflowBinClass
+{
+  GtkWidgetClass parent_class;
+};
+
+GtkWidget *gcal_overflow_bin_new (void) G_GNUC_WARN_UNUSED_RESULT;
+
+GtkWidget *gcal_overflow_bin_get_child (GcalOverflowBin *self);
+void       gcal_overflow_bin_set_child (GcalOverflowBin *self,
+                                        GtkWidget       *child);
+
+void gcal_overflow_bin_set_request_mode (GcalOverflowBin    *self,
+                                         GtkSizeRequestMode  request_mode);
+
+G_END_DECLS
diff --git a/src/gui/meson.build b/src/gui/meson.build
index 78f68a9e..3b783d19 100644
--- a/src/gui/meson.build
+++ b/src/gui/meson.build
@@ -19,6 +19,8 @@ sources += files(
   'gcal-event-popover.c',
   'gcal-event-widget.c',
   'gcal-meeting-row.c',
+  'gcal-overflow-bin.c',
+  'gcal-overflow-bin-layout.c',
   'gcal-quick-add-popover.c',
   'gcal-search-button.c',
   'gcal-search-hit-row.c',


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