Bug #121695: Support account reparenting through drag and drop in the account tree view

Patch by Matt Lavin:
Here is a patch that implements drag and drop reparenting in the accounts page.
The function works for me, and I can't think of anything more that needs to be
done, but I'm new to the Gnucash codebase so a thorough review would be great.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18426 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming 2009-11-20 19:57:13 +00:00
parent da45865cd1
commit b647677f1b
5 changed files with 899 additions and 30 deletions

View File

@ -82,6 +82,7 @@ libgncmod_gnome_utils_la_SOURCES = \
gnc-tree-model.c \
gnc-tree-model-account-types.c \
gnc-tree-model-account.c \
gnc-tree-model-account-drag.c \
gnc-tree-model-budget.c \
gnc-tree-model-commodity.c \
gnc-tree-model-price.c \
@ -150,6 +151,7 @@ gncinclude_HEADERS = \
gnc-tree-model.h \
gnc-tree-model-account-types.h \
gnc-tree-model-account.h \
gnc-tree-model-account-drag.h \
gnc-tree-model-budget.h \
gnc-tree-model-commodity.h \
gnc-tree-model-price.h \

View File

@ -0,0 +1,732 @@
/* gnc-tree-model-account-drag.c
* Copyright (C) 2009 Matt Lavin <matt.lavin@gmail.com>
*
* 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.
*/
/** @addtogroup GUI
@{ */
/** @addtogroup GuiTreeModel GnuCash Tree Model
@{ */
/** @file gnc-tree-model-account-drag.c
@brief GtkTreeModel wrapper that supports dragging Accounts for reparenting
@author Matt Lavin <matt.lavin@gmail.com>
*/
#include "gnc-tree-model-account-drag.h"
#include "gnc-component-manager.h"
#include <glib/gi18n.h>
/** Static Globals *******************************************************/
static QofLogModule log_module = GNC_MOD_GUI;
/* general (object/interface init, etc) */
static void gnc_tree_model_account_drag_tree_model_init (GtkTreeModelIface *iface);
static void gnc_tree_model_account_drag_tree_sortable_init (GtkTreeSortableIface *iface);
static void gnc_tree_model_account_drag_drag_source_init (GtkTreeDragSourceIface*iface);
static void gnc_tree_model_account_drag_drag_dest_init (GtkTreeDragDestIface *iface);
static void gnc_tree_model_account_drag_finalize (GObject *object);
/* TreeModel signal handlers */
static void gnc_tree_model_account_drag_row_changed(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gpointer data);
static void gnc_tree_model_account_drag_row_inserted(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gpointer data);
static void gnc_tree_model_account_drag_row_has_child_toggled(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gpointer data);
static void gnc_tree_model_account_drag_row_deleted(GtkTreeModel *c_model,
GtkTreePath *c_path,
gpointer data);
static void gnc_tree_model_account_drag_rows_reordered(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gint *new_order,
gpointer data);
/* TreeModel interface */
static GtkTreeModelFlags gnc_tree_model_account_drag_get_flags (GtkTreeModel *tree_model);
static gint gnc_tree_model_account_drag_get_n_columns (GtkTreeModel *tree_model);
static GType gnc_tree_model_account_drag_get_column_type (GtkTreeModel *tree_model,
gint index);
static gboolean gnc_tree_model_account_drag_get_iter (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreePath *path);
static GtkTreePath *gnc_tree_model_account_drag_get_path (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static void gnc_tree_model_account_drag_get_value (GtkTreeModel *tree_model,
GtkTreeIter *iter,
gint column,
GValue *value);
static gboolean gnc_tree_model_account_drag_iter_next (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static gboolean gnc_tree_model_account_drag_iter_children (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent);
static gboolean gnc_tree_model_account_drag_iter_has_child (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static gint gnc_tree_model_account_drag_iter_n_children (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static gboolean gnc_tree_model_account_drag_iter_nth_child (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint n);
static gboolean gnc_tree_model_account_drag_iter_parent (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *child);
static void gnc_tree_model_account_drag_ref_node (GtkTreeModel *tree_model,
GtkTreeIter *iter);
static void gnc_tree_model_account_drag_unref_node (GtkTreeModel *tree_model,
GtkTreeIter *iter);
/* TreeDragSource interface */
static gboolean gnc_tree_model_account_drag_row_draggable (GtkTreeDragSource *drag_source,
GtkTreePath *path);
static gboolean gnc_tree_model_account_drag_drag_data_get (GtkTreeDragSource *drag_source,
GtkTreePath *path,
GtkSelectionData *selection_data);
static gboolean gnc_tree_model_account_drag_drag_data_delete (GtkTreeDragSource *drag_source,
GtkTreePath *path);
/* TreeDragDest interface */
static gboolean gnc_tree_model_account_drag_drag_data_received(GtkTreeDragDest *drag_dest,
GtkTreePath *dest,
GtkSelectionData *selection_data);
static gboolean gnc_tree_model_account_drag_row_drop_possible(GtkTreeDragDest *drag_dest,
GtkTreePath *dest_path,
GtkSelectionData *selection_data);
/* TreeSortable interface */
static gboolean gnc_tree_model_account_drag_get_sort_column_id (GtkTreeSortable *sortable,
gint *sort_column_id,
GtkSortType *order);
static void gnc_tree_model_account_drag_set_sort_column_id (GtkTreeSortable *sortable,
gint sort_column_id,
GtkSortType order);
static void gnc_tree_model_account_drag_set_sort_func (GtkTreeSortable *sortable,
gint sort_column_id,
GtkTreeIterCompareFunc func,
gpointer data,
GDestroyNotify destroy);
static void gnc_tree_model_account_drag_set_default_sort_func (GtkTreeSortable *sortable,
GtkTreeIterCompareFunc func,
gpointer data,
GDestroyNotify destroy);
static gboolean gnc_tree_model_account_drag_has_default_sort_func (GtkTreeSortable *sortable);
G_DEFINE_TYPE_WITH_CODE (GncTreeModelAccountDrag, gnc_tree_model_account_drag, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
gnc_tree_model_account_drag_tree_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE,
gnc_tree_model_account_drag_tree_sortable_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
gnc_tree_model_account_drag_drag_source_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_DEST,
gnc_tree_model_account_drag_drag_dest_init))
static void
gnc_tree_model_account_drag_init (GncTreeModelAccountDrag *tree_model_drag)
{
}
static void
gnc_tree_model_account_drag_class_init (GncTreeModelAccountDragClass *class)
{
GObjectClass *object_class;
object_class = (GObjectClass *) class;
object_class->finalize = gnc_tree_model_account_drag_finalize;
}
static void
gnc_tree_model_account_drag_tree_model_init (GtkTreeModelIface *iface)
{
iface->get_flags = gnc_tree_model_account_drag_get_flags;
iface->get_n_columns = gnc_tree_model_account_drag_get_n_columns;
iface->get_column_type = gnc_tree_model_account_drag_get_column_type;
iface->get_iter = gnc_tree_model_account_drag_get_iter;
iface->get_path = gnc_tree_model_account_drag_get_path;
iface->get_value = gnc_tree_model_account_drag_get_value;
iface->iter_next = gnc_tree_model_account_drag_iter_next;
iface->iter_children = gnc_tree_model_account_drag_iter_children;
iface->iter_has_child = gnc_tree_model_account_drag_iter_has_child;
iface->iter_n_children = gnc_tree_model_account_drag_iter_n_children;
iface->iter_nth_child = gnc_tree_model_account_drag_iter_nth_child;
iface->iter_parent = gnc_tree_model_account_drag_iter_parent;
iface->ref_node = gnc_tree_model_account_drag_ref_node;
iface->unref_node = gnc_tree_model_account_drag_unref_node;
}
static void
gnc_tree_model_account_drag_tree_sortable_init (GtkTreeSortableIface *iface)
{
iface->get_sort_column_id = gnc_tree_model_account_drag_get_sort_column_id;
iface->set_sort_column_id = gnc_tree_model_account_drag_set_sort_column_id;
iface->set_sort_func = gnc_tree_model_account_drag_set_sort_func;
iface->set_default_sort_func = gnc_tree_model_account_drag_set_default_sort_func;
iface->has_default_sort_func = gnc_tree_model_account_drag_has_default_sort_func;
}
static void
gnc_tree_model_account_drag_drag_source_init (GtkTreeDragSourceIface *iface)
{
iface->row_draggable = gnc_tree_model_account_drag_row_draggable;
iface->drag_data_delete = gnc_tree_model_account_drag_drag_data_delete;
iface->drag_data_get = gnc_tree_model_account_drag_drag_data_get;
}
static void
gnc_tree_model_account_drag_drag_dest_init (GtkTreeDragDestIface *iface)
{
iface->drag_data_received= gnc_tree_model_account_drag_drag_data_received;
iface->row_drop_possible = gnc_tree_model_account_drag_row_drop_possible;
}
/**
* gnc_tree_model_account_drag_new_with_model:
* @child_model: A #GtkTreeModel
*
* Creates a new #GtkTreeModel, with @child_model as the child model.
*
* Return value: A new #GtkTreeModel.
*/
GtkTreeModel *
gnc_tree_model_account_drag_new_with_model (GtkTreeModel *child_model,
GtkWidget* widget,
GncTreeModelAccountDragLookupFunc lookup_func,
gpointer user_data)
{
GtkTreeModel *retval;
GncTreeModelAccountDrag* model;
g_return_val_if_fail (GTK_IS_TREE_MODEL (child_model), NULL);
g_return_val_if_fail (lookup_func != NULL, NULL);
retval = g_object_new (gnc_tree_model_account_drag_get_type (), NULL);
model = GNC_TREE_MODEL_ACCOUNT_DRAG(retval);
g_object_ref(child_model);
model->child_model = child_model;
g_object_ref(widget);
model->widget = widget;
model->lookup_func = lookup_func;
model->user_data = user_data;
// Register signal handlers
model->changed_id = g_signal_connect (child_model, "row-changed",
G_CALLBACK (gnc_tree_model_account_drag_row_changed),
model);
model->inserted_id = g_signal_connect (child_model, "row-inserted",
G_CALLBACK (gnc_tree_model_account_drag_row_inserted),
model);
model->has_child_toggled_id
= g_signal_connect (child_model, "row-has-child-toggled",
G_CALLBACK (gnc_tree_model_account_drag_row_has_child_toggled),
model);
model->deleted_id = g_signal_connect (child_model, "row-deleted",
G_CALLBACK (gnc_tree_model_account_drag_row_deleted),
model);
model->reordered_id = g_signal_connect (child_model, "rows-reordered",
G_CALLBACK (gnc_tree_model_account_drag_rows_reordered),
model);
return retval;
}
GtkTreeModel *
gnc_tree_model_account_drag_get_model (GncTreeModelAccountDrag *model)
{
g_return_val_if_fail (model != NULL, NULL);
return model->child_model;
}
/* GObject callbacks */
static void
gnc_tree_model_account_drag_finalize (GObject *object)
{
GncTreeModelAccountDrag *tree_model = (GncTreeModelAccountDrag *) object;
// Disconnect signal handlers
g_signal_handler_disconnect (tree_model->child_model,
tree_model->changed_id);
g_signal_handler_disconnect (tree_model->child_model,
tree_model->inserted_id);
g_signal_handler_disconnect (tree_model->child_model,
tree_model->has_child_toggled_id);
g_signal_handler_disconnect (tree_model->child_model,
tree_model->deleted_id);
g_signal_handler_disconnect (tree_model->child_model,
tree_model->reordered_id);
g_object_unref(tree_model->child_model);
tree_model->child_model = NULL;
tree_model->lookup_func = NULL;
tree_model->user_data = NULL;
g_object_unref(tree_model->widget);
tree_model->widget = NULL;
/* must chain up */
G_OBJECT_CLASS (gnc_tree_model_account_drag_parent_class)->finalize (object);
}
/* TreeModel signal handlers */
static void
gnc_tree_model_account_drag_row_changed(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gpointer data)
{
g_return_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_DRAG(data));
gtk_tree_model_row_changed(GTK_TREE_MODEL(data), c_path, c_iter);
}
static void
gnc_tree_model_account_drag_row_inserted(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gpointer data)
{
g_return_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_DRAG(data));
gtk_tree_model_row_inserted(GTK_TREE_MODEL(data), c_path, c_iter);
}
static void
gnc_tree_model_account_drag_row_has_child_toggled(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gpointer data)
{
g_return_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_DRAG(data));
gtk_tree_model_row_has_child_toggled(GTK_TREE_MODEL(data), c_path, c_iter);
}
static void
gnc_tree_model_account_drag_row_deleted(GtkTreeModel *c_model,
GtkTreePath *c_path,
gpointer data)
{
g_return_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_DRAG(data));
gtk_tree_model_row_deleted(GTK_TREE_MODEL(data), c_path);
}
static void
gnc_tree_model_account_drag_rows_reordered(GtkTreeModel *c_model,
GtkTreePath *c_path,
GtkTreeIter *c_iter,
gint *new_order,
gpointer data)
{
g_return_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_DRAG(data));
gtk_tree_model_rows_reordered(GTK_TREE_MODEL(data), c_path, c_iter, new_order);
}
/* Fulfill our model requirements */
static GtkTreeModelFlags
gnc_tree_model_account_drag_get_flags (GtkTreeModel *tree_model)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, 0);
return gtk_tree_model_get_flags (tree_model_drag->child_model);
}
static gint
gnc_tree_model_account_drag_get_n_columns (GtkTreeModel *tree_model)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, 0);
return gtk_tree_model_get_n_columns (tree_model_drag->child_model);
}
static GType
gnc_tree_model_account_drag_get_column_type (GtkTreeModel *tree_model,
gint index)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, G_TYPE_INVALID);
return gtk_tree_model_get_column_type (tree_model_drag->child_model, index);
}
static gboolean
gnc_tree_model_account_drag_get_iter (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreePath *path)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_model_get_iter (tree_model_drag->child_model, iter, path);
}
static GtkTreePath *
gnc_tree_model_account_drag_get_path (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, NULL);
return gtk_tree_model_get_path (tree_model_drag->child_model, iter);
}
static void
gnc_tree_model_account_drag_get_value (GtkTreeModel *tree_model,
GtkTreeIter *iter,
gint column,
GValue *value)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_if_fail (tree_model_drag->child_model != NULL);
gtk_tree_model_get_value (tree_model_drag->child_model,
iter, column, value);
}
static gboolean
gnc_tree_model_account_drag_iter_next (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_model_iter_next (tree_model_drag->child_model, iter);
}
static gboolean
gnc_tree_model_account_drag_iter_children (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_model_iter_children (tree_model_drag->child_model, iter, parent);
}
static gboolean
gnc_tree_model_account_drag_iter_has_child (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_model_iter_has_child (tree_model_drag->child_model, iter);
}
static gint
gnc_tree_model_account_drag_iter_n_children (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, 0);
return gtk_tree_model_iter_n_children (tree_model_drag->child_model, iter);
}
static gboolean
gnc_tree_model_account_drag_iter_nth_child (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint n)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_model_iter_nth_child(tree_model_drag->child_model, iter, parent, n);
}
static gboolean
gnc_tree_model_account_drag_iter_parent (GtkTreeModel *tree_model,
GtkTreeIter *iter,
GtkTreeIter *child)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_model_iter_parent (tree_model_drag->child_model, iter, child);
}
static void
gnc_tree_model_account_drag_ref_node (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_if_fail (tree_model_drag->child_model != NULL);
gtk_tree_model_ref_node (tree_model_drag->child_model, iter);
}
static void
gnc_tree_model_account_drag_unref_node (GtkTreeModel *tree_model,
GtkTreeIter *iter)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) tree_model;
g_return_if_fail (tree_model_drag->child_model != NULL);
gtk_tree_model_unref_node (tree_model_drag->child_model, iter);
}
/* Sortable interface */
static gboolean
gnc_tree_model_account_drag_get_sort_column_id (GtkTreeSortable *sortable,
gint *sort_column_id,
GtkSortType *order)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) sortable;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(tree_model_drag->child_model), sort_column_id, order);
}
static void
gnc_tree_model_account_drag_set_sort_column_id (GtkTreeSortable *sortable,
gint sort_column_id,
GtkSortType order)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) sortable;
g_return_if_fail (tree_model_drag->child_model != NULL);
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(tree_model_drag->child_model), sort_column_id, order);
}
static void
gnc_tree_model_account_drag_set_sort_func (GtkTreeSortable *sortable,
gint sort_column_id,
GtkTreeIterCompareFunc func,
gpointer data,
GDestroyNotify destroy)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) sortable;
g_return_if_fail (tree_model_drag->child_model != NULL);
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(tree_model_drag->child_model), sort_column_id, func, data, destroy);
}
static void
gnc_tree_model_account_drag_set_default_sort_func (GtkTreeSortable *sortable,
GtkTreeIterCompareFunc func,
gpointer data,
GDestroyNotify destroy)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) sortable;
g_return_if_fail (tree_model_drag->child_model != NULL);
gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(tree_model_drag->child_model), func, data, destroy);
}
static gboolean
gnc_tree_model_account_drag_has_default_sort_func (GtkTreeSortable *sortable)
{
GncTreeModelAccountDrag *tree_model_drag = (GncTreeModelAccountDrag *) sortable;
g_return_val_if_fail (tree_model_drag->child_model != NULL, FALSE);
return gtk_tree_sortable_has_default_sort_func(GTK_TREE_SORTABLE(tree_model_drag->child_model));
}
/** GtkTreeDragSource implementation methods ****************************/
static gboolean
gnc_tree_model_account_drag_row_draggable(GtkTreeDragSource *drag_source,
GtkTreePath *path)
{
DEBUG("Inside gnc_tree_model_account_drag_source_row_draggable\n");
return TRUE;
}
static gboolean
gnc_tree_model_account_drag_drag_data_get(GtkTreeDragSource *drag_source,
GtkTreePath *path,
GtkSelectionData *selection_data)
{
Account *account;
gchar *full_account_name;
GncTreeModelAccountDrag* account_drag;
g_return_val_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_DRAG(drag_source), FALSE);
account_drag = GNC_TREE_MODEL_ACCOUNT_DRAG(drag_source);
account = account_drag->lookup_func(path, account_drag->user_data);
if (account == NULL)
return FALSE;
full_account_name = gnc_account_get_full_name(account);
gtk_selection_data_set(selection_data, selection_data->target, 8, (const guchar *)full_account_name, strlen(full_account_name));
DEBUG("Inside gnc_tree_model_account_drag_source_drag_data_get. Target type \"%s\" desired. Account name \"%s\" was selected.\n", gdk_atom_name(selection_data->target), full_account_name);
g_free(full_account_name);
return TRUE;
}
static gboolean
gnc_tree_model_account_drag_drag_data_delete(GtkTreeDragSource *drag_source,
GtkTreePath *path)
{
DEBUG("Inside gnc_tree_model_account_drag_source_drag_data_delete\n");
return TRUE;
}
/** GtkTreeDragDest implementation methods ****************************/
static gboolean
gnc_tree_model_account_drag_account_is_child_of(Account *child, Account* possible_parent)
{
Account* parent = child;
while (parent != NULL)
{
if (parent == possible_parent)
{
return TRUE;
}
parent = gnc_account_get_parent(parent);
}
return FALSE;
}
static gboolean
gnc_tree_model_account_drag_drag_data_received(GtkTreeDragDest *drag_dest,
GtkTreePath *dest,
GtkSelectionData *selection_data)
{
GtkTreePath *parent_path;
Account *new_parent, *dragged_account;
gchar *parent_full_account_name, *dragged_full_account_name;
GncTreeModelAccountDrag* account_drag;
DEBUG("Inside gnc_tree_model_account_drag_drag_data_received.\n");
g_return_val_if_fail(GNC_IS_TREE_MODEL_ACCOUNT_DRAG(drag_dest), FALSE);
account_drag = GNC_TREE_MODEL_ACCOUNT_DRAG(drag_dest);
// The dest path is spec's as the element to insert before,
// but the parent is what's really desired, so move up
// the path to find the parent.
parent_path = gtk_tree_path_copy(dest);
g_return_val_if_fail(parent_path != NULL, FALSE);
gtk_tree_path_up(parent_path);
new_parent = account_drag->lookup_func(parent_path, account_drag->user_data);
gtk_tree_path_free(parent_path);
g_return_val_if_fail(new_parent != NULL, FALSE);
parent_full_account_name = gnc_account_get_full_name(new_parent);
DEBUG("Account name \"%s\" was drop target.\n", parent_full_account_name);
g_free(parent_full_account_name);
g_return_val_if_fail(g_utf8_validate((gchar*)selection_data->data, selection_data->length, NULL), FALSE);
dragged_full_account_name = g_strndup((gchar*)selection_data->data, selection_data->length);
DEBUG("Account name \"%s\" was dragged account.\n", dragged_full_account_name);
dragged_account = gnc_account_lookup_by_full_name(new_parent, dragged_full_account_name);
g_free(dragged_full_account_name);
g_return_val_if_fail(dragged_account != NULL, FALSE);
// Make sure that the new hierarchy would still be valid. Accounts cannot be children
// of themselves
if (gnc_tree_model_account_drag_account_is_child_of(new_parent, dragged_account)) {
GtkWindow *window;
GtkWidget *dialog;
window = account_drag->widget == NULL ? NULL : GTK_WINDOW (gtk_widget_get_toplevel (account_drag->widget));
dialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
_("You cannot move an account into itself."));
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
_("The destination account is a child of the dragged account."));
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
return FALSE;
}
// Make sure the parent account has a valid type for the new child
if (xaccAccountTypesCompatible(xaccAccountGetType(new_parent), xaccAccountGetType(dragged_account)) == FALSE) {
GtkWindow *window;
GtkWidget *dialog;
window = account_drag->widget == NULL ? NULL : GTK_WINDOW (gtk_widget_get_toplevel (account_drag->widget));
dialog = gtk_message_dialog_new (window,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
_("The account cannot be moved because the account types are incompatible."));
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG(dialog),
_("The destination account type, %s, is not compatible with the new parent's type, %s."),
xaccAccountGetTypeStr(xaccAccountGetType(dragged_account)),
xaccAccountGetTypeStr(xaccAccountGetType(new_parent)));
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
return FALSE;
}
gnc_suspend_gui_refresh ();
gnc_account_append_child (new_parent, dragged_account);
gnc_resume_gui_refresh ();
return TRUE;
}
static gboolean
gnc_tree_model_account_drag_row_drop_possible(GtkTreeDragDest *drag_dest,
GtkTreePath *dest,
GtkSelectionData *selection_data)
{
DEBUG("Inside gnc_tree_model_account_drag_dest_row_drop_possible.");
return TRUE;
}

