/*
* glade-gtk-list-store.c - GladeWidgetAdaptor for GtkListStore
*
* Copyright (C) 2013 Tristan Van Berkom
*
* Authors:
* Tristan Van Berkom <tristan.van.berkom@gmail.com>
*
* 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 <config.h>
#include <glib/gi18n-lib.h>
#include <gladeui/glade.h>
#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);
}