/* * glade-gtk-list-store.c - GladeWidgetAdaptor for GtkListStore * * Copyright (C) 2013 Tristan Van Berkom * * Authors: * Tristan Van Berkom * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "glade-cell-renderer-editor.h" #include "glade-store-editor.h" #include "glade-column-types.h" #include "glade-model-data.h" #include "glade-gtk-cell-layout.h" #define GLADE_TAG_COLUMNS "columns" #define GLADE_TAG_COLUMN "column" #define GLADE_TAG_TYPE "type" #define GLADE_TAG_ROW "row" #define GLADE_TAG_DATA "data" #define GLADE_TAG_COL "col" static void glade_gtk_store_set_columns (GObject *object, const GValue *value) { GList *l; gint i, n; GType *types; for (i = 0, l = g_value_get_boxed (value), n = g_list_length (l), types = g_new (GType, n); l; l = g_list_next (l), i++) { GladeColumnType *data = l->data; if (g_type_from_name (data->type_name) != G_TYPE_INVALID) types[i] = g_type_from_name (data->type_name); else types[i] = G_TYPE_POINTER; } if (GTK_IS_LIST_STORE (object)) gtk_list_store_set_column_types (GTK_LIST_STORE (object), n, types); else gtk_tree_store_set_column_types (GTK_TREE_STORE (object), n, types); g_free (types); } static void glade_gtk_store_set_data (GObject *object, const GValue *value) { GladeWidget *gwidget = glade_widget_get_from_gobject (object); GList *columns = NULL; GNode *data_tree, *row, *iter; gint colnum; GtkTreeIter row_iter; GladeModelData *data; GType column_type; if (GTK_IS_LIST_STORE (object)) gtk_list_store_clear (GTK_LIST_STORE (object)); else gtk_tree_store_clear (GTK_TREE_STORE (object)); glade_widget_property_get (gwidget, "columns", &columns); data_tree = g_value_get_boxed (value); /* Nothing to enter without columns defined */ if (!data_tree || !columns) return; for (row = data_tree->children; row; row = row->next) { if (GTK_IS_LIST_STORE (object)) gtk_list_store_append (GTK_LIST_STORE (object), &row_iter); else /* (for now no child data... ) */ gtk_tree_store_append (GTK_TREE_STORE (object), &row_iter, NULL); for (colnum = 0, iter = row->children; iter; colnum++, iter = iter->next) { data = iter->data; if (!g_list_nth (columns, colnum)) break; /* Abort if theres a type mismatch, the widget's being rebuilt * and a sync will come soon with the right values */ column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (object), colnum); if (G_VALUE_TYPE (&data->value) != column_type) continue; if (GTK_IS_LIST_STORE (object)) gtk_list_store_set_value (GTK_LIST_STORE (object), &row_iter, colnum, &data->value); else gtk_tree_store_set_value (GTK_TREE_STORE (object), &row_iter, colnum, &data->value); } } } void glade_gtk_store_set_property (GladeWidgetAdaptor *adaptor, GObject *object, const gchar *property_name, const GValue *value) { if (strcmp (property_name, "columns") == 0) { glade_gtk_store_set_columns (object, value); } else if (strcmp (property_name, "data") == 0) { glade_gtk_store_set_data (object, value); } else /* Chain Up */ GWA_GET_CLASS (G_TYPE_OBJECT)->set_property (adaptor, object, property_name, value); } GladeEditorProperty * glade_gtk_store_create_eprop (GladeWidgetAdaptor *adaptor, GladePropertyClass *klass, gboolean use_command) { GladeEditorProperty *eprop; GParamSpec *pspec; pspec = glade_property_class_get_pspec (klass); /* chain up.. */ if (pspec->value_type == GLADE_TYPE_COLUMN_TYPE_LIST) eprop = g_object_new (GLADE_TYPE_EPROP_COLUMN_TYPES, "property-class", klass, "use-command", use_command, NULL); else if (pspec->value_type == GLADE_TYPE_MODEL_DATA_TREE) eprop = g_object_new (GLADE_TYPE_EPROP_MODEL_DATA, "property-class", klass, "use-command", use_command, NULL); else eprop = GWA_GET_CLASS (G_TYPE_OBJECT)->create_eprop (adaptor, klass, use_command); return eprop; } static void glade_gtk_store_columns_changed (GladeProperty *property, GValue *old_value, GValue *new_value, GladeWidget *store) { GList *l, *list, *children, *prop_refs; /* Reset the attributes for all cell renderers referring to this store */ prop_refs = glade_widget_list_prop_refs (store); for (l = prop_refs; l; l = l->next) { GladeWidget *referring_widget = glade_property_get_widget (GLADE_PROPERTY (l->data)); GObject *referring_object = glade_widget_get_object (referring_widget); if (GTK_IS_CELL_LAYOUT (referring_object)) glade_gtk_cell_layout_sync_attributes (referring_object); else if (GTK_IS_TREE_VIEW (referring_object)) { children = glade_widget_get_children (referring_widget); for (list = children; list; list = list->next) { /* Clear the GtkTreeViewColumns... */ if (GTK_IS_CELL_LAYOUT (list->data)) glade_gtk_cell_layout_sync_attributes (G_OBJECT (list->data)); } g_list_free (children); } } g_list_free (prop_refs); } void glade_gtk_store_post_create (GladeWidgetAdaptor *adaptor, GObject *object, GladeCreateReason reason) { GladeWidget *gwidget; GladeProperty *property; if (reason == GLADE_CREATE_REBUILD) return; gwidget = glade_widget_get_from_gobject (object); property = glade_widget_get_property (gwidget, "columns"); /* Here we watch the value-changed signal on the "columns" property, we need * to reset all the Cell Renderer attributes when the underlying "columns" change, * the reason we do it from "value-changed" is because GladeWidget prop references * are unavailable while rebuilding an object, and the liststore needs to be rebuilt * in order to set the columns. * * This signal will be envoked after applying the new column types to the store * and before the views get any signal to update themselves from the changed model, * perfect time to reset the attributes. */ g_signal_connect (G_OBJECT (property), "value-changed", G_CALLBACK (glade_gtk_store_columns_changed), gwidget); } GladeEditable * glade_gtk_store_create_editable (GladeWidgetAdaptor *adaptor, GladeEditorPageType type) { GladeEditable *editable; /* Get base editable */ editable = GWA_GET_CLASS (G_TYPE_OBJECT)->create_editable (adaptor, type); if (type == GLADE_PAGE_GENERAL) return (GladeEditable *) glade_store_editor_new (adaptor, editable); return editable; } gchar * glade_gtk_store_string_from_value (GladeWidgetAdaptor *adaptor, GladePropertyClass *klass, const GValue *value) { GString *string; GParamSpec *pspec; pspec = glade_property_class_get_pspec (klass); if (pspec->value_type == GLADE_TYPE_COLUMN_TYPE_LIST) { GList *l; string = g_string_new (""); for (l = g_value_get_boxed (value); l; l = g_list_next (l)) { GladeColumnType *data = l->data; g_string_append_printf (string, (g_list_next (l)) ? "%s:%s|" : "%s:%s", data->type_name, data->column_name); } return g_string_free (string, FALSE); } else if (pspec->value_type == GLADE_TYPE_MODEL_DATA_TREE) { GladeModelData *data; GNode *data_tree, *row, *iter; gint rownum; gchar *str; gboolean is_last; /* Return a unique string for the backend to compare */ data_tree = g_value_get_boxed (value); if (!data_tree || !data_tree->children) return g_strdup (""); string = g_string_new (""); for (rownum = 0, row = data_tree->children; row; rownum++, row = row->next) { for (iter = row->children; iter; iter = iter->next) { data = iter->data; if (!G_VALUE_TYPE (&data->value) || G_VALUE_TYPE (&data->value) == G_TYPE_INVALID) str = g_strdup ("(virtual)"); else if (G_VALUE_TYPE (&data->value) != G_TYPE_POINTER) str = glade_utils_string_from_value (&data->value); else str = g_strdup ("(null)"); is_last = !row->next && !iter->next; g_string_append_printf (string, "%s[%d]:%s", data->name, rownum, str); if (data->i18n_translatable) g_string_append_printf (string, " translatable"); if (data->i18n_context) g_string_append_printf (string, " i18n-context:%s", data->i18n_context); if (data->i18n_comment) g_string_append_printf (string, " i18n-comment:%s", data->i18n_comment); if (!is_last) g_string_append_printf (string, "|"); g_free (str); } } return g_string_free (string, FALSE); } else return GWA_GET_CLASS (G_TYPE_OBJECT)->string_from_value (adaptor, klass, value); } static void glade_gtk_store_write_columns (GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node) { GladeXmlNode *columns_node; GladeProperty *prop; GList *l; prop = glade_widget_get_property (widget, "columns"); columns_node = glade_xml_node_new (context, GLADE_TAG_COLUMNS); for (l = g_value_get_boxed (glade_property_inline_value (prop)); l; l = g_list_next (l)) { GladeColumnType *data = l->data; GladeXmlNode *column_node, *comment_node; /* Write column names in comments... */ gchar *comment = g_strdup_printf (" column-name %s ", data->column_name); comment_node = glade_xml_node_new_comment (context, comment); glade_xml_node_append_child (columns_node, comment_node); g_free (comment); column_node = glade_xml_node_new (context, GLADE_TAG_COLUMN); glade_xml_node_append_child (columns_node, column_node); glade_xml_node_set_property_string (column_node, GLADE_TAG_TYPE, data->type_name); } if (!glade_xml_node_get_children (columns_node)) glade_xml_node_delete (columns_node); else glade_xml_node_append_child (node, columns_node); } static void glade_gtk_store_write_data (GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node) { GladeXmlNode *data_node, *col_node, *row_node; GList *columns = NULL; GladeModelData *data; GNode *data_tree = NULL, *row, *iter; gint colnum; glade_widget_property_get (widget, "data", &data_tree); glade_widget_property_get (widget, "columns", &columns); /* XXX log errors about data not fitting columns here when * loggin is available */ if (!data_tree || !columns) return; data_node = glade_xml_node_new (context, GLADE_TAG_DATA); for (row = data_tree->children; row; row = row->next) { row_node = glade_xml_node_new (context, GLADE_TAG_ROW); glade_xml_node_append_child (data_node, row_node); for (colnum = 0, iter = row->children; iter; colnum++, iter = iter->next) { gchar *string, *column_number; data = iter->data; /* Skip non-serializable data */ if (G_VALUE_TYPE (&data->value) == 0 || G_VALUE_TYPE (&data->value) == G_TYPE_POINTER) continue; string = glade_utils_string_from_value (&data->value); /* XXX Log error: data col j exceeds columns on row i */ if (!g_list_nth (columns, colnum)) break; column_number = g_strdup_printf ("%d", colnum); col_node = glade_xml_node_new (context, GLADE_TAG_COL); glade_xml_node_append_child (row_node, col_node); glade_xml_node_set_property_string (col_node, GLADE_TAG_ID, column_number); glade_xml_set_content (col_node, string); if (data->i18n_translatable) glade_xml_node_set_property_string (col_node, GLADE_TAG_TRANSLATABLE, GLADE_XML_TAG_I18N_TRUE); if (data->i18n_context) glade_xml_node_set_property_string (col_node, GLADE_TAG_CONTEXT, data->i18n_context); if (data->i18n_comment) glade_xml_node_set_property_string (col_node, GLADE_TAG_COMMENT, data->i18n_comment); g_free (column_number); g_free (string); } } if (!glade_xml_node_get_children (data_node)) glade_xml_node_delete (data_node); else glade_xml_node_append_child (node, data_node); } void glade_gtk_store_write_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlContext *context, GladeXmlNode *node) { if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) return; /* First chain up and write all the normal properties.. */ GWA_GET_CLASS (G_TYPE_OBJECT)->write_widget (adaptor, widget, context, node); glade_gtk_store_write_columns (widget, context, node); glade_gtk_store_write_data (widget, context, node); } static void glade_gtk_store_read_columns (GladeWidget *widget, GladeXmlNode *node) { GladeNameContext *context; GladeXmlNode *columns_node; GladeProperty *property; GladeXmlNode *prop; GList *types = NULL; GValue value = { 0, }; gchar column_name[256]; column_name[0] = '\0'; column_name[255] = '\0'; if ((columns_node = glade_xml_search_child (node, GLADE_TAG_COLUMNS)) == NULL) return; context = glade_name_context_new (); for (prop = glade_xml_node_get_children_with_comments (columns_node); prop; prop = glade_xml_node_next_with_comments (prop)) { GladeColumnType *data; gchar *type, *comment_str, buffer[256]; if (!glade_xml_node_verify_silent (prop, GLADE_TAG_COLUMN) && !glade_xml_node_is_comment (prop)) continue; if (glade_xml_node_is_comment (prop)) { comment_str = glade_xml_get_content (prop); if (sscanf (comment_str, " column-name %s", buffer) == 1) strncpy (column_name, buffer, 255); g_free (comment_str); continue; } type = glade_xml_get_property_string_required (prop, GLADE_TAG_TYPE, NULL); if (!column_name[0]) { gchar *cname = g_ascii_strdown (type, -1); data = glade_column_type_new (type, cname); g_free (cname); } else data = glade_column_type_new (type, column_name); if (glade_name_context_has_name (context, data->column_name)) { gchar *name = glade_name_context_new_name (context, data->column_name); g_free (data->column_name); data->column_name = name; } glade_name_context_add_name (context, data->column_name); types = g_list_prepend (types, data); g_free (type); column_name[0] = '\0'; } glade_name_context_destroy (context); property = glade_widget_get_property (widget, "columns"); g_value_init (&value, GLADE_TYPE_COLUMN_TYPE_LIST); g_value_take_boxed (&value, g_list_reverse (types)); glade_property_set_value (property, &value); g_value_unset (&value); } static void glade_gtk_store_read_data (GladeWidget *widget, GladeXmlNode *node) { GladeXmlNode *data_node, *row_node, *col_node; GNode *data_tree, *row, *item; GladeModelData *data; GValue *value; GList *column_types = NULL; GladeColumnType *column_type; gint colnum; if ((data_node = glade_xml_search_child (node, GLADE_TAG_DATA)) == NULL) return; /* XXX FIXME: Warn that columns werent there when parsing */ if (!glade_widget_property_get (widget, "columns", &column_types) || !column_types) return; /* Create root... */ data_tree = g_node_new (NULL); for (row_node = glade_xml_node_get_children (data_node); row_node; row_node = glade_xml_node_next (row_node)) { gchar *value_str; if (!glade_xml_node_verify (row_node, GLADE_TAG_ROW)) continue; row = g_node_new (NULL); g_node_append (data_tree, row); /* XXX FIXME: we are assuming that the columns are listed in order */ for (colnum = 0, col_node = glade_xml_node_get_children (row_node); col_node; col_node = glade_xml_node_next (col_node)) { gint read_column; if (!glade_xml_node_verify (col_node, GLADE_TAG_COL)) continue; read_column = glade_xml_get_property_int (col_node, GLADE_TAG_ID, -1); if (read_column < 0) { g_critical ("Parsed negative column id"); continue; } /* Catch up for gaps in the list where unserializable types are involved */ while (colnum < read_column) { column_type = g_list_nth_data (column_types, colnum); data = glade_model_data_new (G_TYPE_INVALID, column_type->column_name); item = g_node_new (data); g_node_append (row, item); colnum++; } if (!(column_type = g_list_nth_data (column_types, colnum))) /* XXX Log this too... */ continue; /* Ignore unloaded column types for the workspace */ if (g_type_from_name (column_type->type_name) != G_TYPE_INVALID) { /* XXX Do we need object properties to somehow work at load time here ?? * should we be doing this part in "finished" ? ... todo thinkso... */ value_str = glade_xml_get_content (col_node); value = glade_utils_value_from_string (g_type_from_name (column_type->type_name), value_str, glade_widget_get_project (widget)); g_free (value_str); data = glade_model_data_new (g_type_from_name (column_type->type_name), column_type->column_name); g_value_copy (value, &data->value); g_value_unset (value); g_free (value); } else { data = glade_model_data_new (G_TYPE_INVALID, column_type->column_name); } data->i18n_translatable = glade_xml_get_property_boolean (col_node, GLADE_TAG_TRANSLATABLE, FALSE); data->i18n_context = glade_xml_get_property_string (col_node, GLADE_TAG_CONTEXT); data->i18n_comment = glade_xml_get_property_string (col_node, GLADE_TAG_COMMENT); item = g_node_new (data); g_node_append (row, item); /* dont increment colnum on invalid xml tags... */ colnum++; } } if (data_tree->children) glade_widget_property_set (widget, "data", data_tree); glade_model_data_tree_free (data_tree); } void glade_gtk_store_read_widget (GladeWidgetAdaptor *adaptor, GladeWidget *widget, GladeXmlNode *node) { if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) return; /* First chain up and read in all the normal properties.. */ GWA_GET_CLASS (G_TYPE_OBJECT)->read_widget (adaptor, widget, node); glade_gtk_store_read_columns (widget, node); if (GTK_IS_LIST_STORE (glade_widget_get_object (widget))) glade_gtk_store_read_data (widget, node); }