/* gtkcellarea.c * * Copyright (C) 2010 Openismus GmbH * * Authors: * Tristan Van Berkom * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "gtkcelllayout.h" #include "gtkcellarea.h" /* GObjectClass */ static void gtk_cell_area_dispose (GObject *object); static void gtk_cell_area_finalize (GObject *object); /* GtkCellAreaClass */ static void gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area, GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height); static void gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area, GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width); /* GtkCellLayoutIface */ static void gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface); static void gtk_cell_area_pack_default (GtkCellLayout *cell_layout, GtkCellRenderer *renderer, gboolean expand); static void gtk_cell_area_clear (GtkCellLayout *cell_layout); static void gtk_cell_area_add_attribute (GtkCellLayout *cell_layout, GtkCellRenderer *renderer, const gchar *attribute, gint column); static void gtk_cell_area_set_cell_data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkCellLayoutDataFunc func, gpointer func_data, GDestroyNotify destroy); static void gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout, GtkCellRenderer *renderer); static void gtk_cell_area_reorder (GtkCellLayout *cell_layout, GtkCellRenderer *cell, gint position); static GList *gtk_cell_area_get_cells (GtkCellLayout *cell_layout); /* Attribute/Cell metadata */ typedef struct { const gchar *attribute; gint column; } CellAttribute; typedef struct { GSList *attributes; GtkCellLayoutDataFunc func; gpointer data; GDestroyNotify destroy; } CellInfo; static CellInfo *cell_info_new (GtkCellLayoutDataFunc func, gpointer data, GDestroyNotify destroy); static void cell_info_free (CellInfo *info); static CellAttribute *cell_attribute_new (GtkCellRenderer *renderer, const gchar *attribute, gint column); static void cell_attribute_free (CellAttribute *attribute); static gint cell_attribute_find (CellAttribute *cell_attribute, const gchar *attribute); /* Struct to pass data along while looping over * cell renderers to apply attributes */ typedef struct { GtkCellArea *area; GtkTreeModel *model; GtkTreeIter *iter; } AttributeData; struct _GtkCellAreaPrivate { GHashTable *cell_info; }; G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkCellArea, gtk_cell_area, G_TYPE_INITIALLY_UNOWNED, G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, gtk_cell_area_cell_layout_init)); static void gtk_cell_area_init (GtkCellArea *area) { GtkCellAreaPrivate *priv; area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, GTK_TYPE_CELL_AREA, GtkCellAreaPrivate); priv = area->priv; priv->cell_info = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)cell_info_free); } static void gtk_cell_area_class_init (GtkCellAreaClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); /* GObjectClass */ object_class->dispose = gtk_cell_area_dispose; object_class->finalize = gtk_cell_area_finalize; /* general */ class->add = NULL; class->remove = NULL; class->forall = NULL; class->event = NULL; class->render = NULL; /* geometry */ class->get_request_mode = NULL; class->get_preferred_width = NULL; class->get_preferred_height = NULL; class->get_preferred_height_for_width = gtk_cell_area_real_get_preferred_height_for_width; class->get_preferred_width_for_height = gtk_cell_area_real_get_preferred_width_for_height; g_type_class_add_private (object_class, sizeof (GtkCellAreaPrivate)); } /************************************************************* * CellInfo Basics * *************************************************************/ static CellInfo * cell_info_new (GtkCellLayoutDataFunc func, gpointer data, GDestroyNotify destroy) { CellInfo *info = g_slice_new (CellInfo); info->attributes = NULL; info->func = func; info->data = data; info->destroy = destroy; return info; } static void cell_info_free (CellInfo *info) { if (info->destroy) info->destroy (info->data); g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL); g_slist_free (info->attributes); g_slice_free (CellInfo, info); } static CellAttribute * cell_attribute_new (GtkCellRenderer *renderer, const gchar *attribute, gint column) { GParamSpec *pspec; /* Check if the attribute really exists and point to * the property string installed on the cell renderer * class (dont dup the string) */ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (renderer), attribute); if (pspec) { CellAttribute *cell_attribute = g_slice_new (CellAttribute); cell_attribute->attribute = pspec->name; cell_attribute->column = column; return cell_attribute; } return NULL; } static void cell_attribute_free (CellAttribute *attribute) { g_slice_free (CellAttribute, attribute); } /* GCompareFunc for g_slist_find_custom() */ static gint cell_attribute_find (CellAttribute *cell_attribute, const gchar *attribute) { return g_strcmp0 (cell_attribute->attribute, attribute); } /************************************************************* * GObjectClass * *************************************************************/ static void gtk_cell_area_finalize (GObject *object) { GtkCellArea *area = GTK_CELL_AREA (object); GtkCellAreaPrivate *priv = area->priv; /* All cell renderers should already be removed at this point, * just kill our hash table here. */ g_hash_table_destroy (priv->cell_info); G_OBJECT_CLASS (gtk_cell_area_parent_class)->finalize (object); } static void gtk_cell_area_dispose (GObject *object) { /* This removes every cell renderer that may be added to the GtkCellArea, * subclasses should be breaking references to the GtkCellRenderers * at this point. */ gtk_cell_layout_clear (GTK_CELL_LAYOUT (object)); G_OBJECT_CLASS (gtk_cell_area_parent_class)->dispose (object); } /************************************************************* * GtkCellAreaClass * *************************************************************/ static void gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area, GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { /* If the area doesnt do height-for-width, fallback on base preferred height */ GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, widget, minimum_height, natural_height); } static void gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area, GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { /* If the area doesnt do width-for-height, fallback on base preferred width */ GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, widget, minimum_width, natural_width); } /************************************************************* * GtkCellLayoutIface * *************************************************************/ static void gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface) { iface->pack_start = gtk_cell_area_pack_default; iface->pack_end = gtk_cell_area_pack_default; iface->clear = gtk_cell_area_clear; iface->add_attribute = gtk_cell_area_add_attribute; iface->set_cell_data_func = gtk_cell_area_set_cell_data_func; iface->clear_attributes = gtk_cell_area_clear_attributes; iface->reorder = gtk_cell_area_reorder; iface->get_cells = gtk_cell_area_get_cells; } static void gtk_cell_area_pack_default (GtkCellLayout *cell_layout, GtkCellRenderer *renderer, gboolean expand) { gtk_cell_area_add (GTK_CELL_AREA (cell_layout), renderer); } static void gtk_cell_area_clear (GtkCellLayout *cell_layout) { GtkCellArea *area = GTK_CELL_AREA (cell_layout); GList *l, *cells = gtk_cell_layout_get_cells (cell_layout); for (l = cells; l; l = l->next) { GtkCellRenderer *renderer = l->data; gtk_cell_area_remove (area, renderer); } g_list_free (cells); } static void gtk_cell_area_add_attribute (GtkCellLayout *cell_layout, GtkCellRenderer *renderer, const gchar *attribute, gint column) { gtk_cell_area_attribute_connect (GTK_CELL_AREA (cell_layout), renderer, attribute, column); } static void gtk_cell_area_set_cell_data_func (GtkCellLayout *cell_layout, GtkCellRenderer *renderer, GtkCellLayoutDataFunc func, gpointer func_data, GDestroyNotify destroy) { GtkCellArea *area = GTK_CELL_AREA (cell_layout); GtkCellAreaPrivate *priv = area->priv; CellInfo *info; info = g_hash_table_lookup (priv->cell_info, renderer); if (info) { if (info->destroy && info->data) info->destroy (info->data); if (func) { info->func = func; info->data = func_data; info->destroy = destroy; } else { info->func = NULL; info->data = NULL; info->destroy = NULL; } } else { info = cell_info_new (func, func_data, destroy); g_hash_table_insert (priv->cell_info, renderer, info); } } static void gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout, GtkCellRenderer *renderer) { GtkCellArea *area = GTK_CELL_AREA (cell_layout); GtkCellAreaPrivate *priv = area->priv; CellInfo *info; info = g_hash_table_lookup (priv->cell_info, renderer); if (info) { g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL); g_slist_free (info->attributes); info->attributes = NULL; } } static void gtk_cell_area_reorder (GtkCellLayout *cell_layout, GtkCellRenderer *cell, gint position) { g_warning ("GtkCellLayout::reorder not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (cell_layout))); } static void accum_cells (GtkCellRenderer *renderer, GList **accum) { *accum = g_list_prepend (*accum, renderer); } static GList * gtk_cell_area_get_cells (GtkCellLayout *cell_layout) { GList *cells = NULL; gtk_cell_area_forall (GTK_CELL_AREA (cell_layout), (GtkCellCallback)accum_cells, &cells); return g_list_reverse (cells); } /************************************************************* * API * *************************************************************/ void gtk_cell_area_add (GtkCellArea *area, GtkCellRenderer *renderer) { GtkCellAreaClass *class; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); class = GTK_CELL_AREA_GET_CLASS (area); if (class->add) class->add (area, renderer); else g_warning ("GtkCellAreaClass::add not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); } void gtk_cell_area_remove (GtkCellArea *area, GtkCellRenderer *renderer) { GtkCellAreaClass *class; GtkCellAreaPrivate *priv; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); class = GTK_CELL_AREA_GET_CLASS (area); priv = area->priv; /* Remove any custom attributes and custom cell data func here first */ g_hash_table_remove (priv->cell_info, renderer); if (class->remove) class->remove (area, renderer); else g_warning ("GtkCellAreaClass::remove not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); } void gtk_cell_area_forall (GtkCellArea *area, GtkCellCallback callback, gpointer callback_data) { GtkCellAreaClass *class; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (callback != NULL); class = GTK_CELL_AREA_GET_CLASS (area); if (class->forall) class->forall (area, callback, callback_data); else g_warning ("GtkCellAreaClass::forall not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); } gint gtk_cell_area_event (GtkCellArea *area, GtkWidget *widget, GdkEvent *event, const GdkRectangle *cell_area) { GtkCellAreaClass *class; g_return_val_if_fail (GTK_IS_CELL_AREA (area), 0); g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); g_return_val_if_fail (event != NULL, 0); g_return_val_if_fail (cell_area != NULL, 0); class = GTK_CELL_AREA_GET_CLASS (area); if (class->event) return class->event (area, widget, event, cell_area); g_warning ("GtkCellAreaClass::event not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); return 0; } void gtk_cell_area_render (GtkCellArea *area, cairo_t *cr, GtkWidget *widget, const GdkRectangle *cell_area) { GtkCellAreaClass *class; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (cr != NULL); g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (cell_area != NULL); class = GTK_CELL_AREA_GET_CLASS (area); if (class->render) class->render (area, cr, widget, cell_area); else g_warning ("GtkCellAreaClass::render not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); } /* Geometry */ GtkSizeRequestMode gtk_cell_area_get_request_mode (GtkCellArea *area) { GtkCellAreaClass *class; g_return_val_if_fail (GTK_IS_CELL_AREA (area), GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH); class = GTK_CELL_AREA_GET_CLASS (area); if (class->get_request_mode) return class->get_request_mode (area); g_warning ("GtkCellAreaClass::get_request_mode not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; } void gtk_cell_area_get_preferred_width (GtkCellArea *area, GtkWidget *widget, gint *minimum_size, gint *natural_size) { GtkCellAreaClass *class; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_WIDGET (widget)); class = GTK_CELL_AREA_GET_CLASS (area); if (class->get_preferred_width) class->get_preferred_width (area, widget, minimum_size, natural_size); else g_warning ("GtkCellAreaClass::get_preferred_width not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); } void gtk_cell_area_get_preferred_height_for_width (GtkCellArea *area, GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { GtkCellAreaClass *class; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_WIDGET (widget)); class = GTK_CELL_AREA_GET_CLASS (area); class->get_preferred_height_for_width (area, widget, width, minimum_height, natural_height); } void gtk_cell_area_get_preferred_height (GtkCellArea *area, GtkWidget *widget, gint *minimum_size, gint *natural_size) { GtkCellAreaClass *class; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_WIDGET (widget)); class = GTK_CELL_AREA_GET_CLASS (area); if (class->get_preferred_height) class->get_preferred_height (area, widget, minimum_size, natural_size); else g_warning ("GtkCellAreaClass::get_preferred_height not implemented for `%s'", g_type_name (G_TYPE_FROM_INSTANCE (area))); } void gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area, GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { GtkCellAreaClass *class; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_WIDGET (widget)); class = GTK_CELL_AREA_GET_CLASS (area); class->get_preferred_width_for_height (area, widget, height, minimum_width, natural_width); } void gtk_cell_area_attribute_connect (GtkCellArea *area, GtkCellRenderer *renderer, const gchar *attribute, gint column) { GtkCellAreaPrivate *priv; CellInfo *info; CellAttribute *cell_attribute; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); g_return_if_fail (attribute != NULL); priv = area->priv; info = g_hash_table_lookup (priv->cell_info, renderer); if (!info) { info = cell_info_new (NULL, NULL, NULL); g_hash_table_insert (priv->cell_info, renderer, info); } else { GSList *node; /* Check we are not adding the same attribute twice */ if ((node = g_slist_find_custom (info->attributes, attribute, (GCompareFunc)cell_attribute_find)) != NULL) { cell_attribute = node->data; g_warning ("Cannot connect attribute `%s' for cell renderer class `%s' " "since `%s' is already attributed to column %d", attribute, g_type_name (G_TYPE_FROM_INSTANCE (area)), attribute, cell_attribute->column); return; } } cell_attribute = cell_attribute_new (renderer, attribute, column); if (!cell_attribute) { g_warning ("Cannot connect attribute `%s' for cell renderer class `%s' " "since attribute does not exist", attribute, g_type_name (G_TYPE_FROM_INSTANCE (area))); return; } info->attributes = g_slist_prepend (info->attributes, cell_attribute); } void gtk_cell_area_attribute_disconnect (GtkCellArea *area, GtkCellRenderer *renderer, const gchar *attribute) { GtkCellAreaPrivate *priv; CellInfo *info; CellAttribute *cell_attribute; GSList *node; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); g_return_if_fail (attribute != NULL); priv = area->priv; info = g_hash_table_lookup (priv->cell_info, renderer); if (info) { node = g_slist_find_custom (info->attributes, attribute, (GCompareFunc)cell_attribute_find); if (node) { cell_attribute = node->data; cell_attribute_free (cell_attribute); info->attributes = g_slist_delete_link (info->attributes, node); } } } static void apply_cell_attributes (GtkCellRenderer *renderer, CellInfo *info, AttributeData *data) { CellAttribute *attribute; GSList *list; GValue value = { 0, }; /* Apply the attributes directly to the renderer */ for (list = info->attributes; list; list = list->next) { attribute = list->data; gtk_tree_model_get_value (data->model, data->iter, attribute->column, &value); g_object_set_property (G_OBJECT (renderer), attribute->attribute, &value); g_value_unset (&value); } /* Call any GtkCellLayoutDataFunc that may have been set by the user */ if (info->func) info->func (GTK_CELL_LAYOUT (data->area), renderer, data->model, data->iter, info->data); } void gtk_cell_area_apply_attributes (GtkCellArea *area, GtkTreeModel *tree_model, GtkTreeIter *iter) { GtkCellAreaPrivate *priv; AttributeData data; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_TREE_MODEL (tree_model)); g_return_if_fail (iter != NULL); priv = area->priv; /* Go over any cells that have attributes or custom GtkCellLayoutDataFuncs and * apply the data from the treemodel */ g_hash_table_foreach (priv->cell_info, (GHFunc)apply_cell_attributes, &data); }