summaryrefslogtreecommitdiff
path: root/gtk/gtkcanvas.c
diff options
context:
space:
mode:
Diffstat (limited to 'gtk/gtkcanvas.c')
-rw-r--r--gtk/gtkcanvas.c427
1 files changed, 427 insertions, 0 deletions
diff --git a/gtk/gtkcanvas.c b/gtk/gtkcanvas.c
new file mode 100644
index 0000000000..18770f5906
--- /dev/null
+++ b/gtk/gtkcanvas.c
@@ -0,0 +1,427 @@
+/*
+ * Copyright © 2022 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtkcanvas.h"
+
+#include "gtkcanvasbox.h"
+#include "gtkcanvasitemprivate.h"
+#include "gtkintl.h"
+#include "gtklistitemfactory.h"
+#include "gtkwidgetprivate.h"
+
+#define GDK_ARRAY_NAME gtk_canvas_items
+#define GDK_ARRAY_TYPE_NAME GtkCanvasItems
+#define GDK_ARRAY_ELEMENT_TYPE GtkCanvasItem *
+#define GDK_ARRAY_FREE_FUNC g_object_unref
+#include "gdk/gdkarrayimpl.c"
+
+/**
+ * GtkCanvas:
+ *
+ * `GtkCanvas` is a widget that allows developers to place a list of items
+ * using their own method.
+ *
+ * ![An example GtkCanvas](canvas.png)
+ */
+
+struct _GtkCanvas
+{
+ GtkWidget parent_instance;
+
+ GListModel *model;
+ GtkListItemFactory *factory;
+
+ GtkCanvasItems items;
+};
+
+enum
+{
+ PROP_0,
+ PROP_FACTORY,
+ PROP_MODEL,
+
+ N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (GtkCanvas, gtk_canvas, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+gtk_canvas_clear_factory (GtkCanvas *self)
+{
+ if (self->factory == NULL)
+ return;
+
+ g_clear_object (&self->factory);
+}
+
+static void
+gtk_canvas_remove_items (GtkCanvas *self,
+ guint pos,
+ guint n_items)
+{
+ guint i;
+
+ /* We first run the factory code on all items, so that the
+ * factory code can reference the items.
+ * Only then do we get rid of them.
+ */
+ for (i = pos; i < pos + n_items; i++)
+ {
+ gtk_canvas_item_teardown (gtk_canvas_items_get (&self->items, i), self->factory);
+ }
+}
+
+static void
+gtk_canvas_add_items (GtkCanvas *self,
+ guint pos,
+ guint n_items)
+{
+ guint i;
+
+ /* We first create all the items and then run the factory code
+ * on them, so that the factory code can reference the items.
+ */
+ for (i = pos; i < pos + n_items; i++)
+ {
+ *gtk_canvas_items_index (&self->items, i) = gtk_canvas_item_new (self,
+ g_list_model_get_item (self->model, i));
+ }
+ for (i = pos; i < pos + n_items; i++)
+ {
+ gtk_canvas_item_setup (gtk_canvas_items_get (&self->items, i), self->factory);
+ }
+}
+
+static void
+gtk_canvas_items_changed_cb (GListModel *model,
+ guint pos,
+ guint removed,
+ guint added,
+ GtkCanvas *self)
+{
+ gtk_canvas_remove_items (self, pos, removed);
+
+ gtk_canvas_items_splice (&self->items, pos, removed, FALSE, NULL, added);
+
+ gtk_canvas_add_items (self, pos, added);
+}
+
+static void
+gtk_canvas_clear_model (GtkCanvas *self)
+{
+ if (self->model == NULL)
+ return;
+
+ g_signal_handlers_disconnect_by_func (self->model,
+ gtk_canvas_items_changed_cb,
+ self);
+ g_clear_object (&self->model);
+}
+
+static void
+gtk_canvas_dispose (GObject *object)
+{
+ GtkCanvas *self = GTK_CANVAS (object);
+
+ gtk_canvas_clear_model (self);
+ gtk_canvas_clear_factory (self);
+
+ G_OBJECT_CLASS (gtk_canvas_parent_class)->dispose (object);
+}
+
+static void
+gtk_canvas_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCanvas *self = GTK_CANVAS (object);
+
+ switch (property_id)
+ {
+ case PROP_FACTORY:
+ g_value_set_object (value, self->factory);
+ break;
+
+ case PROP_MODEL:
+ g_value_set_object (value, self->model);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_canvas_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCanvas *self = GTK_CANVAS (object);
+
+ switch (property_id)
+ {
+ case PROP_FACTORY:
+ gtk_canvas_set_factory (self, g_value_get_object (value));
+ break;
+
+ case PROP_MODEL:
+ gtk_canvas_set_model (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_canvas_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ GtkCanvas *self = GTK_CANVAS (widget);
+ gsize i;
+
+ for (i = 0; i < gtk_canvas_items_get_size (&self->items); i++)
+ {
+ GtkCanvasItem *ci = gtk_canvas_items_get (&self->items, i);
+ GtkWidget *child = gtk_canvas_item_get_widget (ci);
+ graphene_rect_t rect;
+ int x, y, w, h;
+
+ if (child == NULL)
+ continue;
+
+ if (!gtk_canvas_box_eval (gtk_canvas_item_get_bounds (ci), &rect))
+ rect = *graphene_rect_zero ();
+
+ if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, -1, &w, NULL, NULL, NULL);
+ w = MAX (w, ceil (rect.size.width));
+ gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, w, &h, NULL, NULL, NULL);
+ h = MAX (h, ceil (rect.size.height));
+ }
+ else
+ {
+ gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, -1, &h, NULL, NULL, NULL);
+ h = MAX (h, ceil (rect.size.height));
+ gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, h, &w, NULL, NULL, NULL);
+ w = MAX (w, ceil (rect.size.width));
+ }
+
+ /* FIXME: Adapt to growing rect */
+ x = round (rect.origin.x);
+ y = round (rect.origin.y);
+
+ gtk_widget_size_allocate (child, &(GtkAllocation) { x, y, w, h }, -1);
+ }
+}
+
+static void
+gtk_canvas_class_init (GtkCanvasClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ widget_class->size_allocate = gtk_canvas_allocate;
+
+ gobject_class->dispose = gtk_canvas_dispose;
+ gobject_class->get_property = gtk_canvas_get_property;
+ gobject_class->set_property = gtk_canvas_set_property;
+
+ /**
+ * GtkCanvas:factory: (attributes org.gtk.Property.get=gtk_canvas_get_factory org.gtk.Property.set=gtk_canvas_set_factory)
+ *
+ * The factory used to set up canvasitems from items in the model.
+ */
+ properties[PROP_FACTORY] =
+ g_param_spec_object ("factory", NULL, NULL,
+ GTK_TYPE_LIST_ITEM_FACTORY,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkCanvas:model: (attributes org.gtk.Property.get=gtk_canvas_get_model org.gtk.Property.set=gtk_canvas_set_model)
+ *
+ * The model with the items to display
+ */
+ properties[PROP_MODEL] =
+ g_param_spec_object ("model", NULL, NULL,
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, I_("canvas"));
+}
+
+static void
+gtk_canvas_init (GtkCanvas *self)
+{
+}
+
+/**
+ * gtk_canvas_new:
+ * @model: (nullable) (transfer full): the model to use
+ * @factory: (nullable) (transfer full): The factory to populate items with
+ *
+ * Creates a new `GtkCanvas` that uses the given @factory for
+ * mapping items to widgets.
+ *
+ * The function takes ownership of the
+ * arguments, so you can write code like
+ * ```c
+ * canvas = gtk_canvas_new (create_model (),
+ * gtk_builder_list_item_factory_new_from_resource ("/resource.ui"));
+ * ```
+ *
+ * Returns: a new `GtkCanvas` using the given @model and @factory
+ */
+GtkWidget *
+gtk_canvas_new (GListModel *model,
+ GtkListItemFactory *factory)
+{
+ GtkWidget *result;
+
+ g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
+ g_return_val_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory), NULL);
+
+ result = g_object_new (GTK_TYPE_CANVAS,
+ "factory", factory,
+ "model", model,
+ NULL);
+
+ g_clear_object (&model);
+ g_clear_object (&factory);
+
+ return result;
+}
+
+/**
+ * gtk_canvas_set_factory: (attributes org.gtk.Method.set_property=factory)
+ * @self: a `GtkCanvas`
+ * @factory: (nullable) (transfer none): the factory to use
+ *
+ * Sets the `GtkListItemFactory` to use for populating canvas items.
+ */
+void
+gtk_canvas_set_factory (GtkCanvas *self,
+ GtkListItemFactory *factory)
+{
+ guint n_items;
+
+ g_return_if_fail (GTK_IS_CANVAS (self));
+ g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
+
+ if (self->factory == factory)
+ return;
+
+ n_items = self->model ? g_list_model_get_n_items (G_LIST_MODEL (self->model)) : 0;
+ gtk_canvas_remove_items (self, 0, n_items);
+
+ g_set_object (&self->factory, factory);
+ gtk_canvas_items_splice (&self->items, 0, n_items, FALSE, NULL, n_items);
+
+ gtk_canvas_add_items (self, 0, n_items);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
+}
+
+/**
+ * gtk_canvas_get_factory: (attributes org.gtk.Method.get_property=factory)
+ * @self: a `GtkCanvas`
+ *
+ * Gets the factory that's currently used to populate canvas items.
+ *
+ * Returns: (nullable) (transfer none): The factory in use
+ */
+GtkListItemFactory *
+gtk_canvas_get_factory (GtkCanvas *self)
+{
+ g_return_val_if_fail (GTK_IS_CANVAS (self), NULL);
+
+ return self->factory;
+}
+
+/**
+ * gtk_canvas_set_model: (attributes org.gtk.Method.set_property=model)
+ * @self: a `GtkCanvas`
+ * @model: (nullable) (transfer none): the model to use
+ *
+ * Sets the model containing the items to populate the canvas with.
+ */
+void
+gtk_canvas_set_model (GtkCanvas *self,
+ GListModel *model)
+{
+ g_return_if_fail (GTK_IS_CANVAS (self));
+ g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+
+ if (self->model == model)
+ return;
+
+ gtk_canvas_clear_model (self);
+
+ if (model)
+ {
+ guint added;
+
+ self->model = g_object_ref (model);
+
+ g_signal_connect (model,
+ "items-changed",
+ G_CALLBACK (gtk_canvas_items_changed_cb),
+ self);
+
+ added = g_list_model_get_n_items (G_LIST_MODEL (model));
+ gtk_canvas_items_splice (&self->items, 0, gtk_canvas_items_get_size (&self->items), FALSE, NULL, added);
+ gtk_canvas_add_items (self, 0, added);
+ }
+ else
+ {
+ gtk_canvas_items_clear (&self->items);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+}
+
+/**
+ * gtk_canvas_get_model: (attributes org.gtk.Method.get_property=model)
+ * @self: a `GtkCanvas`
+ *
+ * Gets the model that's currently used for the displayed items.
+ *
+ * Returns: (nullable) (transfer none): The model in use
+ */
+GListModel *
+gtk_canvas_get_model (GtkCanvas *self)
+{
+ g_return_val_if_fail (GTK_IS_CANVAS (self), NULL);
+
+ return self->model;
+}
+