/* * 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-grid.h" static void vn_grid_model_holder_init (DbModelHolderInterface * iface); static void vn_grid_iterator_init (DbIteratorInterface * iface); static void vn_grid_on_iter_changed (GtkTreeView * self); static void vn_grid_set_iter (VnGrid * self, DbIter * iter); static gboolean vn_grid_move_iter (VnGrid * self, DbIter * iter); /** * SECTION: vn-grid * @Short_description: a grid to display data * @Title: VnGrid * * #VnGrid is used to show data from a #DbModel organized in #VnColumns. */ G_DEFINE_TYPE_WITH_CODE (VnGrid, vn_grid, GTK_TYPE_TREE_VIEW, G_IMPLEMENT_INTERFACE (DB_TYPE_MODEL_HOLDER, vn_grid_model_holder_init) G_IMPLEMENT_INTERFACE (DB_TYPE_ITERATOR, vn_grid_iterator_init) ); /** * VnGrid: * @params: (element-type Db.Param): **/ /** * vn_grid_new: * @model: a #DbModel * * Creates a new grid * * Return value: (transfer full): #VnGrid created. **/ VnGrid * vn_grid_new (DbModel * model) { return g_object_new (VN_TYPE_GRID, "rules-hint", TRUE, "data-model", model, NULL); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Private static void vn_grid_unref_param (VnGrid * self, DbParam * param) { self->params = g_list_remove (self->params, param); } static gboolean vn_grid_on_key_pressed (GtkTreeView * self, GdkEventKey * event, gpointer data) { gboolean inverted, passed = FALSE; gint sel_tab_index; VnColumn * first = NULL; GList * n, * columns; GtkTreePath * path; GtkTreeViewColumn * col; VnColumn * selected, * next = NULL; GtkCellArea * area; GtkCellEditable * editable; // Remove new not inserted row when Escape is pressed if (event->type == GDK_KEY_PRESS && event->keyval == GDK_KEY_Escape && VN_GRID (self)->mode != DB_ITERATOR_MODE_ON_DEMAND) { db_iterator_reverse_operations (DB_ITERATOR (self)); return FALSE; } // Move cursor when pressing Tab, Shift + Tab or Keypad Enter if (!(event->type == GDK_KEY_PRESS && (event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_ISO_Left_Tab || event->keyval == GDK_KEY_KP_Enter))) return FALSE; gtk_tree_view_get_cursor (self, &path, &col); if (!col || !path) { gtk_tree_path_free (path); return FALSE; } selected = VN_COLUMN (col); g_object_get (col, "cell-area", &area, NULL); editable = gtk_cell_area_get_edit_widget (area); if (editable) gtk_cell_editable_editing_done (editable); g_object_unref (area); columns = gtk_tree_view_get_columns (self); inverted = event->keyval == GDK_KEY_ISO_Left_Tab ? TRUE : FALSE; sel_tab_index = vn_column_get_tab_index (selected); first = NULL; for (n = columns; n; n = n->next) { gint cur_tab_index; VnColumn * current = n->data; if (current == selected) passed = TRUE; else if (VN_IS_COLUMN (current) && vn_column_get_editable (current) && (cur_tab_index = vn_column_get_tab_index (current)) != -1) { if (!inverted) { if (!first || cur_tab_index < vn_column_get_tab_index (first)) first = current; if (cur_tab_index == sel_tab_index && passed) { next = current; break; } if ((!next || cur_tab_index < vn_column_get_tab_index (next)) && cur_tab_index > sel_tab_index) next = current; } else { if (!first || cur_tab_index >= vn_column_get_tab_index (first)) first = current; if ((cur_tab_index == sel_tab_index && !passed) || (cur_tab_index < sel_tab_index && (!next || cur_tab_index >= vn_column_get_tab_index (next)))) next = current; } } } g_list_free (columns); if (!next && path) { gboolean leave = FALSE; next = first ? first : selected; if (inverted) leave = !gtk_tree_path_prev (path); else { DbIter iter; if (db_model_get_iter (VN_GRID (self)->model, &iter, gtk_tree_path_get_indices (path)[0]) && db_model_get_row_operations (VN_GRID (self)->model, &iter) == DB_MODEL_ROW_OP_INSERT) leave = TRUE; else { gtk_tree_path_next (path); if (gtk_tree_path_get_indices (path)[0] >= db_model_get_nrows (VN_GRID (self)->model)) { if (db_model_get_update_flags (VN_GRID (self)->model) & DB_MODEL_INSERT) db_iterator_insert (DB_ITERATOR (self)); else leave = TRUE; } } } if (leave) { gtk_tree_path_free (path); return FALSE; } } gtk_tree_view_set_cursor (self, path, GTK_TREE_VIEW_COLUMN (next), TRUE); gtk_tree_path_free (path); return TRUE; } static void vn_grid_on_cursor_changed (GtkTreeView * self) { GtkTreeIter iter; GtkTreePath * path; if (gtk_tree_selection_count_selected_rows (gtk_tree_view_get_selection (self)) > 1) return; gtk_tree_view_get_cursor (self, &path, NULL); if (path && gtk_tree_model_get_iter (gtk_tree_view_get_model (self), &iter, path)) { DbIter dbiter; vn_gtk_tree_iter_to_db_iter (&iter, &dbiter); vn_grid_move_iter (VN_GRID (self), &dbiter); } gtk_tree_path_free (path); } static void vn_grid_on_iter_changed (GtkTreeView * self) { GtkTreeSelection * selection = gtk_tree_view_get_selection (self); if (VN_GRID (self)->iter && gtk_tree_view_get_model (self)) { GtkTreeIter iter; GtkTreePath * path; vn_gtk_tree_iter_from_db_iter (&iter, VN_GRID (self)->iter); path = gtk_tree_model_get_path (gtk_tree_view_get_model (self), &iter); g_signal_handlers_block_by_func (self, vn_grid_on_cursor_changed, NULL); gtk_tree_view_set_cursor (self, path, NULL, FALSE); g_signal_handlers_unblock_by_func (self, vn_grid_on_cursor_changed, NULL); gtk_tree_path_free (path); } else if (selection) gtk_tree_selection_unselect_all (selection); } static void vn_grid_on_model_changed (VnGrid * self, GParamSpec * spec, gpointer data) { GList * n, * columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (self)); for (n = columns; n; n = n->next) if (VN_IS_COLUMN (n->data)) vn_column_model_changed (n->data); g_list_free (columns); } static void vn_grid_row_num_changed (VnGrid * self) { if (self->iter) self->row = db_model_get_path (self->model, self->iter); else if (!self->remember_selection) self->row = -1; db_iterator_row_num_changed (DB_ITERATOR (self)); } static void vn_grid_iter_changed (VnGrid * self) { vn_grid_row_num_changed (self); db_iterator_iter_changed (DB_ITERATOR (self)); vn_grid_on_iter_changed (GTK_TREE_VIEW (self)); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ DbModelHolder methods static void vn_grid_set_model_mode (VnGrid * self) { if (!self->model) return; if (self->mode == DB_ITERATOR_MODE_ON_CHANGE) db_model_set_mode (self->model, DB_MODEL_MODE_ON_CHANGE); else db_model_set_mode (self->model, DB_MODEL_MODE_ON_DEMAND); } static void vn_grid_on_model_line_inserted (DbModel * model, DbIter * iter, VnGrid * self) { db_iterator_data_changed (DB_ITERATOR (self)); } static void vn_grid_on_model_line_updated_after (DbModel * model, DbIter * iter, VnGrid * self) { if (self->iter && db_iter_compare (iter, self->iter)) vn_grid_iter_changed (self); db_iterator_data_changed (DB_ITERATOR (self)); } static void vn_grid_on_model_line_deleted (DbModel * model, gint row, VnGrid * self) { if (self->iter && row == self->row) { DbIter iter; if (db_model_get_iter (model, &iter, row + 1) || db_model_get_iter (model, &iter, row - 1)) vn_grid_set_iter (self, &iter); else vn_grid_set_iter (self, NULL); } } static void vn_grid_on_model_line_deleted_after (DbModel * model, gint row, VnGrid * self) { if (self->iter) vn_grid_row_num_changed (self); db_iterator_data_changed (DB_ITERATOR (self)); } static void vn_grid_on_model_lines_reordered (DbModel * model, gint column, gint * new_order, VnGrid * self) { if (self->iter) vn_grid_row_num_changed (self); } static void vn_grid_on_model_status_changed (DbModel * model, DbModelStatus status, VnGrid * self) { self->model = db_model_holder_get_model (DB_MODEL_HOLDER (self)); vn_grid_on_model_changed (self, NULL, NULL); if (status == DB_MODEL_STATUS_READY) { DbIter iter; GtkTreeModel * tree_model; db_iterator_status_changed (DB_ITERATOR (self), TRUE); tree_model = GTK_TREE_MODEL (vn_grid_model_new (self->model)); gtk_tree_view_set_model (GTK_TREE_VIEW (self), tree_model); vn_grid_on_iter_changed (GTK_TREE_VIEW (self)); if (self->row >= 0 && self->row < db_model_get_nrows (self->model) && db_model_get_iter (self->model, &iter, self->row)) vn_grid_set_iter (self, &iter); else vn_grid_set_iter (self, NULL); g_object_unref (tree_model); } else { vn_grid_set_iter (self, NULL); db_iterator_status_changed (DB_ITERATOR (self), FALSE); gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL); } } static void vn_grid_on_model_operations_done (DbModel * model, VnGrid * self) { g_return_val_if_fail (VN_IS_GRID (self), NULL); db_iterator_operations_done (DB_ITERATOR (self)); } static DbModel * vn_grid_get_model (VnGrid * self) { g_return_val_if_fail (VN_IS_GRID (self), NULL); return self->model; } static void vn_grid_set_model (VnGrid * self, DbModel * model) { g_return_if_fail (VN_IS_GRID (self)); if (!model) return; if (!self->model) { self->model = g_object_ref (model); g_object_connect (model ,"signal::line-inserted", vn_grid_on_model_line_inserted, self ,"signal-after::line-updated", vn_grid_on_model_line_updated_after, self ,"signal::line-deleted", vn_grid_on_model_line_deleted, self ,"signal-after::line-deleted", vn_grid_on_model_line_deleted_after, self ,"signal::lines-reordered", vn_grid_on_model_lines_reordered, self ,"signal::status-changed", vn_grid_on_model_status_changed, self ,"signal::operations-done", vn_grid_on_model_operations_done, self ,NULL ); vn_grid_set_model_mode (self); vn_grid_on_model_status_changed (model, db_model_get_status (model), self); } else g_warning ("VnGrid: Can't reassign the 'data-model' property"); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ DbIterator methods static DbIteratorMode vn_grid_get_mode (VnGrid * self) { g_return_val_if_fail (VN_IS_GRID (self), 0); return self->mode; } static void vn_grid_set_mode (VnGrid * self, DbIteratorMode mode) { g_return_if_fail (VN_IS_GRID (self)); self->mode = mode; vn_grid_set_model_mode (self); } static gint vn_grid_get_row (VnGrid * self) { g_return_val_if_fail (VN_IS_GRID (self), -1); if (self->iter) return self->row; else return -1; } static gboolean vn_grid_get_iter (VnGrid * self, DbIter ** iter) { g_return_val_if_fail (VN_IS_GRID (self), NULL); if (!self->iter) return FALSE; *iter = self->iter; return TRUE; } static void vn_grid_set_iter (VnGrid * self, DbIter * iter) { if (iter) { db_iter_free (self->iter); self->iter = db_iter_copy (iter); vn_grid_iter_changed (self); } else if (self->iter) { if (!self->remember_selection) self->row = 0; db_iter_free (self->iter); self->iter = NULL; vn_grid_iter_changed (self); } } static gboolean vn_grid_move_iter (VnGrid * self, DbIter * iter) { g_return_val_if_fail (VN_IS_GRID (self), FALSE); if (self->iter && db_iter_compare (self->iter, iter)) return FALSE; if (self->iter && self->mode != DB_ITERATOR_MODE_ON_DEMAND) { if (db_model_get_row_operations (self->model, self->iter) == DB_MODEL_ROW_OP_INSERT) db_model_reverse_operations (self->model); db_model_perform_operations (self->model, FALSE); } vn_grid_set_iter (self, iter); return TRUE; } static GList * vn_grid_get_param_list (VnGrid * self) { g_return_val_if_fail (VN_IS_GRID (self), NULL); return self->params; } static void vn_grid_delete_selection (VnGrid * self) { GtkTreeModel * tree_model; GtkTreeSelection * selection; GList * selected, * l; g_return_val_if_fail (VN_IS_GRID (self), NULL); if (!self->model) return; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self)); selected = gtk_tree_selection_get_selected_rows (selection, NULL); tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); for (l = selected; l; l = l->next) { DbIter iter; GtkTreeIter tree_iter; gtk_tree_model_get_iter (tree_model, &tree_iter, l->data); vn_gtk_tree_iter_to_db_iter (&tree_iter, &iter); db_model_delete (self->model, &iter); } g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties typedef enum { PROP_MODEL = 1 ,PROP_MODE ,PROP_REMEMBER_SELECTION } VnGridProp; static void vn_grid_set_property (VnGrid * self, guint id, const GValue * value, GParamSpec * pspec) { switch (id) { case PROP_MODEL: vn_grid_set_model (self, g_value_get_object (value)); break; case PROP_MODE: vn_grid_set_mode (self, g_value_get_enum (value)); break; case PROP_REMEMBER_SELECTION: self->remember_selection = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } static void vn_grid_get_property (VnGrid * self, guint id, GValue * value, GParamSpec * pspec) { switch (id) { case PROP_MODEL: g_value_set_object (value, self->model); break; case PROP_MODE: g_value_set_enum (value, self->mode); break; case PROP_REMEMBER_SELECTION: g_value_set_boolean (value, self->remember_selection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Class static void vn_grid_init (VnGrid * self) { GtkTreeSelection * selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self)); self->mode = 0; self->iter = 0; self->row = 0; self->params = NULL; self->model = NULL; gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); g_signal_connect (self, "notify::model", G_CALLBACK (vn_grid_on_model_changed), NULL); g_object_connect (self ,"signal-after::cursor-changed", vn_grid_on_cursor_changed, NULL ,"signal::key-press-event", vn_grid_on_key_pressed, NULL ,NULL ); } static void vn_grid_finalize (VnGrid * self) { GList * n; for (n = self->params; n; n = n->next) g_object_weak_unref (n->data, (GWeakNotify) vn_grid_unref_param, self); g_list_free (self->params); db_iter_free (self->iter); self->iter = NULL; if (self->model) { g_object_disconnect (self->model ,"any_signal", vn_grid_on_model_line_inserted, self ,"any_signal", vn_grid_on_model_line_updated_after, self ,"any_signal", vn_grid_on_model_line_deleted, self ,"any_signal", vn_grid_on_model_line_deleted_after, self ,"any_signal", vn_grid_on_model_lines_reordered, self ,"any_signal", vn_grid_on_model_status_changed, self ,"any_signal", vn_grid_on_model_operations_done, self ,NULL ); g_object_unref (self->model); } G_OBJECT_CLASS (vn_grid_parent_class)->finalize (G_OBJECT (self)); } static void vn_grid_class_init (VnGridClass * klass) { GObjectClass * k = G_OBJECT_CLASS (klass); k->set_property = (GObjectSetPropertyFunc) vn_grid_set_property; k->get_property = (GObjectGetPropertyFunc) vn_grid_get_property; k->finalize = (GObjectFinalizeFunc) vn_grid_finalize; g_object_class_override_property (k, PROP_MODEL, "data-model"); g_object_class_override_property (k, PROP_MODE, "mode"); g_object_class_override_property (k, PROP_REMEMBER_SELECTION, "remember-selection"); } static void vn_grid_model_holder_init (DbModelHolderInterface * iface) { iface->get_model = (DbModelHolderGetModelFunc) vn_grid_get_model; iface->set_model = (DbModelHolderSetModelFunc) vn_grid_set_model; } static void vn_grid_iterator_init (DbIteratorInterface * iface) { iface->get_mode = (DbIteratorGetMode) vn_grid_get_mode; iface->set_mode = (DbIteratorSetMode) vn_grid_set_mode; iface->get_row = (DbIteratorGetRow) vn_grid_get_row; iface->get_iter = (DbIteratorGetIter) vn_grid_get_iter; iface->set_iter = (DbIteratorSetIter) vn_grid_set_iter; iface->move_iter = (DbIteratorMoveIter) vn_grid_move_iter; iface->get_param_list = (DbIteratorGetParamList) vn_grid_get_param_list; iface->delete_selection = (DbIteratorDeleteSelection) vn_grid_delete_selection; }