View File

@ -0,0 +1,78 @@
/* gnc-tree-model-account-drag.h
* Copyright (C) 2009 Matt Lavin <matt.lavin@gmail.com>
*
* 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.
*/
/** @addtogroup GUI
@{ */
/** @addtogroup GuiTreeModel GnuCash Tree Model
@{ */
/** @file gnc-tree-model-account-drag.h
@brief GtkTreeModel wrapper that supports dragging Accounts for reparenting
@author Matt Lavin <matt.lavin@gmail.com>
*/
#ifndef __GNC_TREE_MODEL_ACCOUNT_DRAG_H
#define __GNC_TREE_MODEL_ACCOUNT_DRAG_H
#include <gtk/gtk.h>
#include <gtk/gtktreemodel.h>
#include "config.h"
#include "Account.h"
#include "gnc-tree-view-account.h"
G_BEGIN_DECLS
#define GNC_TYPE_TREE_MODEL_ACCOUNT_DRAG (gnc_tree_model_account_drag_get_type ())
#define GNC_TREE_MODEL_ACCOUNT_DRAG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNC_TYPE_TREE_MODEL_ACCOUNT_DRAG, GncTreeModelAccountDrag))
#define GNC_TREE_MODEL_ACCOUNT_DRAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNC_TYPE_TREE_MODEL_ACCOUNT_DRAG, GncTreeModelAccountDragClass))
#define GNC_IS_TREE_MODEL_ACCOUNT_DRAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNC_TYPE_TREE_MODEL_ACCOUNT_DRAG))
#define GNC_IS_TREE_MODEL_ACCOUNT_DRAG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNC_TYPE_TREE_MODEL_ACCOUNT_DRAG))
#define GNC_TREE_MODEL_ACCOUNT_DRAG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_TREE_MODEL_ACCOUNT_DRAG, GncTreeModelAccountDragClass))
typedef Account* (*GncTreeModelAccountDragLookupFunc) (GtkTreePath *path,
gpointer user_data);
typedef struct {
GObject parent;
/* < private > */
GtkTreeModel *child_model;
GtkWidget* widget;
GncTreeModelAccountDragLookupFunc lookup_func;
gpointer user_data;
gulong changed_id;
gulong inserted_id;
gulong has_child_toggled_id;
gulong deleted_id;
gulong reordered_id;
} GncTreeModelAccountDrag;
typedef struct {
GObjectClass parent_class;
} GncTreeModelAccountDragClass;
GType gnc_tree_model_account_drag_get_type (void) G_GNUC_CONST;
GtkTreeModel *gnc_tree_model_account_drag_new_with_model (GtkTreeModel *child_model, GtkWidget* widget, GncTreeModelAccountDragLookupFunc lookup_func, gpointer user_data);
GtkTreeModel *gnc_tree_model_account_drag_get_model (GncTreeModelAccountDrag *model);
G_END_DECLS
#endif /* __GNC_TREE_MODEL_ACCOUNT_DRAG_H */

