[gnome-builder/wip/gtk4-port] libide/gtk: add radiobox



commit b16f62785d2e66d18d5a5bdfbe2e031487298be7
Author: Christian Hergert <chergert redhat com>
Date:   Wed Apr 6 17:25:13 2022 -0700

    libide/gtk: add radiobox
    
    This is just a small port from Dazzle without the custom parsing of
    elements from XML.

 src/libide/gtk/ide-gtk-init.c  |   2 +
 src/libide/gtk/ide-radio-box.c | 379 +++++++++++++++++++++++++++++++++++++++++
 src/libide/gtk/ide-radio-box.h |  51 ++++++
 src/libide/gtk/libide-gtk.h    |   1 +
 src/libide/gtk/meson.build     |   2 +
 5 files changed, 435 insertions(+)
---
diff --git a/src/libide/gtk/ide-gtk-init.c b/src/libide/gtk/ide-gtk-init.c
index 65fce248d..5821a299b 100644
--- a/src/libide/gtk/ide-gtk-init.c
+++ b/src/libide/gtk/ide-gtk-init.c
@@ -31,6 +31,7 @@
 #include "ide-cell-renderer-fancy.h"
 #include "ide-fancy-tree-view.h"
 #include "ide-progress-icon.h"
+#include "ide-radio-box.h"
 #include "ide-search-entry.h"
 #include "ide-three-grid.h"
 #include "ide-truncate-model.h"
@@ -44,6 +45,7 @@ _ide_gtk_init (void)
   g_type_ensure (IDE_TYPE_FANCY_TREE_VIEW);
   g_type_ensure (IDE_TYPE_FILE_CHOOSER_ENTRY);
   g_type_ensure (IDE_TYPE_PROGRESS_ICON);
+  g_type_ensure (IDE_TYPE_RADIO_BOX);
   g_type_ensure (IDE_TYPE_SEARCH_ENTRY);
   g_type_ensure (IDE_TYPE_THREE_GRID);
   g_type_ensure (IDE_TYPE_TRUNCATE_MODEL);
