/* * Copyright (C) 2012 - Juan Ferrer Toribio * * 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 . */ #include "vn-list-model.h" typedef struct { gchar * detail; gpointer callback; } GvnSignalInfo; /* * gvn_object_link: * @obj: instance * @prop: property of @obj to be set to @prop_value * @prop_value: the value to set on @prop * @info: an array of GvnSignalInfo * * Connects the signals of an object passed on @prop_value to the callbacks * listed in a GvnSignalInfo array. */ void _gvn_object_link (gpointer self, gpointer * prop, gpointer prop_value, GvnSignalInfo * info) { gint i; if (*prop) { for (i = 0; info[i].detail; i++) g_signal_handlers_disconnect_by_func (*prop, info[i].callback, self); g_object_unref (*prop); } if (prop_value) { for (i = 0; info[i].detail; i++) if (g_str_has_suffix (info[i].detail, ".after")) { gchar ** split = g_strsplit (info[i].detail, ".", 0); g_signal_connect_after (prop_value, split[0], info[i].callback, self); g_strfreev (split); } else g_signal_connect (prop_value, info[i].detail, info[i].callback, self); *prop = g_object_ref_sink (prop_value); } else *prop = NULL; } #define gvn_object_link(self, prop, prop_value, info) (_gvn_object_link (self, (void**) prop, prop_value, info)) static void vn_list_model_tree_model_init (GtkTreeModelIface * iface); static void vn_list_model_tree_sortable_init (GtkTreeSortableIface * iface); G_DEFINE_TYPE_WITH_CODE (VnListModel, vn_list_model, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, vn_list_model_tree_model_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE, vn_list_model_tree_sortable_init) ); /** * vn_list_model_new: * @model: a #DbModel * * Creates a new #VnListModel with @model as the data model. * * Return value: (transfer full): a #VnListModel. **/ VnListModel * vn_list_model_new (DbModel * model) { return g_object_new (VN_TYPE_LIST_MODEL, "model", model, NULL); } /** * vn_list_model_get_model: * @obj: a #VnListModel * * Passes the #DbModel used internally by @model. * * Return value: (transfer none): a #DbModel. **/ DbModel * vn_list_model_get_model (VnListModel * self) { g_return_val_if_fail (VN_IS_LIST_MODEL (self), NULL); return self->model; } /** * vn_list_model_use_null_row: * @self: a #VnListModel * @use: wether to use a null row * * Sets wether to use a null row at the end of the model. This row can't be * modified, its purpose is to set a #NULL option in selection widgets, like * #VnCombo. **/ void vn_list_model_use_null_row (VnListModel * self, gboolean use) { g_return_if_fail (VN_IS_LIST_MODEL (self)); self->null_row = use; } /** * vn_gtk_tree_iter_from_db_iter: * @dest_iter: the #GtkTreeIter to set using the data on @src_iter * @src_iter: a valid #DbIter * * Sets a #GtkTreeIter with the data of a #DbIter. This function is mostly used * internally. **/ void gtk_tree_iter_from_db_iter (GtkTreeIter * dest_iter, const DbIter * src_iter) { dest_iter->stamp = src_iter->stamp; dest_iter->user_data = src_iter->data; } /** * vn_gtk_tree_iter_to_db_iter: * @src_iter: a valid #GtkTreeIter * @dest_iter: the #DbIter to set using the data on @src_iter * * Sets a with #DbIter the data of a #GtkTreeIter. This function is mostly used * internally. **/ void gtk_tree_iter_to_db_iter (const GtkTreeIter * src_iter, DbIter * dest_iter) { dest_iter->stamp = src_iter->stamp; dest_iter->data = src_iter->user_data; } /** * vn_list_model_iter_is_valid: * @iter: a #GtkTreeIter * @model: a #GtkTreeModel * * Checks if @iter is a valid #GtkTreeIter pointing inside #GtkTreeModel. **/ gboolean vn_list_model_iter_is_valid (GtkTreeIter * iter, GtkTreeModel * model) { DbIter dbiter; g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE); gtk_tree_iter_to_db_iter (iter, &dbiter); return db_model_iter_is_valid (&dbiter, VN_LIST_MODEL (model)->model); } static gint vn_list_model_get_nrows (GtkTreeModel * self) { gint nrows = db_model_get_nrows (VN_LIST_MODEL (self)->model); return VN_LIST_MODEL (self)->null_row ? nrows + 1 : nrows; } //++++ Broadcast of GtkTreeModel and GtkTreeSortable signals: static void vn_list_model_on_line_updated (DbModel * model, DbIter * dbiter, GtkTreeModel * self) { GtkTreeIter iter; GtkTreePath * path; gint p = db_model_get_path (model, dbiter); p = VN_LIST_MODEL (self)->null_row ? p + 1 : p; gtk_tree_iter_from_db_iter (&iter, dbiter); path = gtk_tree_path_new (); gtk_tree_path_append_index (path, p); gtk_tree_model_row_changed (self, path, &iter); gtk_tree_path_free (path); } static void vn_list_model_on_line_inserted (DbModel * model, DbIter * dbiter, GtkTreeModel * self) { GtkTreeIter iter; GtkTreePath * path; gint p = db_model_get_path (model, dbiter); p = VN_LIST_MODEL (self)->null_row ? p + 1 : p; gtk_tree_iter_from_db_iter (&iter, dbiter); path = gtk_tree_path_new (); gtk_tree_path_append_index (path, p); gtk_tree_model_row_inserted (self, path, &iter); gtk_tree_path_free (path); } static void vn_list_model_on_line_deleted (DbModel * model, gint pos, GtkTreeModel * self) { GtkTreePath * path = gtk_tree_path_new (); gtk_tree_path_append_index (path, VN_LIST_MODEL (self)->null_row ? pos + 1 : pos); if (gtk_tree_path_get_indices (path)) gtk_tree_model_row_deleted (self, path); gtk_tree_path_free (path); } static void vn_list_model_on_lines_reordered (DbModel * model, guint col, gint * new_order, GtkTreeModel * self) { GtkTreePath * path = gtk_tree_path_new (); gtk_tree_model_rows_reordered (self, path, NULL, new_order); gtk_tree_path_free (path); } static void vn_list_model_on_sort_changed (DbModel * model, GtkTreeSortable * self) { g_signal_emit_by_name (self, "sort-column-changed"); } //++++ Implementation of GtkTreeModel methods (using DbModel methods) static GtkTreeModelFlags vn_list_model_get_flags (GtkTreeModel * self) { return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY; } static gint vn_list_model_get_ncols (GtkTreeModel * self) { return db_model_get_ncols (VN_LIST_MODEL (self)->model); } static GType vn_list_model_get_column_type (GtkTreeModel * self, gint index_) { return db_model_get_column_type (VN_LIST_MODEL (self)->model, index_); } static gboolean vn_list_model_get_iter (GtkTreeModel * self, GtkTreeIter * iter, GtkTreePath * path) { gboolean ok = FALSE; gint p = gtk_tree_path_get_indices (path)[0]; p = VN_LIST_MODEL (self)->null_row ? p - 1 : p; if (p >= 0) { DbIter dbiter; ok = db_model_get_iter (VN_LIST_MODEL (self)->model, &dbiter, p); if (ok) gtk_tree_iter_from_db_iter (iter, &dbiter); } else { iter->user_data = self; ok = TRUE; } return ok; } static GtkTreePath * vn_list_model_get_path (GtkTreeModel * self, GtkTreeIter * iter) { gint p; GtkTreePath * path; if (iter->user_data != self) { DbIter dbiter; gtk_tree_iter_to_db_iter (iter, &dbiter); p = db_model_get_path (VN_LIST_MODEL (self)->model, &dbiter); p = VN_LIST_MODEL (self)->null_row ? p + 1 : p; } else p = 0; path = gtk_tree_path_new (); gtk_tree_path_append_index (path, p); return path; } static void vn_list_model_get_value (GtkTreeModel * self, GtkTreeIter * iter, gint column, GValue * value) { GValue * v = NULL; if (iter->user_data != self) { DbIter dbiter; gtk_tree_iter_to_db_iter (iter, &dbiter); v = (GValue *) db_model_get_value (VN_LIST_MODEL (self)->model, &dbiter, column, NULL); } if (v) { g_value_init (value, G_VALUE_TYPE (v)); g_value_copy (v, value); } else g_value_init (value, GVN_TYPE_NULL); } static gboolean vn_list_model_iter_next (GtkTreeModel * self, GtkTreeIter * iter) { DbIter dbiter; gboolean ret_val; if (iter->user_data != self) { gtk_tree_iter_to_db_iter (iter, &dbiter); ret_val = db_model_iter_next (VN_LIST_MODEL (self)->model, &dbiter); } else ret_val = db_model_get_iter_first (VN_LIST_MODEL (self)->model, &dbiter); gtk_tree_iter_from_db_iter (iter, &dbiter); return ret_val; } static gboolean vn_list_model_iter_previous (GtkTreeModel * self, GtkTreeIter * iter) { if (iter->user_data != self) { DbIter dbiter; DbModel * model = VN_LIST_MODEL (self)->model; gtk_tree_iter_to_db_iter (iter, &dbiter); if (db_model_iter_prev (model, &dbiter)) { iter->user_data = dbiter.data; return TRUE; } else if (db_model_get_iter_first (model, &dbiter) && iter->user_data == dbiter.data) { iter->user_data = self; return TRUE; } } return FALSE; } static gboolean vn_list_model_iter_children (GtkTreeModel * self, GtkTreeIter * iter, GtkTreeIter * parent) { if (parent == NULL) { gboolean ret_val; if (!VN_LIST_MODEL (self)->null_row) { DbIter dbiter; ret_val = db_model_get_iter_first (VN_LIST_MODEL (self)->model, &dbiter); if (ret_val) gtk_tree_iter_from_db_iter (iter, &dbiter); } else { iter->user_data = self; ret_val = TRUE; } return ret_val; } return FALSE; } static gboolean vn_list_model_iter_has_child (GtkTreeModel * self, GtkTreeIter * iter) { return FALSE; } static gint vn_list_model_iter_n_children (GtkTreeModel * self, GtkTreeIter * iter) { if (iter == NULL) { gint nrows = vn_list_model_get_nrows (self); return VN_LIST_MODEL (self)->null_row ? nrows + 1 : nrows; } return 0; } static gboolean vn_list_model_iter_nth_child (GtkTreeModel * self, GtkTreeIter * iter ,GtkTreeIter * parent, gint n) { if (parent == NULL) { DbIter dbiter; gboolean ok = db_model_get_iter (VN_LIST_MODEL (self)->model, &dbiter, VN_LIST_MODEL (self)->null_row ? n - 1 : n); if (ok) { gtk_tree_iter_from_db_iter (iter, &dbiter); return TRUE; } } return FALSE; } static gboolean vn_list_model_iter_parent (GtkTreeModel * self, GtkTreeIter * iter, GtkTreeIter * child) { return FALSE; } //++++ Implementation of GtkTreeSortable methods (using DbModel methods) static gboolean vn_list_model_get_sort_column_id (GtkTreeSortable * self, gint * sort_column_id, GtkSortType * order) { return db_model_get_sort_column_id (VN_LIST_MODEL (self)->model ,sort_column_id, (DbSortType *) order); } static void vn_list_model_set_sort_column_id (GtkTreeSortable * self, gint sort_column_id, GtkSortType order) { DbSortType db_order; if (order == GTK_SORT_ASCENDING) db_order = DB_SORT_ASCENDING; else db_order = DB_SORT_DESCENDING; db_model_set_sort_column_id (VN_LIST_MODEL (self)->model, sort_column_id, db_order); } static void vn_list_model_set_sort_func (GtkTreeSortable *sortable, gint sort_column_id, GtkTreeIterCompareFunc sort_func, gpointer user_data, GDestroyNotify destroy) { g_warning ("Function vn_list_model_set_sort_func not implemented"); } static void vn_list_model_set_default_sort_func (GtkTreeSortable *sortable, GtkTreeIterCompareFunc sort_func, gpointer user_data, GDestroyNotify destroy) { g_warning ("Function vn_list_model_set_default_sort_func not implemented"); } static gboolean vn_list_model_has_default_sort_func (GtkTreeSortable * self) { return TRUE; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Properties typedef enum { PROP_MODEL = 1 ,PROP_NULL_ROW } VnListModelProp; /*Experimental way to connect a property object signals using gvn_object_link*/ GvnSignalInfo signal_info[] = { {"line-updated.after", vn_list_model_on_line_updated },{"line-inserted", vn_list_model_on_line_inserted },{"line-deleted.after", vn_list_model_on_line_deleted },{"lines-reordered", vn_list_model_on_lines_reordered },{"sort-changed", vn_list_model_on_sort_changed },{NULL} }; static void vn_list_model_set_property (VnListModel * self, guint property_id, const GValue * value, GParamSpec * pspec) { switch (property_id) { case PROP_MODEL: gvn_object_link (self, &self->model, g_value_get_object (value), signal_info); /*g_object_ref_sink (self->model); g_object_connect (self->model ,"signal-after::line-updated", vn_list_model_on_line_updated, obj ,"signal::line-inserted", vn_list_model_on_line_inserted, obj ,"signal-after::line-deleted", vn_list_model_on_line_deleted, obj ,"signal::lines-reordered", vn_list_model_on_lines_reordered, obj ,"signal::sort-changed", vn_list_model_on_sort_changed, self, NULL);*/ break; case PROP_NULL_ROW: vn_list_model_use_null_row (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); } } static void vn_list_model_get_property (VnListModel * self, guint property_id, GValue * value, GParamSpec * pspec) { switch (property_id) { case PROP_MODEL: g_value_set_object (value, self->model); break; case PROP_NULL_ROW: g_value_set_boolean (value, self->null_row); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); } } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Class static void vn_list_model_init (VnListModel * self) { self->model = NULL; self->null_row = FALSE; } static void vn_list_model_finalize (VnListModel * self) { gvn_object_link (self, &self->model, NULL, signal_info); /* g_object_disconnect (self->model ,"any_signal", vn_list_model_on_line_updated, obj ,"any_signal", vn_list_model_on_line_inserted, obj ,"any_signal", vn_list_model_on_line_deleted, obj ,"any_signal", vn_list_model_on_lines_reordered, obj ,"any_signal", vn_list_model_on_sort_changed, self, NULL); g_object_unref (self->model); */ G_OBJECT_CLASS (vn_list_model_parent_class)->finalize (G_OBJECT (self)); } static void vn_list_model_class_init (VnListModelClass * k) { GObjectClass * klass = G_OBJECT_CLASS (k); klass->set_property = (GObjectSetPropertyFunc) vn_list_model_set_property; klass->get_property = (GObjectGetPropertyFunc) vn_list_model_get_property; klass->finalize = (GObjectFinalizeFunc) vn_list_model_finalize; g_object_class_install_property (klass, PROP_MODEL, g_param_spec_object ("model" ,"Model" ,"The #DbModel with the data used by the #VnListModel" ,DB_TYPE_MODEL ,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE )); g_object_class_install_property (klass, PROP_NULL_ROW, g_param_spec_boolean ("null-row" ,_("Null row") ,_("Whether the list model will add a null row or not") ,FALSE ,G_PARAM_READWRITE )); } static void vn_list_model_tree_model_init (GtkTreeModelIface * iface) { iface->get_flags = vn_list_model_get_flags; iface->get_n_columns = vn_list_model_get_ncols; iface->get_column_type = vn_list_model_get_column_type; iface->get_iter = vn_list_model_get_iter; iface->get_path = vn_list_model_get_path; iface->get_value = vn_list_model_get_value; iface->iter_next = vn_list_model_iter_next; iface->iter_previous = vn_list_model_iter_previous; iface->iter_children = vn_list_model_iter_children; iface->iter_has_child = vn_list_model_iter_has_child; iface->iter_n_children = vn_list_model_iter_n_children; iface->iter_nth_child = vn_list_model_iter_nth_child; iface->iter_parent = vn_list_model_iter_parent; } static void vn_list_model_tree_sortable_init (GtkTreeSortableIface * iface) { iface->get_sort_column_id = vn_list_model_get_sort_column_id; iface->set_sort_column_id = vn_list_model_set_sort_column_id; iface->set_sort_func = vn_list_model_set_sort_func; iface->set_default_sort_func = vn_list_model_set_default_sort_func; iface->has_default_sort_func = vn_list_model_has_default_sort_func; }