View File

@ -30,6 +30,7 @@
#include "gnc-tree-view.h"
#include "gnc-tree-model-account.h"
#include "gnc-tree-model-account-drag.h"
#include "gnc-tree-model-account-types.h"
#include "gnc-tree-view-account.h"
@ -62,10 +63,16 @@ static void gnc_tree_view_account_finalize (GObject *object);
static void gtva_update_column_names (GncTreeView *view);
static void gtva_currency_changed_cb (void);
static Account* gnc_tree_view_account_loookup_account (GtkTreePath *path,
gpointer data);
static gboolean gnc_tree_view_account_filter_helper (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
static GtkTreeModel*
gnc_tree_view_account_get_sort_model (GncTreeViewAccount *view);
static void gtva_setup_column_renderer_edited_cb(GncTreeViewAccount *account_view,
GtkTreeViewColumn *column,
GtkCellRenderer *renderer,
@ -469,7 +476,7 @@ GtkTreeView *
gnc_tree_view_account_new_with_root (Account *root, gboolean show_root)
{
GncTreeView *view;
GtkTreeModel *model, *f_model, *s_model;
GtkTreeModel *model, *f_model, *s_model, *d_model;
GtkTreePath *virtual_root_path = NULL;
const gchar *sample_type, *sample_commodity;
GncTreeViewAccountPrivate *priv;
@ -497,9 +504,13 @@ gnc_tree_view_account_new_with_root (Account *root, gboolean show_root)
/* Set up the view private sort layer on the common model. */
s_model = gtk_tree_model_sort_new_with_model(f_model);
g_object_unref(G_OBJECT(f_model));
gnc_tree_view_set_model (view, s_model);
d_model = gnc_tree_model_account_drag_new_with_model(s_model, GTK_WIDGET(view), gnc_tree_view_account_loookup_account, view);
g_object_unref(G_OBJECT(s_model));
gnc_tree_view_set_model (view, d_model);
g_object_unref(G_OBJECT(d_model));
/* Set default visibilities */
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(view), FALSE);
@ -700,6 +711,28 @@ gnc_tree_view_account_new (gboolean show_root)
g_free(path_string); \
}
static Account*
gnc_tree_view_account_loookup_account (GtkTreePath *path,
gpointer data)
{
g_return_val_if_fail(GNC_IS_TREE_VIEW_ACCOUNT(data), NULL);
return gnc_tree_view_account_get_account_from_path(GNC_TREE_VIEW_ACCOUNT(data), path);
}
static GtkTreeModel*
gnc_tree_view_account_get_sort_model (GncTreeViewAccount *view)
{
GtkTreeModel *d_model, *s_model;
d_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_model_account_drag_get_model(GNC_TREE_MODEL_ACCOUNT_DRAG(d_model));
return s_model;
}
static GtkTreePath *
gnc_tree_view_account_get_path_from_account (GncTreeViewAccount *view,
Account *account)
@ -715,7 +748,7 @@ gnc_tree_view_account_get_path_from_account (GncTreeViewAccount *view,
}
/* Reach down to the real model and get a path for this account */
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
path = gnc_tree_model_account_get_path_from_account (GNC_TREE_MODEL_ACCOUNT(model), account);
@ -754,7 +787,7 @@ gnc_tree_view_account_get_iter_from_account (GncTreeViewAccount *view,
ENTER("view %p, account %p (%s)", view, account, xaccAccountGetName(account));
/* Reach down to the real model and get an iter for this account */
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
if (!gnc_tree_model_account_get_iter_from_account (
@ -793,7 +826,7 @@ gnc_tree_view_account_count_children (GncTreeViewAccount *view,
}
/* Any children? */
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
num_children = gtk_tree_model_iter_n_children(s_model, &s_iter);
LEAVE("%d children", num_children);
return num_children;
@ -925,7 +958,7 @@ gnc_tree_view_account_refilter (GncTreeViewAccount *view)
g_return_if_fail(GNC_IS_TREE_VIEW_ACCOUNT(view));
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (f_model));
}
@ -979,7 +1012,7 @@ gnc_tree_view_account_get_account_from_path (GncTreeViewAccount *view,
g_return_val_if_fail (GNC_IS_TREE_VIEW_ACCOUNT (view), NULL);
g_return_val_if_fail (s_path != NULL, NULL);
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
f_path = gtk_tree_model_sort_convert_path_to_child_path (
GTK_TREE_MODEL_SORT (s_model), s_path);
if (!f_path) {
@ -1010,17 +1043,19 @@ gnc_tree_view_account_get_account_from_path (GncTreeViewAccount *view,
Account *
gnc_tree_view_account_get_account_from_iter (GtkTreeModel *s_model,
gnc_tree_view_account_get_account_from_iter (GtkTreeModel *d_model,
GtkTreeIter *s_iter)
{
GtkTreeModel *model, *f_model;
GtkTreeModel *model, *f_model, *s_model;
GtkTreeIter iter, f_iter;
Account *account;
g_return_val_if_fail (GTK_IS_TREE_MODEL_SORT(s_model), NULL);
g_return_val_if_fail (GNC_IS_TREE_MODEL_ACCOUNT_DRAG(d_model), NULL);
g_return_val_if_fail (s_iter != NULL, NULL);
ENTER("model %p, iter %p", s_model, s_iter);
ENTER("model %p, iter %p", d_model, s_iter);
s_model = gnc_tree_model_account_drag_get_model(GNC_TREE_MODEL_ACCOUNT_DRAG (d_model));
gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT(s_model),
&f_iter,
@ -1044,7 +1079,7 @@ Account *
gnc_tree_view_account_get_selected_account (GncTreeViewAccount *view)
{
GtkTreeSelection *selection;
GtkTreeModel *f_model, *s_model;
GtkTreeModel *f_model, *s_model, *d_model;
GtkTreeIter iter, f_iter, s_iter;
Account *account;
GtkSelectionMode mode;
@ -1057,11 +1092,13 @@ gnc_tree_view_account_get_selected_account (GncTreeViewAccount *view)
if ((mode != GTK_SELECTION_SINGLE) && (mode != GTK_SELECTION_BROWSE)) {
return NULL;
}
if (!gtk_tree_selection_get_selected (selection, &s_model, &s_iter)) {
if (!gtk_tree_selection_get_selected (selection, &d_model, &s_iter)) {
LEAVE("no account, get_selected failed");
return FALSE;
}
s_model = gnc_tree_model_account_drag_get_model(GNC_TREE_MODEL_ACCOUNT_DRAG (d_model));
gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (s_model),
&f_iter, &s_iter);
@ -1097,7 +1134,7 @@ gnc_tree_view_account_set_selected_account (GncTreeViewAccount *view,
if (account == NULL)
return;
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
@ -1223,7 +1260,7 @@ gnc_tree_view_account_set_selected_accounts (GncTreeViewAccount *view,
g_return_if_fail (GNC_IS_TREE_VIEW_ACCOUNT (view));
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
@ -1304,7 +1341,7 @@ gnc_tree_view_account_select_subaccounts (GncTreeViewAccount *view,
}
/* Any children? */
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
num_children = gtk_tree_model_iter_n_children(s_model, &si_account);
if (num_children == 0) {
LEAVE("no children");
@ -1377,7 +1414,7 @@ gnc_tree_view_account_get_cursor_account (GncTreeViewAccount *view)
ENTER("view %p", view);
g_return_val_if_fail (GNC_IS_TREE_VIEW_ACCOUNT (view), NULL);
s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
s_model = gnc_tree_view_account_get_sort_model(view);
gtk_tree_view_get_cursor (GTK_TREE_VIEW(view), &s_path, NULL);
if (!s_path) {
LEAVE("no account");
@ -1457,15 +1494,14 @@ gtva_currency_changed_cb (void)
static void
account_cell_kvp_data_func (GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell,
GtkTreeModel *s_model,
GtkTreeModel *model,
GtkTreeIter *s_iter,
gpointer key)
{
Account *account;
kvp_frame * frame;
g_return_if_fail (GTK_IS_TREE_MODEL_SORT (s_model));
account = gnc_tree_view_account_get_account_from_iter(s_model, s_iter);
account = gnc_tree_view_account_get_account_from_iter(model, s_iter);
frame = xaccAccountGetSlots(account);
g_object_set (G_OBJECT (cell),
@ -1503,10 +1539,10 @@ gnc_tree_view_account_add_kvp_column (GncTreeViewAccount *view,
}
static void col_edited_helper(GtkCellRendererText *cell, gchar *path_string,
gchar *new_text, gpointer _s_model)
gchar *new_text, gpointer _model)
{
Account *account;
GtkTreeModel *s_model;
GtkTreeModel *model;
GtkTreeIter s_iter;
GncTreeViewAccountColumnTextEdited col_edited_cb;
GtkTreeViewColumn *col;
@ -1515,26 +1551,25 @@ static void col_edited_helper(GtkCellRendererText *cell, gchar *path_string,
"column_edited_callback");
col = GTK_TREE_VIEW_COLUMN(g_object_get_data(G_OBJECT(cell),
"column_view"));
s_model = GTK_TREE_MODEL(_s_model);
model = GTK_TREE_MODEL(_model);
if (!gtk_tree_model_get_iter_from_string(s_model, &s_iter, path_string))
if (!gtk_tree_model_get_iter_from_string(model, &s_iter, path_string))
return;
account = gnc_tree_view_account_get_account_from_iter(s_model, &s_iter);
account = gnc_tree_view_account_get_account_from_iter(model, &s_iter);
col_edited_cb(account, col, new_text);
}
static void col_source_helper(GtkTreeViewColumn *col, GtkCellRenderer *cell,
GtkTreeModel *s_model, GtkTreeIter *s_iter,
GtkTreeModel *model, GtkTreeIter *iter,
gpointer _col_source_cb)
{
Account *account;
gchar *text;
GncTreeViewAccountColumnSource col_source_cb;
g_return_if_fail (GTK_IS_TREE_MODEL_SORT (s_model));
col_source_cb = (GncTreeViewAccountColumnSource) _col_source_cb;
account = gnc_tree_view_account_get_account_from_iter(s_model, s_iter);
account = gnc_tree_view_account_get_account_from_iter(model, iter);
text = col_source_cb(account, col, cell);
g_object_set (G_OBJECT (cell), "text", text, "xalign", 1.0, NULL);
g_free(text);
@ -1556,7 +1591,7 @@ gtva_setup_column_renderer_edited_cb(GncTreeViewAccount *account_view,
{
g_object_set(G_OBJECT(renderer), "editable", FALSE, NULL);
g_object_set_data(G_OBJECT(renderer), "column_edited_callback", col_edited_cb);
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(account_view));
s_model = gnc_tree_view_account_get_sort_model(account_view);
g_signal_handlers_disconnect_by_func(G_OBJECT(renderer), col_edited_cb, s_model);
g_object_set_data(G_OBJECT(renderer), "column_view", column);
}
@ -1565,7 +1600,7 @@ gtva_setup_column_renderer_edited_cb(GncTreeViewAccount *account_view,
g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);
g_object_set_data(G_OBJECT(renderer), "column_edited_callback",
col_edited_cb);
s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(account_view));
s_model = gnc_tree_view_account_get_sort_model(account_view);
g_signal_connect(G_OBJECT(renderer), "edited",
(GCallback) col_edited_helper, s_model);
g_object_set_data(G_OBJECT(renderer), "column_view", column);

View File

@ -407,6 +407,25 @@ gnc_plugin_page_account_tree_close_cb (gpointer user_data)
gnc_main_window_close_page(plugin_page);
}
static const GtkTargetEntry dnd_targets[] = {
{ "gnc_account", GTK_TARGET_SAME_WIDGET, 0 }
};
static void
gnc_enable_account_dragging(GtkTreeView *tree_view)
{
GtkTreeModel *model;
model = gtk_tree_view_get_model (tree_view);
g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (model, GTK_TYPE_TREE_DRAG_DEST));
g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (model, GTK_TYPE_TREE_DRAG_SOURCE));
gtk_tree_view_enable_model_drag_source(tree_view, GDK_BUTTON1_MASK, dnd_targets, 1, GDK_ACTION_MOVE);
gtk_tree_view_enable_model_drag_dest(tree_view, dnd_targets, 1, GDK_ACTION_MOVE);
}
static GtkWidget *
gnc_plugin_page_account_tree_create_widget (GncPluginPage *plugin_page)
{
@ -466,6 +485,9 @@ gnc_plugin_page_account_tree_create_widget (GncPluginPage *plugin_page)
g_signal_connect (G_OBJECT (tree_view), "row-activated",
G_CALLBACK (gnc_plugin_page_account_tree_double_click_cb), page);
/* Support dragging accounts for re-ordering/re-parenting */
gnc_enable_account_dragging(tree_view);
gtk_tree_view_set_headers_visible(tree_view, TRUE);
gnc_plugin_page_account_tree_selection_changed_cb (NULL, page);
gtk_widget_show (GTK_WIDGET (tree_view));