diff --git a/src/libide/gtk/ide-radio-box.c b/src/libide/gtk/ide-radio-box.c
new file mode 100644
index 000000000..0d452adbe
--- /dev/null
+++ b/src/libide/gtk/ide-radio-box.c
@@ -0,0 +1,379 @@
+/* ide-radio-box.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-radio-box"
+
+#include "config.h"
+
+#include "ide-radio-box.h"
+
+#define N_PER_ROW 4
+
+typedef struct
+{
+  gchar           *id;
+  gchar           *text;
+  GtkToggleButton *button;
+} IdeRadioBoxItem;
+
+struct _IdeRadioBox
+{
+  GtkWidget      parent;
+
+  GArray        *items;
+  gchar         *active_id;
+
+  GtkBox        *vbox;
+  GtkBox        *hbox;
+  GtkRevealer   *revealer;
+
+  guint          has_more : 1;
+};
+
+G_DEFINE_FINAL_TYPE (IdeRadioBox, ide_radio_box, GTK_TYPE_WIDGET)
+
+enum {
+  PROP_0,
+  PROP_ACTIVE_ID,
+  PROP_HAS_MORE,
+  PROP_SHOW_MORE,
+  N_PROPS
+};
+
+enum {
+  CHANGED,
+  N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static gboolean
+ide_radio_box_get_has_more (IdeRadioBox *self)
+{
+  g_return_val_if_fail (IDE_IS_RADIO_BOX (self), FALSE);
+
+  return self->has_more;
+}
+
+static gboolean
+ide_radio_box_get_show_more (IdeRadioBox *self)
+{
+  g_return_val_if_fail (IDE_IS_RADIO_BOX (self), FALSE);
+
+  return gtk_revealer_get_reveal_child (self->revealer);
+}
+
+static void
+ide_radio_box_set_show_more (IdeRadioBox *self,
+                             gboolean     show_more)
+{
+  g_return_if_fail (IDE_IS_RADIO_BOX (self));
+
+  gtk_revealer_set_reveal_child (self->revealer, show_more);
+}
+
+static void
+ide_radio_box_item_clear (IdeRadioBoxItem *item)
+{
+  g_free (item->id);
+  g_free (item->text);
+}
+
+static void
+ide_radio_box_dispose (GObject *object)
+{
+  IdeRadioBox *self = (IdeRadioBox *)object;
+  GtkWidget *child;
+
+  while (self->items->len > 0)
+    {
+      g_autofree char *id = g_strdup (g_array_index (self->items, IdeRadioBoxItem, 0).id);
+
+      ide_radio_box_remove_item (self, id);
+    }
+
+  while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
+    gtk_widget_unparent (child);
+
+  G_OBJECT_CLASS (ide_radio_box_parent_class)->dispose (object);
+}
+
+static void
+ide_radio_box_finalize (GObject *object)
+{
+  IdeRadioBox *self = (IdeRadioBox *)object;
+
+  g_clear_pointer (&self->items, g_array_unref);
+  g_clear_pointer (&self->active_id, g_free);
+
+  G_OBJECT_CLASS (ide_radio_box_parent_class)->finalize (object);
+}
+
+static void
+ide_radio_box_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  IdeRadioBox *self = IDE_RADIO_BOX (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACTIVE_ID:
+      g_value_set_string (value, ide_radio_box_get_active_id (self));
+      break;
+
+    case PROP_HAS_MORE:
+      g_value_set_boolean (value, ide_radio_box_get_has_more (self));
+      break;
+
+    case PROP_SHOW_MORE:
+      g_value_set_boolean (value, ide_radio_box_get_show_more (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_radio_box_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  IdeRadioBox *self = IDE_RADIO_BOX (object);
+
+  switch (prop_id)
+    {
+    case PROP_ACTIVE_ID:
+      ide_radio_box_set_active_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_SHOW_MORE:
+      ide_radio_box_set_show_more (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_radio_box_class_init (IdeRadioBoxClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = ide_radio_box_dispose;
+  object_class->finalize = ide_radio_box_finalize;
+  object_class->get_property = ide_radio_box_get_property;
+  object_class->set_property = ide_radio_box_set_property;
+
+  properties [PROP_ACTIVE_ID] =
+    g_param_spec_string ("active-id",
+                         "Active Id",
+                         "Active Id",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_HAS_MORE] =
+    g_param_spec_boolean ("has-more",
+                         "Has More",
+                         "Has more items to view",
+                         FALSE,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SHOW_MORE] =
+    g_param_spec_boolean ("show-more",
+                          "Show More",
+                          "Show additional items",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [CHANGED] =
+    g_signal_new ("changed", G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  gtk_widget_class_set_css_name (widget_class, "radiobox");
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+static void
+ide_radio_box_init (IdeRadioBox *self)
+{
+  g_autoptr(GSimpleActionGroup) group = g_simple_action_group_new ();
+  g_autoptr(GPropertyAction) action = NULL;
+  GtkWidget *vbox;
+
+  /* GPropertyAction doesn't like NULL strings */
+  self->active_id = g_strdup ("");
+
+  self->items = g_array_new (FALSE, FALSE, sizeof (IdeRadioBoxItem));
+  g_array_set_clear_func (self->items, (GDestroyNotify)ide_radio_box_item_clear);
+
+  vbox = g_object_new (GTK_TYPE_BOX,
+                       "orientation", GTK_ORIENTATION_VERTICAL,
+                       "visible", TRUE,
+                       NULL);
+  gtk_widget_set_parent (vbox, GTK_WIDGET (self));
+
+  self->hbox = g_object_new (GTK_TYPE_BOX,
+                             "orientation", GTK_ORIENTATION_HORIZONTAL,
+                             "visible", TRUE,
+                             NULL);
+  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self->hbox)), "linked");
+  gtk_box_append (GTK_BOX (vbox), GTK_WIDGET (self->hbox));
+
+  self->revealer = g_object_new (GTK_TYPE_REVEALER,
+                                 "reveal-child", FALSE,
+                                 "visible", TRUE,
+                                 NULL);
+  gtk_box_append (GTK_BOX (vbox), GTK_WIDGET (self->revealer));
+
+  self->vbox = g_object_new (GTK_TYPE_BOX,
+                             "orientation", GTK_ORIENTATION_VERTICAL,
+                             "margin-top", 12,
+                             "spacing", 12,
+                             "visible", TRUE,
+                             NULL);
+  gtk_revealer_set_child (self->revealer, GTK_WIDGET (self->vbox));
+
+  action = g_property_action_new ("active", self, "active-id");
+  g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "radiobox", G_ACTION_GROUP (group));
+}
+
+void
+ide_radio_box_remove_item (IdeRadioBox *self,
+                           const gchar *id)
+{
+  g_return_if_fail (IDE_IS_RADIO_BOX (self));
+  g_return_if_fail (id != NULL);
+
+  for (guint i = 0; i < self->items->len; i++)
+    {
+      IdeRadioBoxItem *item = &g_array_index (self->items, IdeRadioBoxItem, i);
+
+      if (g_strcmp0 (id, item->id) == 0)
+        {
+          GtkToggleButton *button = item->button;
+          GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (button));
+
+          g_array_remove_index_fast (self->items, i);
+          gtk_box_remove (GTK_BOX (parent), GTK_WIDGET (button));
+
+          break;
+        }
+    }
+}
+
+void
+ide_radio_box_add_item (IdeRadioBox *self,
+                        const gchar *id,
+                        const gchar *text)
+{
+  IdeRadioBoxItem item = { 0 };
+  guint precount;
+
+  g_return_if_fail (IDE_IS_RADIO_BOX (self));
+  g_return_if_fail (id != NULL);
+  g_return_if_fail (text != NULL);
+
+  precount = self->items->len;
+
+  for (guint i = 0; i < precount; ++i)
+    {
+      /* Avoid duplicate items */
+      if (!g_strcmp0 (g_array_index (self->items, IdeRadioBoxItem, i).id, id))
+        return;
+    }
+
+  item.id = g_strdup (id);
+  item.text = g_strdup (text);
+  item.button = g_object_new (GTK_TYPE_TOGGLE_BUTTON,
+                              "active", (g_strcmp0 (id, self->active_id) == 0),
+                              "action-name", "radiobox.active",
+                              "action-target", g_variant_new_string (id),
+                              "hexpand", TRUE,
+                              "label", text,
+                              "visible", TRUE,
+                              NULL);
+
+  g_array_append_val (self->items, item);
+
+  if (precount > 0 && (precount % N_PER_ROW) == 0)
+    {
+      gboolean show_more = ide_radio_box_get_show_more (self);
+      gboolean visible = !self->has_more || show_more;
+
+      self->has_more = self->items->len > N_PER_ROW;
+      self->hbox = g_object_new (GTK_TYPE_BOX,
+                                 "orientation", GTK_ORIENTATION_HORIZONTAL,
+                                 "visible", visible,
+                                 NULL);
+      gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self->hbox)), "linked");
+      gtk_box_append (GTK_BOX (self->vbox), GTK_WIDGET (self->hbox));
+    }
+
+  gtk_box_append (GTK_BOX (self->hbox), GTK_WIDGET (item.button));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_MORE]);
+
+  /* If this is the first item and no active id has been set,
+   * then go ahead and set the active item to this one.
+   */
+  if (self->items->len == 1 && (!self->active_id || !*self->active_id))
+    ide_radio_box_set_active_id (self, id);
+}
+
+void
+ide_radio_box_set_active_id (IdeRadioBox *self,
+                             const gchar *id)
+{
+  g_return_if_fail (IDE_IS_RADIO_BOX (self));
+
+  if (id == NULL)
+    id = "";
+
+  if (g_strcmp0 (id, self->active_id) != 0)
+    {
+      g_free (self->active_id);
+      self->active_id = g_strdup (id);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVE_ID]);
+      g_signal_emit (self, signals [CHANGED], 0);
+    }
+}
+
+const gchar *
+ide_radio_box_get_active_id (IdeRadioBox *self)
+{
+  g_return_val_if_fail (IDE_IS_RADIO_BOX (self), NULL);
+
+  return self->active_id;
+}
+
+GtkWidget *
+ide_radio_box_new (void)
+{
+  return g_object_new (IDE_TYPE_RADIO_BOX, NULL);
+}
diff --git a/src/libide/gtk/ide-radio-box.h b/src/libide/gtk/ide-radio-box.h
new file mode 100644
index 000000000..20cb33da8
--- /dev/null
+++ b/src/libide/gtk/ide-radio-box.h
@@ -0,0 +1,51 @@
+/* ide-radio-box.h
+ *
+ * Copyright (C) 2016-2022 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if !defined (IDE_GTK_INSIDE) && !defined (IDE_GTK_COMPILATION)
+# error "Only <libide-gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RADIO_BOX (ide_radio_box_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeRadioBox, ide_radio_box, IDE, RADIO_BOX, GtkWidget)
+
+IDE_AVAILABLE_IN_ALL
+GtkWidget   *ide_radio_box_new           (void);
+IDE_AVAILABLE_IN_ALL
+void         ide_radio_box_add_item      (IdeRadioBox *self,
+                                          const gchar *id,
+                                          const gchar *text);
+IDE_AVAILABLE_IN_ALL
+void         ide_radio_box_remove_item   (IdeRadioBox *self,
+                                          const gchar *id);
+IDE_AVAILABLE_IN_ALL
+const gchar *ide_radio_box_get_active_id (IdeRadioBox *self);
+IDE_AVAILABLE_IN_ALL
+void         ide_radio_box_set_active_id (IdeRadioBox *self,
+                                          const gchar *id);
+
+G_END_DECLS
diff --git a/src/libide/gtk/libide-gtk.h b/src/libide/gtk/libide-gtk.h
index b53e6de65..166c0239e 100644
--- a/src/libide/gtk/libide-gtk.h
+++ b/src/libide/gtk/libide-gtk.h
@@ -32,6 +32,7 @@
 # include "ide-gtk-enums.h"
 # include "ide-menu-manager.h"
 # include "ide-progress-icon.h"
+# include "ide-radio-box.h"
 # include "ide-search-entry.h"
 # include "ide-three-grid.h"
 # include "ide-truncate-model.h"
diff --git a/src/libide/gtk/meson.build b/src/libide/gtk/meson.build
index d14952186..b711b454f 100644
--- a/src/libide/gtk/meson.build
+++ b/src/libide/gtk/meson.build
@@ -17,6 +17,7 @@ libide_gtk_public_headers = [
   'ide-gtk.h',
   'ide-menu-manager.h',
   'ide-progress-icon.h',
+  'ide-radio-box.h',
   'ide-search-entry.h',
   'ide-three-grid.h',
   'ide-truncate-model.h',
@@ -45,6 +46,7 @@ libide_gtk_public_sources = [
   'ide-gtk.c',
   'ide-menu-manager.c',
   'ide-progress-icon.c',
+  'ide-radio-box.c',
   'ide-search-entry.c',
   'ide-three-grid.c',
   'ide-truncate-model.c',


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