[Gnucash-changes] r14249 -
gnucash/branches/register-rewrite/src/gnome-utils - Add a
treeview and treemodel for transactions.
Chris Shoemaker
chris at cvs.gnucash.org
Mon May 29 17:18:26 EDT 2006
Author: chris
Date: 2006-05-29 17:18:25 -0400 (Mon, 29 May 2006)
New Revision: 14249
Trac: http://svn.gnucash.org/trac/changeset/14249
Added:
gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.c
gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.h
gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.c
gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.h
Modified:
gnucash/branches/register-rewrite/src/gnome-utils/Makefile.am
Log:
Add a treeview and treemodel for transactions.
Modified: gnucash/branches/register-rewrite/src/gnome-utils/Makefile.am
===================================================================
--- gnucash/branches/register-rewrite/src/gnome-utils/Makefile.am 2006-05-29 20:59:33 UTC (rev 14248)
+++ gnucash/branches/register-rewrite/src/gnome-utils/Makefile.am 2006-05-29 21:18:25 UTC (rev 14249)
@@ -88,9 +88,11 @@
gnc-tree-model-budget.c \
gnc-tree-model-commodity.c \
gnc-tree-model-price.c \
+ gnc-tree-model-transaction.c \
gnc-tree-view-account.c \
gnc-tree-view-commodity.c \
gnc-tree-view-price.c \
+ gnc-tree-view-transaction.c \
gnc-tree-view.c \
gnc-window.c \
gncmod-gnome-utils.c \
@@ -154,9 +156,11 @@
gnc-tree-model-budget.h \
gnc-tree-model-commodity.h \
gnc-tree-model-price.h \
+ gnc-tree-model-transaction.h \
gnc-tree-view-account.h \
gnc-tree-view-commodity.h \
gnc-tree-view-price.h \
+ gnc-tree-view-transaction.h \
gnc-tree-view.h \
gnc-window.h \
misc-gnome-utils.h \
Added: gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.c
===================================================================
--- gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.c 2006-05-29 20:59:33 UTC (rev 14248)
+++ gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.c 2006-05-29 21:18:25 UTC (rev 14249)
@@ -0,0 +1,1381 @@
+/*
+ TODO: remove bsplit_node, bsplit_parent_node: would that really be simpler?
+
+ - The SEP trans stuff works ok, but it complicates view iter
+ navigation, so I don't use it. It could probably be removed.
+
+*/
+
+/********************************************************************\
+ * gnc-tree-model-transaction.c -- GtkTreeModel implementation to *
+ * display Transactions in a GtkTreeView. *
+ * Copyright (C) 2006 Chris Shoemaker <c.shoemaker at cox.net> *
+ * *
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "gnc-tree-model-transaction.h"
+#include "gnc-component-manager.h"
+#include "Transaction.h"
+#include "TransactionP.h" //what a shame
+#include "Scrub.h"
+#include "gnc-commodity.h"
+//#include "gnc-engine-util.h"
+#include "gnc-gobject-utils.h"
+#include "gnc-ui-util.h"
+#include "gnc-event.h"
+
+/** Static Globals *******************************************************/
+static QofLogModule log_module = GNC_MOD_GUI;
+
+struct GncTreeModelTransactionPrivate
+{
+ QofBook *book;
+ Query *query;
+ Account *anchor;
+ gboolean include_subacc;
+
+ GList *tlist;
+
+ Transaction *btrans;
+ Split *bsplit;
+ GList *bsplit_node; /* never added to any list, just for
+ representation of the iter */
+ GList *bsplit_parent_node;
+
+ Transaction *sep_trans;
+ gint event_handler_id;
+};
+
+#define TREE_MODEL_TRANSACTION_CM_CLASS "tree-model-transactions"
+#define SEP 0x4
+#define BLANK 0x2
+#define SPLIT 0x1
+#define IS_SEP(x) (GPOINTER_TO_INT((x)->user_data) & SEP)
+#define IS_BLANK(x) (GPOINTER_TO_INT((x)->user_data) & BLANK)
+#define IS_SPLIT(x) (GPOINTER_TO_INT((x)->user_data) & SPLIT)
+#define IS_BLANK_SPLIT(x) (IS_BLANK(x) && IS_SPLIT(x))
+#define IS_BLANK_TRANS(x) (IS_BLANK(x) && !IS_SPLIT(x))
+
+/* Meaning of user_data fields in iter struct:
+ *
+ * user_data: a bitfield for SEP, BLANK, SPLIT
+ * user_data2: a pointer to a node in a GList of Transactions
+ * if this is a Split, then this points to the GList node of the
+ * parent transaction
+ * user_data3: a pointer to a node in a GList of Splits
+ * NULL if this is a transaction.
+ */
+#define VALID_ITER(model, iter) \
+ (GNC_IS_TREE_MODEL_TRANSACTION(model) && \
+ ((iter) && (iter)->user_data2) && \
+ ((iter)->stamp == (model)->stamp) && \
+ (!IS_SPLIT(iter) ^ ((iter)->user_data3 != NULL)) && \
+ (!IS_BLANK_SPLIT(iter) || \
+ ((iter)->user_data2 == (model)->priv->bsplit_parent_node)) \
+ )
+
+static GtkTreeIter
+make_iter(GncTreeModelTransaction *model, gint f, GList *tnode, GList *snode)
+{
+ GtkTreeIter iter, *iter_p;
+ iter_p = &iter;
+ iter.stamp = model->stamp;
+ iter.user_data = GINT_TO_POINTER(f);
+ iter.user_data2 = tnode;
+ iter.user_data3 = snode;
+ if (!VALID_ITER(model, &iter)) PERR("Making invalid iter");
+ return iter;
+}
+
+/** Declarations *********************************************************/
+static void gnc_tree_model_transaction_class_init (
+ GncTreeModelTransactionClass *klass);
+static void gnc_tree_model_transaction_init (GncTreeModelTransaction *model);
+static void gnc_tree_model_transaction_finalize (GObject *object);
+
+/** Implementation of GtkTreeModel **************************************/
+static void gnc_tree_model_transaction_tree_model_init (
+ GtkTreeModelIface *iface);
+static guint gnc_tree_model_transaction_get_flags (GtkTreeModel *model);
+static int gnc_tree_model_transaction_get_n_columns (GtkTreeModel *model);
+static GType gnc_tree_model_transaction_get_column_type (
+ GtkTreeModel *model, int index);
+static gboolean gnc_tree_model_transaction_get_iter (
+ GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path);
+static GtkTreePath *gnc_tree_model_transaction_get_path (
+ GtkTreeModel *model, GtkTreeIter *iter);
+static void gnc_tree_model_transaction_get_value (
+ GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value);
+static gboolean gnc_tree_model_transaction_iter_next (
+ GtkTreeModel *model, GtkTreeIter *iter);
+static gboolean gnc_tree_model_transaction_iter_children (
+ GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent);
+static gboolean gnc_tree_model_transaction_iter_has_child (
+ GtkTreeModel *model, GtkTreeIter *iter);
+static int gnc_tree_model_transaction_iter_n_children (
+ GtkTreeModel *model, GtkTreeIter *iter);
+static gboolean gnc_tree_model_transaction_iter_nth_child (
+ GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent, int n);
+static gboolean gnc_tree_model_transaction_iter_parent (
+ GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child);
+
+/** Helper Functions ****************************************************/
+
+static void gnc_tree_model_transaction_event_handler(
+ QofEntity *entity, QofEventId event_type, gpointer tm, gpointer event_data);
+
+/************************************************************/
+/* g_object required functions */
+/************************************************************/
+
+static GtkObjectClass *parent_class = NULL;
+
+GType
+gnc_tree_model_transaction_get_type (void)
+{
+ static GType gnc_tree_model_transaction_type = 0;
+
+ if (gnc_tree_model_transaction_type == 0) {
+ static const GTypeInfo our_info = {
+ sizeof (GncTreeModelTransactionClass), /* class_size */
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) gnc_tree_model_transaction_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GncTreeModelTransaction), /* */
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gnc_tree_model_transaction_init
+ };
+
+ static const GInterfaceInfo tree_model_info = {
+ (GInterfaceInitFunc) gnc_tree_model_transaction_tree_model_init,
+ NULL,
+ NULL
+ };
+
+ gnc_tree_model_transaction_type = g_type_register_static (
+ GNC_TYPE_TREE_MODEL, GNC_TREE_MODEL_TRANSACTION_NAME,
+ &our_info, 0);
+
+ g_type_add_interface_static (gnc_tree_model_transaction_type,
+ GTK_TYPE_TREE_MODEL, &tree_model_info);
+ }
+
+ return gnc_tree_model_transaction_type;
+}
+
+static void
+gnc_tree_model_transaction_class_init (GncTreeModelTransactionClass *klass)
+{
+ GObjectClass *o_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ /* GObject signals */
+ o_class = G_OBJECT_CLASS (klass);
+ o_class->finalize = gnc_tree_model_transaction_finalize;
+}
+
+static void
+gnc_tree_model_transaction_init (GncTreeModelTransaction *model)
+{
+ ENTER("model %p", model);
+ while (model->stamp == 0) {
+ model->stamp = g_random_int ();
+ }
+
+ model->priv = g_new0 (GncTreeModelTransactionPrivate, 1);
+ LEAVE(" ");
+}
+
+static void
+gnc_tree_model_transaction_finalize (GObject *object)
+{
+ GncTreeModelTransaction *model;
+ GncTreeModelTransactionPrivate *priv;
+
+ ENTER("model %p", object);
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNC_IS_TREE_MODEL_TRANSACTION (object));
+
+ model = GNC_TREE_MODEL_TRANSACTION (object);
+ priv = model->priv;
+
+ if (priv->event_handler_id) {
+ qof_event_unregister_handler(priv->event_handler_id);
+ priv->event_handler_id = 0;
+ }
+
+ priv->book = NULL;
+ g_list_free(priv->tlist);
+ priv->tlist = NULL;
+ xaccFreeQuery(priv->query);
+ priv->query = NULL;
+ if (priv->bsplit && !xaccSplitGetParent(priv->bsplit))
+ ; //LEAK? xaccFreeSplit(priv->bsplit);
+ priv->bsplit = NULL;
+ priv->bsplit_node = NULL;
+ priv->bsplit_parent_node = NULL;
+
+ //LEAK?: xaccFreeTransaction(priv->btrans);
+ priv->btrans = NULL;
+
+ g_free(priv);
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ G_OBJECT_CLASS (parent_class)->finalize(object);
+ LEAVE(" ");
+}
+
+/************************************************************/
+/* New Model Creation */
+/************************************************************/
+static GncTreeModelTransaction *
+gnc_tree_model_transaction_new(GList *tlist)
+{
+ GncTreeModelTransaction *model;
+ GncTreeModelTransactionPrivate *priv;
+
+ ENTER("");
+
+ model = g_object_new(GNC_TYPE_TREE_MODEL_TRANSACTION, NULL);
+
+ priv = model->priv;
+ priv->book = gnc_get_current_book();
+ priv->tlist = tlist;
+
+ priv->bsplit = xaccMallocSplit(priv->book);
+ priv->bsplit_node = g_list_append(NULL, priv->bsplit);
+ priv->btrans = xaccMallocTransaction(priv->book);
+ priv->tlist = g_list_append(priv->tlist, priv->btrans);
+
+ if (0) {
+ priv->sep_trans = xaccMallocTransaction(priv->book);
+ xaccTransSetDatePostedSecs(priv->sep_trans, time(NULL));
+ priv->tlist = g_list_append(priv->tlist, priv->sep_trans);
+ }
+ priv->event_handler_id = qof_event_register_handler(
+ gnc_tree_model_transaction_event_handler, model);
+ LEAVE("model %p", model);
+ return model;
+}
+
+
+GncTreeModelTransaction *
+gnc_tree_model_transaction_new_from_query(Query *query)
+{
+ GncTreeModelTransaction *model;
+ GList *tlist;
+
+ tlist = xaccQueryGetTransactions(query, QUERY_TXN_MATCH_ANY);
+ model = gnc_tree_model_transaction_new(tlist);
+ model->priv->query = query;
+ return model;
+}
+
+GncTreeModelTransaction *
+gnc_tree_model_transaction_new_from_account(Account *acc)
+{
+ GncTreeModelTransaction *model;
+ GList *tlist, *slist;
+
+ slist = xaccAccountGetSplitList(acc);
+ tlist = xaccSplitListGetUniqueTransactions(slist);
+ model = gnc_tree_model_transaction_new(tlist);
+ model->priv->anchor = acc;
+ return model;
+}
+
+/************************************************************/
+/* Gnc Tree Model Debugging Utility Function */
+/************************************************************/
+
+#define ITER_STRING_LEN 128
+
+static const gchar *
+iter_to_string(GtkTreeIter *iter)
+{
+#ifdef G_THREADS_ENABLED
+ static GStaticPrivate gtmits_buffer_key = G_STATIC_PRIVATE_INIT;
+ gchar *string;
+
+ string = g_static_private_get (>mits_buffer_key);
+ if (string == NULL) {
+ string = malloc(ITER_STRING_LEN + 1);
+ g_static_private_set (>mits_buffer_key, string, g_free);
+ }
+#else
+ static char string[ITER_STRING_LEN + 1];
+#endif
+
+ if (iter)
+ snprintf(
+ string, ITER_STRING_LEN,
+ "[stamp:%x data:%d, %p (%p:%s), %p]",
+ iter->stamp, GPOINTER_TO_INT(iter->user_data),
+ iter->user_data2,
+ iter->user_data2 ? ((GList *) iter->user_data2)->data : 0,
+ iter->user_data2 ?
+ ((QofEntity *)((GList *) iter->user_data2)->data)->e_type : "",
+ iter->user_data3);
+ else
+ strcpy(string, "(null)");
+ return string;
+}
+
+
+/************************************************************/
+/* Gtk Tree Model Required Interface Functions */
+/************************************************************/
+
+static void
+gnc_tree_model_transaction_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = gnc_tree_model_transaction_get_flags;
+ iface->get_n_columns = gnc_tree_model_transaction_get_n_columns;
+ iface->get_column_type = gnc_tree_model_transaction_get_column_type;
+ iface->get_iter = gnc_tree_model_transaction_get_iter;
+ iface->get_path = gnc_tree_model_transaction_get_path;
+ iface->get_value = gnc_tree_model_transaction_get_value;
+ iface->iter_next = gnc_tree_model_transaction_iter_next;
+ iface->iter_children = gnc_tree_model_transaction_iter_children;
+ iface->iter_has_child = gnc_tree_model_transaction_iter_has_child;
+ iface->iter_n_children = gnc_tree_model_transaction_iter_n_children;
+ iface->iter_nth_child = gnc_tree_model_transaction_iter_nth_child;
+ iface->iter_parent = gnc_tree_model_transaction_iter_parent;
+}
+
+static GtkTreeModelFlags
+gnc_tree_model_transaction_get_flags (GtkTreeModel *tm)
+{
+ return 0;
+}
+
+static int
+gnc_tree_model_transaction_get_n_columns (GtkTreeModel *tm)
+{
+ g_return_val_if_fail(GNC_IS_TREE_MODEL_TRANSACTION(tm), -1);
+ return GNC_TREE_MODEL_TRANSACTION_NUM_COLUMNS;
+}
+
+static GType
+gnc_tree_model_transaction_get_column_type (GtkTreeModel *tm, int index)
+{
+ g_return_val_if_fail (GNC_IS_TREE_MODEL_TRANSACTION (tm),
+ G_TYPE_INVALID);
+ g_return_val_if_fail ((index < GNC_TREE_MODEL_TRANSACTION_NUM_COLUMNS) &&
+ (index >= 0), G_TYPE_INVALID);
+
+ switch (index) {
+ case GNC_TREE_MODEL_TRANSACTION_COL_GUID:
+ return G_TYPE_POINTER;
+
+ case GNC_TREE_MODEL_TRANSACTION_COL_DATE:
+ return G_TYPE_ULONG;
+
+ case GNC_TREE_MODEL_TRANSACTION_COL_NUM:
+ case GNC_TREE_MODEL_TRANSACTION_COL_DESCRIPTION:
+ return G_TYPE_STRING;
+
+ default:
+ g_assert_not_reached();
+ return G_TYPE_INVALID;
+ }
+}
+
+static void
+gnc_tree_model_transaction_get_value (GtkTreeModel *tm, GtkTreeIter *iter,
+ int column, GValue *value)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ Split *split = NULL;
+ Transaction *trans;
+ gboolean is_split, is_blank, is_sep;
+ const GUID * guid;
+ GList *node;
+
+ TRACE("model %p, iter %s, col %d", tm, iter_to_string(iter), column);
+ g_assert(VALID_ITER(model, iter));
+
+ is_split = IS_SPLIT(iter);
+ is_blank = IS_BLANK(iter);
+ is_sep = IS_SEP(iter);
+ node = (GList *) iter->user_data2;
+ trans = (Transaction *) node->data;
+ if (is_split) {
+ node = (GList *) iter->user_data3;
+ split = (Split *) node->data;
+ }
+
+ g_value_init(value,
+ gnc_tree_model_transaction_get_column_type(tm, column));
+ switch (column) {
+ case GNC_TREE_MODEL_TRANSACTION_COL_GUID:
+ if (is_split)
+ guid = qof_entity_get_guid((QofEntity *)split);
+ else
+ guid = qof_entity_get_guid((QofEntity *)trans);
+ g_value_set_pointer(value, (gpointer) guid);
+ break;
+ case GNC_TREE_MODEL_TRANSACTION_COL_DATE:
+ if (is_split)
+ g_value_set_ulong(value, 0);
+ else {
+ gulong i = (gulong) xaccTransGetDate(trans);
+ if (is_sep)
+ g_value_set_ulong(value, time(NULL));
+ else if (is_blank && i == 0)
+ /* kinda hokie but we just want default blank trans
+ right after sep */
+ g_value_set_ulong(value, time(NULL)+10);
+ else
+ g_value_set_ulong(value, i);
+ }
+ break;
+ /*
+ case GNC_TREE_MODEL_TRANSACTION_COL_REC:
+ g_value_set_string(value, is_split ? xaccSplitGetReconcile(split) :
+ "");
+ break;
+ */
+ case GNC_TREE_MODEL_TRANSACTION_COL_NUM:
+ g_value_set_string(value, is_split ? xaccSplitGetAction(split) :
+ xaccTransGetNum(trans));
+ break;
+ case GNC_TREE_MODEL_TRANSACTION_COL_DESCRIPTION:
+ g_value_set_string(value, is_split ? xaccSplitGetMemo(split) :
+ xaccTransGetDescription(trans));
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+gnc_tree_model_transaction_get_iter(GtkTreeModel *tm, GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ GList *tnode, *snode, *slist;
+ gint depth, *indices, flags;
+
+ g_return_val_if_fail (GNC_IS_TREE_MODEL_TRANSACTION (tm), FALSE);
+ TRACE("model %p, path %s", tm, gtk_tree_path_to_string(path));
+
+ depth = gtk_tree_path_get_depth(path);
+ indices = gtk_tree_path_get_indices (path);
+ tnode = g_list_nth(model->priv->tlist, indices[0]);
+ if (!tnode) {
+ DEBUG("path index off end of list");
+ goto fail;
+ }
+
+ if (depth == 1) { /* Trans */
+ snode = NULL;
+ /* Check if this is the separator or blank trans */
+ if (tnode->data == model->priv->sep_trans)
+ flags = SEP;
+ else if (tnode->data == model->priv->btrans)
+ flags = BLANK;
+ else flags = 0;
+ } else if (depth == 2) { /* Split */
+ Split *split = xaccTransGetSplit(tnode->data, indices[1]);
+
+ slist = xaccTransGetSplitList(tnode->data);
+ snode = g_list_find(slist, split);
+
+ flags = SPLIT;
+
+ if (!snode && tnode == model->priv->bsplit_parent_node &&
+ xaccTransCountSplits(tnode->data) == indices[1])
+ snode = model->priv->bsplit_node;
+
+ if (!snode) goto fail;
+
+ if (snode->data == model->priv->bsplit_node->data)
+ flags |= BLANK;
+
+ if (!snode) {
+ PERR("Invalid path index: %d", indices[1]);
+ goto fail;
+ }
+ } else {
+ DEBUG("Invalid path depth");
+ goto fail;
+ }
+
+ *iter = make_iter(model, flags, tnode, snode);
+ g_assert(VALID_ITER(model, iter));
+ return TRUE;
+ fail:
+ iter->stamp = 0;
+ return FALSE;
+}
+
+static GtkTreePath *
+gnc_tree_model_transaction_get_path (GtkTreeModel *tm, GtkTreeIter *iter)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ GtkTreePath *path;
+ gint pos;
+ GList *tnode;
+
+ g_assert(VALID_ITER(model, iter));
+ path = gtk_tree_path_new();
+ tnode = iter->user_data2;
+
+ /* This works fine for the separator and blank trans, too. */
+ pos = g_list_position(model->priv->tlist, tnode);
+ if (pos == -1)
+ goto fail;
+ gtk_tree_path_append_index(path, pos);
+
+ if (IS_SPLIT(iter)) {
+ Transaction *trans;
+ GList *slist;
+ Split *split;
+
+ trans = (Transaction *) tnode->data;
+ slist = xaccTransGetSplitList(trans);
+ split = ((GList*)iter->user_data3)->data;
+ pos = xaccTransGetSplitIndex(trans, split);
+ if (pos == -1) {
+ if (IS_BLANK(iter))
+ pos = xaccTransCountSplits(trans);
+ else
+ goto fail;
+ }
+ gtk_tree_path_append_index(path, pos);
+ }
+
+ return path;
+ fail:
+ return NULL;
+}
+
+static gboolean
+gnc_tree_model_transaction_iter_next (GtkTreeModel *tm, GtkTreeIter *iter)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ GList *snode, *tnode;
+ gint flags = 0;
+
+ ENTER("model %p, iter %s", tm, iter_to_string(iter));
+ g_assert(VALID_ITER(model, iter));
+
+ if (IS_BLANK(iter)) {
+ LEAVE("Blanks _never_ have a next");
+ goto fail;
+ }
+
+ tnode = iter->user_data2;
+ if (IS_SPLIT(iter)) {
+ flags = SPLIT;
+ snode = iter->user_data3;
+ do {
+ snode = snode->next;
+ } while (snode && !xaccTransStillHasSplit(tnode->data, snode->data));
+ if (!snode) {
+ if (model->priv->bsplit_parent_node == tnode) {
+ snode = model->priv->bsplit_node;
+ flags |= BLANK;
+ } else {
+ LEAVE("Last non-blank split has no next");
+ goto fail;
+ }
+ }
+ } else {
+ flags = 0;
+ snode = NULL;
+ tnode = tnode->next;
+
+ /* Check if this is the separator or blank trans */
+ if (!tnode) {
+ LEAVE("last trans has no next");
+ goto fail;
+ } else if (tnode->data == model->priv->sep_trans)
+ flags |= SEP;
+ else if (tnode->data == model->priv->btrans)
+ flags |= BLANK;
+
+ }
+
+ *iter = make_iter(model, flags, tnode, snode);
+ LEAVE("iter %s", iter_to_string(iter));
+ return TRUE;
+ fail:
+ iter->stamp = 0;
+ return FALSE;
+}
+
+static gboolean
+gnc_tree_model_transaction_iter_children (GtkTreeModel *tm, GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ GList *tnode, *snode = NULL, *slist;
+ gint flags = 0;
+ Transaction *trans;
+ Split *split;
+
+ ENTER("model %p, iter %p (to be filed in), parent %s",
+ tm, iter, iter_to_string(parent));
+
+ g_return_val_if_fail (GNC_IS_TREE_MODEL_TRANSACTION (tm), FALSE);
+
+ if (!parent) {
+ /* Get the very first iter */
+ tnode = model->priv->tlist;
+ if (tnode) {
+ if (tnode->data == model->priv->sep_trans)
+ flags = SEP;
+ else if (tnode->data == model->priv->btrans)
+ flags = BLANK;
+
+ *iter = make_iter(model, flags, tnode, NULL);
+ LEAVE("iter (2) %s", iter_to_string(iter));
+ return TRUE;
+ } else {
+ PERR("We should never have a NULL trans list.");
+ goto fail;
+ }
+ }
+
+ g_assert(VALID_ITER(model, parent));
+
+ if (IS_SPLIT(parent))
+ goto fail; /* Splits never have children */
+ if (IS_SEP(parent))
+ goto fail; /* The separator trans has no children */
+
+ tnode = parent->user_data2;
+ trans = tnode->data;
+ slist = xaccTransGetSplitList(trans);
+ split = xaccTransGetSplit(trans, 0);
+ flags = SPLIT;
+ if (model->priv->bsplit_parent_node == tnode && !split) {
+ split = (Split *) ((GList *)model->priv->bsplit_node)->data;
+ }
+ if (split == (Split *) ((GList *)model->priv->bsplit_node)->data) {
+ flags |= BLANK;
+ snode = model->priv->bsplit_node;
+ } else if (split)
+ snode = g_list_find(slist, split);
+
+ if (!snode)
+ goto fail;
+
+ *iter = make_iter(model, flags, tnode, snode);
+ LEAVE("iter %s", iter_to_string(iter));
+ return TRUE;
+ fail:
+ LEAVE("iter has no children");
+ iter->stamp = 0;
+ return FALSE;
+}
+
+static gboolean
+gnc_tree_model_transaction_iter_has_child (GtkTreeModel *tm, GtkTreeIter *iter)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ GList *tnode;
+ Transaction *trans;
+ Split *split;
+
+ g_assert(VALID_ITER(model, iter));
+ ENTER("model %p, iter %s", tm, iter_to_string(iter));
+
+ if (IS_SPLIT(iter)) {
+ LEAVE(" splits have no children");
+ return FALSE;
+ }
+ if (IS_SEP(iter)) {
+ LEAVE(" the separator has no children");
+ return FALSE;
+ }
+
+ tnode = iter->user_data2;
+ trans = tnode->data;
+ if (!trans) {
+ PERR(" The trans data should NEVER be NULL.");
+ LEAVE(" trans data was NULL!");
+ return FALSE;
+ }
+ split = xaccTransGetSplit(trans, 0);
+ if (split || (model->priv->bsplit_parent_node == tnode)) {
+ LEAVE(" yes");
+ return TRUE;
+ } else {
+ LEAVE(" trans has no children");
+ return FALSE;
+ }
+}
+
+static int
+gnc_tree_model_transaction_iter_n_children (GtkTreeModel *tm,
+ GtkTreeIter *iter)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ Transaction *trans;
+ GList *tnode;
+ int i;
+
+ ENTER("model %p, iter %s", tm, iter_to_string(iter));
+ g_return_val_if_fail (GNC_IS_TREE_MODEL_TRANSACTION (tm), -1);
+
+ if (iter == NULL) {
+ i = g_list_length(model->priv->tlist);
+ LEAVE("toplevel count is %d", i);
+ return i;
+ }
+
+ g_assert(VALID_ITER(model, iter));
+
+ if (IS_SEP(iter) || IS_SPLIT(iter)) {
+ LEAVE("iter has no children");
+ return 0;
+ }
+
+ tnode = iter->user_data2;
+ trans = tnode->data;
+ i = xaccTransCountSplits(trans);
+ if (model->priv->bsplit_parent_node == tnode)
+ i++;
+
+ LEAVE("iter has %d children", i);
+ return i;
+}
+
+static gboolean
+gnc_tree_model_transaction_iter_nth_child (GtkTreeModel *tm, GtkTreeIter *iter,
+ GtkTreeIter *parent, int n)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ Transaction *trans;
+ Split *split;
+ GList *slist, *snode, *tnode;
+ gint flags;
+
+ ENTER("model %p, iter %s, n %d", tm, iter_to_string(iter), n);
+ g_return_val_if_fail (GNC_IS_TREE_MODEL_TRANSACTION (tm), FALSE);
+
+ if (parent == NULL) { /* Top-level */
+ tnode = g_list_nth(model->priv->tlist, n);
+
+ if (!tnode) {
+ PERR("Trans list should never be NULL.");
+ goto fail;
+ }
+ if (tnode->data == model->priv->sep_trans)
+ flags = SEP;
+ else if (tnode->data == model->priv->btrans)
+ flags = BLANK;
+
+ *iter = make_iter(model, flags, tnode, NULL);
+ LEAVE("iter (2) %s", iter_to_string(iter));
+ return TRUE;
+ }
+
+ DEBUG("parent iter %s", iter_to_string(parent));
+ g_assert(VALID_ITER(model, parent));
+
+ if (IS_SPLIT(parent) || IS_SEP(parent))
+ goto fail; /* Splits and separator have no children */
+
+ flags = SPLIT;
+ tnode = parent->user_data2;
+ trans = tnode->data;
+ split = xaccTransGetSplit(trans, n);
+ slist = xaccTransGetSplitList(trans);
+ snode = g_list_find(slist, split);
+ if (!snode && model->priv->bsplit_parent_node == tnode &&
+ n == xaccTransCountSplits(trans)) {
+ snode = model->priv->bsplit_node;
+ } else goto fail;
+
+ if (snode->data == model->priv->bsplit_node->data)
+ flags |= BLANK;
+ *iter = make_iter(model, flags, tnode, snode);
+ LEAVE("iter (3) %s", iter_to_string(iter));
+ return TRUE;
+ fail:
+ iter->stamp = 0;
+ return FALSE;
+}
+
+static gboolean
+gnc_tree_model_transaction_iter_parent (GtkTreeModel *tm, GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION (tm);
+ GList *tnode;
+ gint flags = 0;
+
+ ENTER("model %p, child %s", tm, iter_to_string(child));
+ g_assert(VALID_ITER(model, child));
+
+ /* Only splits have parents. */
+ if (!IS_SPLIT(child)) goto fail;
+
+ tnode = child->user_data2;
+ if (tnode->data == model->priv->btrans)
+ flags = BLANK;
+ *iter = make_iter(model, flags, tnode, NULL);
+ LEAVE("iter (3) %s", iter_to_string(iter));
+ return TRUE;
+ fail:
+ iter->stamp = 0;
+ return FALSE;
+}
+
+/****** End of GtkTreeModel Interface implementation *******/
+
+static void
+increment_stamp(GncTreeModelTransaction *model)
+{
+ do model->stamp++;
+ while (model->stamp == 0);
+}
+
+static void
+insert_row_at(GncTreeModelTransaction *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+ GList *tnode;
+
+ g_assert(VALID_ITER(model, iter));
+ path = gnc_tree_model_transaction_get_path(GTK_TREE_MODEL(model), iter);
+ if (!path) PERR("Null path");
+
+ increment_stamp(model);
+ if (gnc_tree_model_transaction_get_iter(
+ GTK_TREE_MODEL(model), iter, path)) {
+ gtk_tree_model_row_inserted(GTK_TREE_MODEL(model), path, iter);
+ } else PERR("Tried to insert with invalid iter.");
+
+ if (gtk_tree_path_up(path) && gnc_tree_model_transaction_get_iter(
+ GTK_TREE_MODEL(model), iter, path)) {
+ //gtk_tree_model_row_changed(GTK_TREE_MODEL(model), path, iter);
+ tnode = iter->user_data2;
+ /* Assumption: When the blank split is inserted into the blank
+ trans, it's always the first child of the blank trans. */
+ if (IS_BLANK_TRANS(iter) && tnode->data == model->priv->btrans) {
+ increment_stamp(model);
+
+ PINFO("toggling has_child at row %s\n",
+ gtk_tree_path_to_string(path));
+ gtk_tree_model_row_has_child_toggled(GTK_TREE_MODEL(model),
+ path, iter);
+ }
+ }
+
+ gtk_tree_path_free(path);
+}
+
+static void
+delete_row_at_path(GncTreeModelTransaction *model, GtkTreePath *path)
+{
+ GncTreeModelTransactionPrivate *priv = model->priv;
+ GList *tnode;
+ GtkTreeIter iter;
+ gint depth;
+
+ if (!path) PERR("Null path");
+ increment_stamp(model);
+ gtk_tree_model_row_deleted(GTK_TREE_MODEL(model), path);
+
+ depth = gtk_tree_path_get_depth(path);
+ if (depth == 2) {
+ if (gtk_tree_path_up(path) && gnc_tree_model_transaction_get_iter(
+ GTK_TREE_MODEL(model), &iter, path)) {
+ //gtk_tree_model_row_changed(GTK_TREE_MODEL(model), path, &iter);
+ tnode = iter.user_data2;
+ /* Assumption: When the blank split is removed from the blank
+ trans, it's always the last child of the blank trans. */
+ if (IS_BLANK_TRANS(&iter) && tnode->data == model->priv->btrans) {
+ increment_stamp(model);
+
+ PINFO("toggling has_child at row %s\n",
+ gtk_tree_path_to_string(path));
+
+ gtk_tree_model_row_has_child_toggled(GTK_TREE_MODEL(model),
+ path, &iter);
+ }
+ }
+ } else {
+ if (gnc_tree_model_transaction_get_iter(
+ GTK_TREE_MODEL(model), &iter, path)) {
+ tnode = iter.user_data2;
+ if (tnode == priv->bsplit_parent_node)
+ priv->bsplit_parent_node = NULL;
+ model->priv->tlist = g_list_delete_link(
+ model->priv->tlist, tnode);
+
+ }
+ }
+}
+
+static void
+delete_row_at(GncTreeModelTransaction *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+ g_assert(VALID_ITER(model, iter));
+ path = gnc_tree_model_transaction_get_path(GTK_TREE_MODEL(model), iter);
+ delete_row_at_path(model, path);
+ gtk_tree_path_free(path);
+}
+
+static void
+changed_row_at(GncTreeModelTransaction *model, GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+ g_assert(VALID_ITER(model, iter));
+ path = gnc_tree_model_transaction_get_path(GTK_TREE_MODEL(model), iter);
+ if (!path) PERR("Null path");
+
+ increment_stamp(model);
+ if (gnc_tree_model_transaction_get_iter(GTK_TREE_MODEL(model), iter, path))
+ gtk_tree_model_row_changed(GTK_TREE_MODEL(model), path, iter);
+ else PERR("Tried to change with invalid iter.");
+
+ gtk_tree_path_free(path);
+}
+
+static void
+insert_trans(GncTreeModelTransaction *model, Transaction *trans)
+{
+ GtkTreeIter iter;
+ GList *tnode, *snode;
+
+ model->priv->tlist = g_list_prepend(model->priv->tlist, trans);
+ tnode = model->priv->tlist;
+ iter = make_iter(model, 0, tnode, NULL);
+ insert_row_at(model, &iter);
+ for (snode = xaccTransGetSplitList(trans); snode; snode = snode->next) {
+ if (xaccTransStillHasSplit(trans, snode->data)) {
+ iter = make_iter(model, SPLIT, tnode, snode);
+ insert_row_at(model, &iter);
+ }
+ }
+
+}
+
+/* Moves the blank split to 'trans'.
+ */
+gboolean
+gnc_tree_model_transaction_set_blank_split_parent(
+ GncTreeModelTransaction *model, Transaction *trans)
+{
+ GList *tnode, *bs_parent_node;
+ GncTreeModelTransactionPrivate *priv;
+ GtkTreeIter iter;
+ gboolean moved;
+
+ priv = model->priv;
+ tnode = g_list_find(priv->tlist, trans);
+
+ if (priv->sep_trans == trans) return FALSE;
+
+ bs_parent_node = priv->bsplit_parent_node;
+
+ if (tnode != bs_parent_node) {
+ moved = (bs_parent_node != NULL);
+ if (moved) {
+ /* Delete the row where the blank split used to be. */
+ iter = make_iter(model, SPLIT | BLANK, bs_parent_node,
+ priv->bsplit_node);
+ delete_row_at(model, &iter);
+ priv->bsplit_parent_node = NULL;
+ }
+
+ priv->bsplit_parent_node = tnode;
+ iter = make_iter(model, SPLIT | BLANK, tnode, priv->bsplit_node);
+ insert_row_at(model, &iter);
+ } else
+ moved = FALSE;
+
+ return moved;
+}
+
+Account *
+gnc_tree_model_transaction_get_anchor(GncTreeModelTransaction *model)
+{
+ g_return_val_if_fail(GNC_IS_TREE_MODEL_TRANSACTION(model), NULL);
+ return model->priv->anchor;
+}
+
+gboolean
+gnc_tree_model_transaction_get_split_and_trans (
+ GncTreeModelTransaction *model, GtkTreeIter *iter,
+ gboolean *is_split, gboolean *is_blank, Split **split, Transaction **trans)
+{
+ GList *node;
+
+ g_return_val_if_fail(VALID_ITER(model, iter), FALSE);
+
+ if (is_split)
+ *is_split = IS_SPLIT(iter);
+ if (is_blank)
+ *is_blank = IS_BLANK(iter);
+
+ if (trans) {
+ node = iter->user_data2;
+ *trans = node ? (Transaction *) node->data : NULL;
+ }
+ if (split) {
+ node = iter->user_data3;
+ *split = node ? (Split *) node->data : NULL;
+ }
+ return TRUE;
+}
+
+static void
+make_new_blank_split(GncTreeModelTransaction *model)
+{
+ GtkTreeIter iter;
+ Split *split;
+ GList *tnode = model->priv->bsplit_parent_node;
+
+ split = xaccMallocSplit(model->priv->book);
+ if (model->priv->anchor)
+ xaccSplitSetAccount(split, model->priv->anchor);
+ model->priv->bsplit = split;
+ model->priv->bsplit_node->data = model->priv->bsplit;
+
+ /* Insert the new row */
+ iter = make_iter(model, BLANK|SPLIT, tnode, model->priv->bsplit_node);
+ insert_row_at(model, &iter);
+}
+
+/* Turn the current blank split into a real split. This function is
+ * never called in response to an engine event. Instead, this
+ * function is called from the treeview to tell the model to commit
+ * the blank split.
+ */
+static void
+gnc_tree_model_transaction_commit_blank_split(GncTreeModelTransaction *model)
+{
+ Split *bsplit;
+ Transaction *trans;
+ GList *tnode, *snode;
+ GtkTreeIter iter;
+
+ tnode = model->priv->bsplit_parent_node;
+ bsplit = model->priv->bsplit;
+ if (!tnode || !tnode->data) {
+ PERR("blank split has no trans");
+ return;
+ }
+ trans = tnode->data;
+ if (xaccTransGetSplitIndex(trans, bsplit) == -1) {
+ PINFO("blank split has been removed from this trans");
+ return;
+ }
+ snode = g_list_find(xaccTransGetSplitList(trans), bsplit);
+ if (!snode) {
+ PERR("Failed to turn blank split into real split");
+ return;
+ }
+
+ /* If we haven't set an amount yet, and there's an imbalance, use that. */
+ if (gnc_numeric_zero_p(xaccSplitGetAmount(bsplit))) {
+ gnc_numeric imbal = gnc_numeric_neg(xaccTransGetImbalance(trans));
+ if (!gnc_numeric_zero_p(imbal)) {
+ gnc_numeric amount, rate;
+ Account *acct = xaccSplitGetAccount(bsplit);
+ xaccSplitSetValue(bsplit, imbal);
+ if (gnc_commodity_equal(xaccAccountGetCommodity(acct),
+ xaccTransGetCurrency(trans)))
+ amount = imbal;
+ else {
+ rate = xaccTransGetAccountConvRate(trans, acct);
+ amount = gnc_numeric_mul(
+ imbal, rate,
+ xaccAccountGetCommoditySCU(acct), GNC_RND_ROUND);
+ }
+ if (gnc_numeric_check(amount) == GNC_ERROR_OK)
+ xaccSplitSetAmount(bsplit, amount);
+ }
+ }
+ /* Mark the old blank split as changed */
+ iter = make_iter(model, SPLIT, tnode, snode);
+ changed_row_at(model, &iter);
+ make_new_blank_split(model);
+}
+
+void
+gnc_tree_model_transaction_commit_split(GncTreeModelTransaction *model,
+ Split *split)
+{
+ if (split == model->priv->bsplit) {
+ gnc_tree_model_transaction_commit_blank_split(model);
+ }
+}
+
+static gboolean
+get_iter_from_split (GncTreeModelTransaction *model, Split *split,
+ GtkTreeIter *iter)
+{
+ GncTreeModelTransactionPrivate *priv;
+ GList *tnode, *snode, *slist;
+ Transaction *trans;
+ gint flags;
+
+ g_return_val_if_fail(GNC_IS_TREE_MODEL_TRANSACTION (model), FALSE);
+ g_return_val_if_fail(split && iter, FALSE);
+
+ priv = model->priv;
+ if (priv->book != xaccSplitGetBook(split)) return FALSE;
+
+ trans = xaccSplitGetParent(split);
+ tnode = g_list_find(priv->tlist, trans);
+ if (!tnode) return FALSE;
+
+ if (!xaccTransStillHasSplit(trans, split)) return FALSE;
+
+ slist = xaccTransGetSplitList(trans);
+ snode = g_list_find(slist, split);
+ flags = SPLIT;
+ if (!snode && split == (Split *) ((GList *)priv->bsplit_node)->data) {
+ snode = priv->bsplit_node;
+ flags |= BLANK;
+ }
+ if (!snode) return FALSE;
+
+ *iter = make_iter(model, flags, tnode, snode);
+ return TRUE;
+}
+
+gboolean
+gnc_tree_model_transaction_get_iter_from_trans(
+ GncTreeModelTransaction *model, Transaction *trans, GtkTreeIter *iter)
+{
+ GncTreeModelTransactionPrivate *priv;
+ GList *tnode;
+ gint flags = 0;
+
+ g_return_val_if_fail(GNC_IS_TREE_MODEL_TRANSACTION(model), FALSE);
+ g_return_val_if_fail(trans && iter, FALSE);
+
+ priv = model->priv;
+ if (priv->book != xaccTransGetBook(trans)) return FALSE;
+
+ tnode = g_list_find(priv->tlist, trans);
+ if (!tnode) return FALSE;
+
+ if (trans == priv->btrans)
+ flags |= BLANK;
+
+ *iter = make_iter(model, flags, tnode, NULL);
+ return TRUE;
+}
+
+/* Returns just the path to the transaction if idx_of_split is -1. */
+static GtkTreePath *
+get_removal_path(GncTreeModelTransaction *model, Transaction *trans,
+ gint idx_of_split)
+{
+ GncTreeModelTransactionPrivate *priv;
+ GList *tnode;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ g_return_val_if_fail(GNC_IS_TREE_MODEL_TRANSACTION(model), NULL);
+ g_return_val_if_fail(trans, NULL);
+
+ priv = model->priv;
+ if (priv->book != xaccTransGetBook(trans)) return FALSE;
+
+ tnode = g_list_find(priv->tlist, trans);
+ if (!tnode) return FALSE;
+
+ iter = make_iter(model, 0, tnode, NULL);
+ path = gnc_tree_model_transaction_get_path(GTK_TREE_MODEL(model), &iter);
+
+ if (idx_of_split >= 0)
+ gtk_tree_path_append_index(path, idx_of_split);
+ else if (idx_of_split != -1)
+ PERR("Invalid idx_of_split");
+ return path;
+}
+
+/** This function is the handler for all event messages from the
+ * engine. Its purpose is to update the tree model any time
+ * an split or trans is added to the engine or deleted from the engine.
+ * This change to the model is then propagated to any/all overlying
+ * filters and views. This function listens to the ADD, REMOVE, and
+ * DESTROY events.
+ */
+static void
+gnc_tree_model_transaction_event_handler(
+ QofEntity *entity, QofEventId event_type, gpointer tm, gpointer event_data)
+{
+ GncTreeModelTransaction *model = (GncTreeModelTransaction *) tm;
+ GncTreeModelTransactionPrivate *priv = model->priv;
+ GncEventData *ed = event_data;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ Transaction *trans;
+ Split *split;
+ QofIdType type;
+ const gchar *name;
+ GList *tnode;
+
+ g_return_if_fail(GNC_IS_TREE_MODEL_TRANSACTION(model));
+ if (QOF_INSTANCE(entity)->book != priv->book)
+ return;
+ type = entity->e_type;
+
+ if (safe_strcmp(type, GNC_ID_SPLIT) == 0) {
+ /* Get the split.*/
+ split = (Split *) entity;
+ name = xaccSplitGetMemo(split);
+
+ switch (event_type) {
+ case QOF_EVENT_MODIFY:
+ if (get_iter_from_split(model, split, &iter)) {
+ DEBUG("change split %p (%s)", split, name);
+ changed_row_at(model, &iter);
+ }
+ break;
+ default:
+ DEBUG("ignored event for %p (%s)", split, name);
+ }
+ } else if (safe_strcmp(type, GNC_ID_TRANS) == 0) {
+ /* Get the trans.*/
+ trans = (Transaction *) entity;
+ name = xaccTransGetDescription(trans);
+
+ switch (event_type) {
+ case GNC_EVENT_ITEM_ADDED:
+ split = (Split *) ed->node;
+ /* The blank split will be added to the transaction when
+ it's first edited. That will generate an event, but
+ we don't want to emit row_inserted because we were
+ already showing the blank split. */
+ if (split == priv->bsplit) break;
+
+ /* Tell the filters/views where the new row was added. */
+ if (get_iter_from_split(model, split, &iter)) {
+ DEBUG("add split %p (%s)", split, name);
+ insert_row_at(model, &iter);
+ }
+ break;
+ case GNC_EVENT_ITEM_REMOVED:
+ split = (Split *) ed->node;
+
+ path = get_removal_path(model, trans, ed->idx);
+ if (path) {
+ DEBUG("remove trans %p (%s)", trans, name);
+ delete_row_at_path(model, path);
+ gtk_tree_path_free(path);
+ }
+ if (split == priv->bsplit)
+ make_new_blank_split(model);
+ break;
+ case QOF_EVENT_MODIFY:
+ /* The blank trans won't emit MODIFY until it's committed */
+ if (priv->btrans == trans) {
+ priv->btrans = xaccMallocTransaction(priv->book);
+ priv->tlist = g_list_prepend(priv->tlist, priv->btrans);
+
+ /* Insert a new blank trans */
+ iter = make_iter(model, BLANK, priv->tlist, NULL);
+ insert_row_at(model, &iter);
+ }
+
+ if (gnc_tree_model_transaction_get_iter_from_trans(
+ model, trans, &iter)) {
+ DEBUG("change trans %p (%s)", trans, name);
+ changed_row_at(model, &iter);
+ }
+ break;
+ case QOF_EVENT_DESTROY:
+ if (priv->btrans == trans) {
+ tnode = g_list_find(priv->tlist, priv->btrans);
+ priv->btrans = xaccMallocTransaction(priv->book);
+ tnode->data = priv->btrans;
+
+ iter = make_iter(model, BLANK, tnode, NULL);
+ changed_row_at(model, &iter);
+ } else if (gnc_tree_model_transaction_get_iter_from_trans(
+ model, trans, &iter)) {
+ delete_row_at(model, &iter);
+ DEBUG("destroy trans %p (%s)", trans, name);
+ }
+ break;
+ default:
+ DEBUG("ignored event for %p (%s)", trans, name);
+ }
+ } else if (safe_strcmp(type, GNC_ID_ACCOUNT) == 0) {
+ switch (event_type) {
+ Account *acc;
+ case GNC_EVENT_ITEM_ADDED:
+ split = (Split *) ed;
+ acc = xaccSplitGetAccount(split);
+ trans = xaccSplitGetParent(split);
+ if (!g_list_find(priv->tlist, trans) &&
+ ((xaccAccountHasAncestor(acc, priv->anchor) &&
+ priv->include_subacc) || acc == priv->anchor)) {
+ insert_trans(model, trans);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+}
+
+QofBook *
+gnc_tree_model_transaction_get_book(GncTreeModelTransaction *model)
+{
+ g_return_val_if_fail(GNC_IS_TREE_MODEL_TRANSACTION(model), NULL);
+ return model->priv->book;
+}
+
+//FIXME: Is this even needed?
+gint
+gtmt_sort_by_date(GtkTreeModel *tm, GtkTreeIter *a, GtkTreeIter *b,
+ gpointer user_data)
+{
+ GncTreeModelTransaction *model = GNC_TREE_MODEL_TRANSACTION(tm);
+ GList *tnode;
+ time_t i, j;
+
+ /* Games we play here: blank trans is always last; sep trans is
+ always now */
+ if (!VALID_ITER(model, a)) PERR("Invalid a iter.");
+ if (!VALID_ITER(model, b)) PERR("Invalid b iter.");
+
+ if (IS_BLANK_TRANS(a)) return 1;
+ if (IS_BLANK_TRANS(b)) return -1;
+
+ tnode = a->user_data2;
+ i = IS_SEP(a) ? time(NULL) : xaccTransGetDate((Transaction*)tnode->data);
+ tnode = b->user_data2;
+ j = IS_SEP(b) ? time(NULL) : xaccTransGetDate((Transaction*)tnode->data);
+
+ return (gint)(i - j);
+}
Added: gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.h
===================================================================
--- gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.h 2006-05-29 20:59:33 UTC (rev 14248)
+++ gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-model-transaction.h 2006-05-29 21:18:25 UTC (rev 14249)
@@ -0,0 +1,172 @@
+/********************************************************************\
+ * gnc-tree-model-transaction.h -- GtkTreeModel implementation to *
+ * display Transactions in a GtkTreeView. *
+ * Copyright (C) 2006 Chris Shoemaker <c.shoemaker at cox.net> *
+ * *
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+
+#ifndef __GNC_TREE_MODEL_TRANSACTION_H
+#define __GNC_TREE_MODEL_TRANSACTION_H
+
+#include <gtk/gtktreemodel.h>
+#include "gnc-tree-model.h"
+
+#include "Query.h"
+
+G_BEGIN_DECLS
+
+/* type macros */
+#define GNC_TYPE_TREE_MODEL_TRANSACTION (gnc_tree_model_transaction_get_type ())
+#define GNC_TREE_MODEL_TRANSACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNC_TYPE_TREE_MODEL_TRANSACTION, GncTreeModelTransaction))
+#define GNC_TREE_MODEL_TRANSACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNC_TYPE_TREE_MODEL_TRANSACTION, GncTreeModelTransactionClass))
+#define GNC_IS_TREE_MODEL_TRANSACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNC_TYPE_TREE_MODEL_TRANSACTION))
+#define GNC_IS_TREE_MODEL_TRANSACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNC_TYPE_TREE_MODEL_TRANSACTION))
+#define GNC_TREE_MODEL_TRANSACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_TREE_MODEL_TRANSACTION, GncTreeModelTransactionClass))
+#define GNC_TREE_MODEL_TRANSACTION_NAME "GncTreeModelTransaction"
+
+
+typedef enum {
+ GNC_TREE_MODEL_TRANSACTION_COL_GUID,
+ GNC_TREE_MODEL_TRANSACTION_COL_DATE,
+ GNC_TREE_MODEL_TRANSACTION_COL_NUM,
+ GNC_TREE_MODEL_TRANSACTION_COL_DESCRIPTION,
+ //GNC_TREE_MODEL_TRANSACTION_COL_REC,
+ GNC_TREE_MODEL_TRANSACTION_NUM_COLUMNS,
+} GncTreeModelTransactionColumn;
+
+/* typedefs & structures */
+typedef struct GncTreeModelTransactionPrivate GncTreeModelTransactionPrivate;
+
+typedef struct {
+ GncTreeModel gnc_tree_model;
+ GncTreeModelTransactionPrivate *priv;
+ int stamp;
+} GncTreeModelTransaction;
+
+typedef struct {
+ GncTreeModelClass gnc_tree_model;
+} GncTreeModelTransactionClass;
+
+/* Standard g_object type */
+GType gnc_tree_model_transaction_get_type (void);
+
+QofBook *gnc_tree_model_transaction_get_book(GncTreeModelTransaction *model);
+
+/** @name Transaction Tree Model Constructors
+ @{ */
+
+/** Create a new GtkTreeModel for manipulating GnuCash transactions. */
+GncTreeModelTransaction *
+gnc_tree_model_transaction_new_from_query (Query *query);
+GncTreeModelTransaction *
+gnc_tree_model_transaction_new_from_account (Account *acc);
+/** @} */
+
+
+/** @name Transaction Tree Model Get/Set Functions
+ @{ */
+
+#if 0
+/** Convert a model/iter pair to a gnucash split. This routine should
+ * only be called from an split tree view filter function. The
+ * model and iter values will be provided as part of the call to the
+ * filter.
+ *
+ * @param model A pointer to the split tree model.
+ *
+ * @param iter A gtk_tree_iter corresponding to a single split in
+ * the model.
+ *
+ * @return A pointer to the corresponding split.
+ */
+Transaction *gnc_tree_model_transaction_get_split (
+ GncTreeModelTransaction *model, GtkTreeIter *iter);
+
+
+/** Convert a model/split pair into a gtk_tree_model_iter. This
+ * routine should only be called from the file
+ * gnc-tree-view-split.c.
+ *
+ * @internal
+ *
+ * @param model The model that an split belongs to.
+ *
+ * @param split The split to convert.
+ *
+ * @param iter A pointer to an iter. This iter will be rewritten to
+ * contain the results of the query.
+ *
+ * @return TRUE if the split was found and the iter filled
+ * in. FALSE otherwise.
+ */
+gboolean gnc_tree_model_transaction_get_iter_from_trans (
+ GncTreeModelTransaction *model,
+ Transaction *trans,
+ GtkTreeIter *iter);
+
+
+/** Convert a model/split pair into a gtk_tree_model_path. This
+ * routine should only be called from the file
+ * gnc-tree-view-split.c.
+ *
+ * @internal
+ *
+ * @param model The model that an split belongs to.
+ *
+ * @param split The split to convert.
+ *
+ * @return A pointer to a path describing the split. It is the
+ * responsibility of the caller to free this path when done.
+ */
+GtkTreePath *gnc_tree_model_transaction_get_path_from_trans (
+ GncTreeModelTransaction *model, Transaction *trans);
+#endif
+
+gboolean gnc_tree_model_transaction_set_blank_split_parent(
+ GncTreeModelTransaction *model, Transaction *trans);
+
+Account *
+gnc_tree_model_transaction_get_anchor(GncTreeModelTransaction *model);
+
+gboolean
+gnc_tree_model_transaction_get_split_and_trans (
+ GncTreeModelTransaction *model, GtkTreeIter *iter,
+ gboolean *is_split, gboolean *is_blank,
+ Split **split, Transaction **trans);
+
+void
+gnc_tree_model_transaction_commit_split(GncTreeModelTransaction *model,
+ Split *split);
+gint gtmt_sort_by_date(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
+ gpointer user_data);
+
+gboolean
+gnc_tree_model_transaction_get_iter_from_trans(
+ GncTreeModelTransaction *model, Transaction *trans, GtkTreeIter *iter);
+
+/** @} */
+
+G_END_DECLS
+
+#endif /* __GNC_TREE_MODEL_TRANSACTION_H */
+
+/** @} */
+/** @} */
Added: gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.c
===================================================================
--- gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.c 2006-05-29 20:59:33 UTC (rev 14248)
+++ gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.c 2006-05-29 21:18:25 UTC (rev 14249)
@@ -0,0 +1,1534 @@
+/* TODO:
+ - date entry
+ - autocomplete
+ - expansion policy - easy
+ - action field for trans
+ - replace has_rate with price visibility test
+ - non-anchored register
+
+test basis mode debcred edit for blanktrans
+
+ QUESTIONS
+ - some bug with a zero amount in xfer dialog?
+ - Is there any need to register with CM?
+ - basic mode cred/deb edit - allow edit of trans with >2 splits?
+
+ Done-ish
+ - keynav
+ - don't commit blank split just because of tab-through
+ - cancel commit - navigation aborts to trans row, not prev split row
+ - basic mode account field edit - done?
+ - stock register - done?
+ - reorderable columns? solved by gconf
+ - column selection? solved by gconf
+
+*/
+/********************************************************************\
+ * gnc-tree-view-transaction.c -- GtkTreeView implementation to *
+ * display Transactions in a GtkTreeView. *
+ * Copyright (C) 2006 Chris Shoemaker <c.shoemaker at cox.net> *
+ * *
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "gnc-date.h"
+#include "gnc-tree-view.h"
+#include "gnc-tree-model-account.h"
+#include "gnc-tree-model-transaction.h"
+#include "gnc-tree-view-transaction.h"
+#include "gnctreemodelsort.h"
+
+#include "Account.h"
+#include "Transaction.h"
+#include "Scrub.h"
+#include "gnc-component-manager.h"
+#include "gnc-icons.h"
+#include "gnc-ui-util.h"
+#include "gnc-exp-parser.h"
+#include "dialog-transfer.h"
+#include "dialog-utils.h"
+
+#define SPLIT_TRANS_STR _("-- Split Transaction --")
+
+/** Static Globals *******************************************************/
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = GNC_MOD_GUI;
+
+/** Declarations *********************************************************/
+static void gnc_tree_view_transaction_class_init(
+ GncTreeViewTransactionClass *klass);
+static void gnc_tree_view_transaction_init (GncTreeViewTransaction *view);
+static void gnc_tree_view_transaction_finalize (GObject *object);
+static void gnc_tree_view_transaction_dispose (GObject *object);
+
+static void gtvt_edited_cb(GtkCellRendererText *cell, const gchar *path_string,
+ const gchar *new_text, gpointer _model);
+static void start_edit(GtkCellRenderer *cr, GtkCellEditable *editable,
+ const gchar *path, gpointer user_data);
+static void get_editable_start_editing_cb(
+ GtkCellRenderer *cr, GtkCellEditable *editable,
+ const gchar *path, gpointer user_data);
+
+static gboolean
+gtvt_key_press_cb(GtkWidget *treeview, GdkEventKey *event, gpointer userdata);
+
+typedef enum {
+ COL_DATE,
+ COL_NUM,
+ COL_DESCRIPTION,
+ COL_ACCOUNT,
+ COL_AMOUNT,
+ COL_VALUE,
+ COL_CREDIT,
+ COL_DEBIT,
+ COL_RECN,
+ COL_BALANCE,
+ COL_RATE,
+ COL_TYPE,
+ COL_NOTES,
+} ViewCol;
+
+typedef struct {
+ ViewCol viewcol;
+ gint modelcol;
+ gchar *title;
+ gchar *pref_name;
+ gchar *sizer;
+ int visibility_model_col;
+ void (*edited_cb)(GtkCellRendererText *, const gchar *,
+ const gchar *, gpointer);
+ void (*editing_started_cb)(GtkCellRenderer *, GtkCellEditable *,
+ const gchar *, gpointer);
+ GtkTreeIterCompareFunc sort_fn;
+} ColDef;
+
+static ColDef all_tree_view_transaction_columns[] = {
+ {COL_DATE, GNC_TREE_MODEL_TRANSACTION_COL_DATE,
+ "Date", "date", "00/00/0000xxx", -1,
+ gtvt_edited_cb, NULL, gtmt_sort_by_date},
+ {COL_NUM, GNC_TREE_MODEL_TRANSACTION_COL_NUM,
+ "Num", "num", "0000xx", -1,
+ gtvt_edited_cb, NULL, NULL},
+ {COL_DESCRIPTION, GNC_TREE_MODEL_TRANSACTION_COL_DESCRIPTION,
+ "Description", "description", "xxxxxxxxxxxxxxxxxxx", -1,
+ gtvt_edited_cb, NULL, NULL},
+ {COL_ACCOUNT, -1,
+ "Transfer", "transfer", "xxxxxxxxxxxxxxxxxxx", -1,
+ NULL /*FIXME?*/, start_edit, NULL},
+ {COL_RECN, -1,
+ "R", "recn", "x", -1,
+ gtvt_edited_cb, NULL, NULL},
+ {COL_AMOUNT, -1,
+ "Amt", "amount", "xxxxxx", -1,
+ gtvt_edited_cb, get_editable_start_editing_cb, NULL},
+ {COL_VALUE, -1,
+ "Val", "value", "xxxxxx", -1,
+ NULL, NULL, NULL},
+ {COL_CREDIT, -1,
+ "Credit", "credit", "xxxxxx", -1,
+ gtvt_edited_cb, get_editable_start_editing_cb,
+ NULL},
+ {COL_DEBIT, -1,
+ "Debit", "debit", "xxxxxx", -1,
+ gtvt_edited_cb, get_editable_start_editing_cb,
+ NULL},
+ {COL_BALANCE, -1,
+ "Balance", "balance", "xxxxxxx", -1,
+ NULL, NULL, NULL},
+ {COL_RATE, -1,
+ "Price", "price", "xxxxxx", -1,
+ gtvt_edited_cb, get_editable_start_editing_cb,
+ NULL},
+ {COL_TYPE, -1,
+ "Type", "type", "x", -1,
+ NULL, NULL, NULL},
+ {COL_NOTES, -1,
+ "Notes", "notes", "xxxxxxxxx", -1,
+ gtvt_edited_cb, get_editable_start_editing_cb, NULL},
+};
+
+struct GncTreeViewTransactionPrivate
+{
+ QofBook *book;
+ Account *anchor;
+ gnc_commodity *reg_comm;
+
+ Split *dirty_split;
+ Transaction *dirty_trans;
+
+ gchar *acct_edit_path; // remember which row's account we're editing
+ GtkCellRenderer *temp_cr;
+
+ gboolean has_rate; /* if set, the transfer dialog will never automatically pop-up */
+};
+
+
+/************************************************************/
+/* g_object required functions */
+/************************************************************/
+
+static GObjectClass *parent_class = NULL;
+
+GType
+gnc_tree_view_transaction_get_type (void)
+{
+ static GType gnc_tree_view_transaction_type = 0;
+
+ if (gnc_tree_view_transaction_type == 0) {
+ static const GTypeInfo ti = {
+ sizeof (GncTreeViewTransactionClass),
+ NULL, NULL,
+ (GClassInitFunc) gnc_tree_view_transaction_class_init,
+ NULL, NULL,
+ sizeof (GncTreeViewTransaction),
+ 0,
+ (GInstanceInitFunc) gnc_tree_view_transaction_init
+ };
+
+ gnc_tree_view_transaction_type = g_type_register_static (
+ GNC_TYPE_TREE_VIEW, "GncTreeViewTransaction", &ti, 0);
+ }
+
+ return gnc_tree_view_transaction_type;
+}
+
+static void
+gnc_tree_view_transaction_class_init (GncTreeViewTransactionClass *klass)
+{
+ GObjectClass *o_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ /* GObject signals */
+ o_class = G_OBJECT_CLASS (klass);
+ o_class->finalize = gnc_tree_view_transaction_finalize;
+ o_class->dispose = gnc_tree_view_transaction_dispose;
+}
+
+static void
+gnc_tree_view_transaction_init (GncTreeViewTransaction *view)
+{
+ view->priv = g_new0 (GncTreeViewTransactionPrivate, 1);
+}
+
+static void
+gnc_tree_view_transaction_finalize (GObject *object)
+{
+ GncTreeViewTransaction *view;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNC_IS_TREE_VIEW_TRANSACTION (object));
+
+ view = GNC_TREE_VIEW_TRANSACTION (object);
+ g_free (view->priv);
+
+ if (G_OBJECT_CLASS(parent_class)->finalize)
+ G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void
+gnc_tree_view_transaction_dispose (GObject *object)
+{
+ GncTreeViewTransaction *view;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNC_IS_TREE_VIEW_TRANSACTION (object));
+
+ view = GNC_TREE_VIEW_TRANSACTION (object);
+ gnc_tree_view_set_model(GNC_TREE_VIEW(view), NULL);
+
+ if (G_OBJECT_CLASS (parent_class)->dispose)
+ G_OBJECT_CLASS (parent_class)->dispose(object);
+}
+
+static GncTreeModelTransaction *
+get_trans_model_from_view(GncTreeViewTransaction *tv)
+{
+ GncTreeModelSort *s_model = GNC_TREE_MODEL_SORT(
+ gtk_tree_view_get_model(GTK_TREE_VIEW(tv)));
+ return GNC_TREE_MODEL_TRANSACTION(gnc_tree_model_sort_get_model(s_model));
+}
+
+static gboolean
+get_model_iter_from_view_string(GncTreeViewTransaction *tv,
+ const gchar *path_string, GtkTreeIter *iter)
+{
+ GncTreeModelSort *s_model;
+ GtkTreeIter s_iter;
+
+ s_model = GNC_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(tv)));
+ if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(s_model),
+ &s_iter, path_string)) {
+ iter = NULL;
+ return FALSE;
+ }
+ gnc_tree_model_sort_convert_iter_to_child_iter(s_model, iter, &s_iter);
+ return TRUE;
+}
+
+//FIXME: should this be needed?
+static gboolean
+get_model_iter_from_selection(GncTreeViewTransaction *tv,
+ GtkTreeSelection *sel, GtkTreeIter *iter)
+{
+ GtkTreeModel *s_model;
+ GtkTreeIter s_iter;
+
+ if (gtk_tree_selection_get_selected(sel, &s_model, &s_iter)) {
+ gnc_tree_model_sort_convert_iter_to_child_iter(
+ GNC_TREE_MODEL_SORT(s_model), iter, &s_iter);
+ return TRUE;
+ }
+ return FALSE;
+
+}
+
+static GtkTreePath *
+get_view_path_from_model_iter(GncTreeViewTransaction *view, GtkTreeIter *iter)
+{
+ GncTreeModelSort *s_model;
+ GtkTreeIter s_iter;
+
+ s_model = GNC_TREE_MODEL_SORT(gtk_tree_view_get_model(
+ GTK_TREE_VIEW(view)));
+ gnc_tree_model_sort_convert_child_iter_to_iter(s_model, &s_iter, iter);
+ return gtk_tree_model_get_path(GTK_TREE_MODEL(s_model), &s_iter);
+}
+
+#if UNUSED
+static gboolean
+get_model_iter_from_view_path(GncTreeViewTransaction *tv,
+ GtkTreePath *s_path, GtkTreeIter *iter)
+{
+ GncTreeModelSort *s_model;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+
+ s_model = GNC_TREE_MODEL_SORT(gtk_tree_view_get_model(GTK_TREE_VIEW(tv)));
+ model = gnc_tree_model_sort_get_model(s_model);
+ if (!model) {
+ iter = NULL;
+ return FALSE;
+ }
+
+ path = gnc_tree_model_sort_convert_path_to_child_path(s_model, s_path);
+ if (gtk_tree_model_get_iter(model, iter, path)) {
+ gtk_tree_path_free(path);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+gnc_tree_view_transaction_separator(GtkTreeModel *tm, GtkTreeIter *s_iter,
+ gpointer data)
+{
+ GtkTreeIter iter;
+ GncTreeModelSort *tms = GNC_TREE_MODEL_SORT(tm);
+ GtkTreeModel *tmt;
+
+ gnc_tree_model_sort_convert_iter_to_child_iter(tms, &iter, s_iter);
+ tmt = gnc_tree_model_sort_get_model(tms);
+ return FALSE;
+ //return gnc_tree_model_transaction_separator(tmt, &iter, data);
+}
+
+#endif
+
+/* poor name: really means: If this is the blank split, it may now
+ eventually graduate to a real split. The trans must already be
+ opened for editing because the split will be added to the
+ transaction if hasn't been already. */
+static void
+mark_split_dirty(GncTreeViewTransaction *tv, Split *split, Transaction *trans)
+{
+ if (split != tv->priv->dirty_split && tv->priv->dirty_split) {
+ g_print("commiting dirty split\n");
+ gnc_tree_model_transaction_commit_split(get_trans_model_from_view(tv),
+ tv->priv->dirty_split);
+ }
+
+ if (split && trans && xaccSplitGetParent(split) != trans)
+ xaccSplitSetParent(split, trans);
+
+ tv->priv->dirty_split = split;
+}
+
+/* means: open trans for editing, unless we're editing a Split (split
+ != NULL) AND split doesn't belong to the trans (because it is the
+ blank split) */
+static void
+begin_edit(GncTreeViewTransaction *tv, Split *split, Transaction *trans)
+{
+ /* explain me */
+ if (split && trans != xaccSplitGetParent(split))
+ return;
+ if (trans != tv->priv->dirty_trans) {
+ xaccTransBeginEdit(trans);
+ tv->priv->dirty_trans = trans;
+ if (!xaccTransGetCurrency(trans)) {
+ xaccTransSetCurrency(trans, tv->priv->reg_comm);
+ }
+ }
+}
+
+//FIXME
+static Split *
+get_other_split(GncTreeViewTransaction *tv, Transaction *trans)
+{
+ int i;
+ Split *split = NULL;
+ Account *anchor = tv->priv->anchor;
+
+ for (i = 0; (split = xaccTransGetSplit(trans, i)); i++) {
+ if (anchor == xaccSplitGetAccount(split))
+ return xaccSplitGetOtherSplit(split);
+ }
+ return NULL;
+}
+
+/*
+static Split *
+get_this_split(GncTreeViewTransaction *tv, Transaction *trans)
+{
+ int i;
+ Split *split = NULL;
+ Account *anchor = tv->priv->anchor;
+
+ for (i = 0; (split = xaccTransGetSplit(trans, i)); i++) {
+ if (anchor == xaccSplitGetAccount(split))
+ return split;
+ }
+ return NULL;
+}
+*/
+
+/* The returned Splits may be newly created and not yet belong to trans. */
+static gboolean
+get_split_pair(GncTreeViewTransaction *tv, Transaction *trans,
+ Split **osplit, Split **split)
+{
+ gint count = xaccTransCountSplits(trans);
+ Account *anchor = tv->priv->anchor;
+
+ if (count == 0) {
+ *split = xaccMallocSplit(tv->priv->book);
+ xaccSplitSetAccount(*split, anchor);
+ *osplit = xaccMallocSplit(tv->priv->book);
+ } else if (count == 2) {
+ int i;
+ Split *s;
+ for (i = 0; (s = xaccTransGetSplit(trans, i)); i++) {
+ if (anchor == xaccSplitGetAccount(s)) {
+ *split = s;
+ break;
+ }
+ }
+ //*split = get_this_split(tv, trans);
+ g_assert(*split);
+ *osplit = get_other_split(tv, trans);
+ g_assert(*osplit);
+ } else return FALSE;
+ return TRUE;
+}
+
+/* Returns a value for display. */
+static gnc_numeric
+get_value_for(GncTreeViewTransaction *tv, Transaction *trans,
+ Split *split, gboolean is_blank)
+{
+ gnc_commodity *currency = xaccTransGetCurrency(trans);
+ gnc_numeric total;
+
+ total = xaccSplitGetValue(split);
+
+ if (is_blank && gnc_numeric_zero_p(total)) {
+ gnc_numeric rate;
+ total = gnc_numeric_neg(xaccTransGetImbalance(trans));
+ if (!gnc_numeric_zero_p(total)) {
+ if (!xaccTransGetRateForCommodity(
+ trans, tv->priv->reg_comm, NULL, &rate))
+ return gnc_numeric_zero();
+
+ total = gnc_numeric_mul(
+ total, rate,
+ gnc_commodity_get_fraction (currency),
+ GNC_RND_ROUND);
+ }
+ } else {
+ if (!gnc_numeric_zero_p(total) &&
+ gnc_numeric_check(total) == GNC_ERROR_OK) {
+
+ /* fixme: if needs conversion? */
+ gnc_commodity *commodity = tv->priv->reg_comm;
+ if (commodity && !gnc_commodity_equiv(commodity, currency))
+ total = xaccSplitConvertAmount(split, commodity);
+ }
+ }
+ return total;
+}
+
+static gnc_numeric
+get_rate_for(GncTreeViewTransaction *tv, Transaction *trans,
+ Split *split, gboolean is_blank)
+{
+ gnc_numeric num;
+
+ num = get_value_for(tv, trans, split, is_blank);
+ num = gnc_numeric_div(
+ xaccSplitGetAmount(split), num,
+ GNC_DENOM_AUTO, GNC_HOW_RND_ROUND);
+ return num;
+}
+
+/* Instead of setting a different cellDataFunc for each column, we just
+ collect everything here and use this one func. */
+static void
+cdf(GtkTreeViewColumn *col, GtkCellRenderer *cell, GtkTreeModel *s_model,
+ GtkTreeIter *s_iter, gpointer data)
+{
+ GncTreeModelTransaction *model;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ ViewCol viewcol;
+ gboolean is_trans, is_split, is_blank;
+ gboolean editable, expanded;
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(data);
+ Account *anchor = tv->priv->anchor;
+ Split *split;
+ Transaction *trans;
+
+ gnc_numeric num;
+ //gnc_commodity *comm;
+ const gchar *s = "";
+
+ g_return_if_fail(GTK_TREE_VIEW_COLUMN(col));
+ g_return_if_fail(GTK_CELL_RENDERER(cell));
+ g_return_if_fail(GNC_TREE_MODEL_SORT(s_model));
+
+ model = get_trans_model_from_view(tv);
+ g_return_if_fail(model);
+
+ gnc_tree_model_sort_convert_iter_to_child_iter(
+ GNC_TREE_MODEL_SORT(s_model), &iter, s_iter);
+ viewcol = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell),
+ "view_column"));
+ g_return_if_fail(gnc_tree_model_transaction_get_split_and_trans(
+ GNC_TREE_MODEL_TRANSACTION(model), &iter,
+ &is_split, &is_blank, &split, &trans));
+ is_trans = !is_split;
+
+ // TODO: gconf-ize
+ if (xaccTransGetDate(trans) > time(NULL)) {
+ g_object_set(cell, "foreground", "blue", NULL);
+ g_object_set(cell, "style", PANGO_STYLE_ITALIC, NULL);
+ } else {
+ g_object_set(cell, "foreground", "black", NULL);
+ g_object_set(cell, "style", PANGO_STYLE_NORMAL, NULL);
+ }
+
+ switch (viewcol) {
+ case COL_DATE:
+ g_object_set(cell, "visible", is_trans, NULL);
+ if (is_trans) {
+ Timespec ts = {0,0};
+ xaccTransGetDatePostedTS (trans, &ts);
+ g_object_set(cell, "text", gnc_print_date(ts), NULL);
+ }
+ break;
+ case COL_TYPE: {
+ static char ss[2];
+ char type = xaccTransGetTxnType(trans);
+ if (type == TXN_TYPE_NONE)
+ type = '?';
+
+ ss[0] = type;
+ ss[1] = '\0';
+ g_object_set(cell, "text", ss, NULL);
+ }
+ break;
+ case COL_NOTES:
+ if (is_trans) {
+ s = xaccTransGetNotes(trans);
+ } else s = "";
+ g_object_set(cell, "text", s, NULL);
+ break;
+ case COL_RECN:
+ if (is_trans) {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path(s_model, s_iter);
+ if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path)) {
+ s = "";
+ } else if (anchor) {
+ s = xaccTransHasReconciledSplitsByAccount(
+ trans, anchor) ? "c":"n";
+ } else {
+ s = "";
+ }
+ gtk_tree_path_free(path);
+ } else {
+ switch (xaccSplitGetReconcile(split)) {
+ case NREC: s = "n"; break;
+ case CREC: s = "c"; break;
+ case YREC: s = "y"; break;
+ case FREC: s = "f"; break;
+ case VREC: s = "V"; break;
+ default: s = "";
+ }
+ }
+ g_object_set(cell, "text", s, NULL);
+ break;
+ case COL_AMOUNT:
+ if (is_split) {
+ gnc_numeric amt = xaccSplitGetAmount(split);
+ s = xaccPrintAmount(amt, gnc_account_print_info(
+ xaccSplitGetAccount(split), TRUE));
+ editable = TRUE;
+ } else {
+ s = "";
+ editable = FALSE;
+ }
+ g_object_set(cell, "text", s, "editable", editable, NULL);
+ break;
+ case COL_VALUE:
+ if (is_split) {
+ gnc_numeric amt = xaccSplitGetValue(split);
+ s = xaccPrintAmount(amt, gnc_commodity_print_info(
+ xaccTransGetCurrency(trans), TRUE));
+ } else s = "";
+ g_object_set(cell, "text", s, NULL);
+ break;
+ case COL_DEBIT:
+ case COL_CREDIT:
+ if (is_split) {
+ num = get_value_for(tv, trans, split, is_blank);
+ editable = TRUE;
+ } else {
+ //comm = xaccTransGetCurrency(trans);
+ if (anchor) {
+ gint count = xaccTransCountSplits(trans);
+ path = gtk_tree_model_get_path(s_model, s_iter);
+ expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path);
+ editable = !expanded && ((2 == count) || (0 == count));
+ num = xaccTransGetAccountAmount(trans, anchor);
+ } else {
+ editable = FALSE;
+ num = gnc_numeric_zero();
+ /* CHECKME: Should trans cred/debit fields in unanchored reg
+ show imbalances? I don't think so. */
+ }
+ }
+
+ if ((gnc_numeric_check(num) != GNC_ERROR_OK) ||
+ gnc_numeric_zero_p(num) ||
+ (gnc_numeric_negative_p(num) && viewcol == COL_DEBIT) ||
+ (gnc_numeric_positive_p(num) && viewcol == COL_CREDIT)) {
+ s = "";
+ } else {
+ s = xaccPrintAmount(gnc_numeric_abs(num),
+ gnc_account_print_info(anchor, TRUE));
+ //FIXME: TRUE just for debugging
+ }
+ g_object_set(cell, "text", s, "editable", editable, NULL);
+ break;
+ case COL_BALANCE:
+ if (is_trans && trans && anchor) {
+ num = xaccTransGetAccountBalance(trans, anchor);
+ s = xaccPrintAmount(num, gnc_account_print_info(
+ anchor, FALSE));
+ } else s = "";
+ g_object_set(cell, "text", s, NULL);
+ break;
+ case COL_ACCOUNT:
+ if (is_trans) {
+ gint count = xaccTransCountSplits(trans);
+ path = gtk_tree_model_get_path(s_model, s_iter);
+ expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(tv), path);
+ if (count == 0 || expanded) {
+ s = ""; /* blank-out if splits are visible */
+ } else if (2 == count) {
+ Account *acct;
+ Split *osplit;
+ osplit = get_other_split(tv, trans);
+ acct = xaccSplitGetAccount(osplit);
+ s = xaccAccountGetName(acct);
+ } else {
+ s = SPLIT_TRANS_STR;
+ }
+ editable = anchor && !expanded && ((2 == count) || (0 == count));
+ gtk_tree_path_free(path);
+ }
+ if (is_split) {
+ Account *acct = xaccSplitGetAccount(split);
+ s = xaccAccountGetName(acct);
+ editable = TRUE;
+ }
+ g_object_set(cell, "text", s, "editable", editable, NULL);
+ break;
+ case COL_RATE:
+ if (is_trans) {
+ s = "";
+ editable = FALSE;
+ } else {
+ gnc_commodity *split_com = xaccAccountGetCommodity(
+ xaccSplitGetAccount(split));
+ num = get_rate_for(tv, trans, split, is_blank);
+ if (gnc_numeric_check(num) == GNC_ERROR_OK) {
+ s = xaccPrintAmount(num, gnc_default_price_print_info());
+ editable = !gnc_numeric_zero_p(num) &&
+ !gnc_commodity_equiv(split_com, tv->priv->reg_comm);
+ } else {
+ s = "";
+ editable = FALSE;
+ }
+ }
+ g_object_set(cell, "text", s, "editable", editable, NULL);
+ break;
+ default:
+ break;
+ }
+}
+
+static gboolean
+needs_exchange_rate(GncTreeViewTransaction *tv, Transaction *trans,
+ Split *split)
+{
+ gnc_commodity *split_com, *txn_curr, *reg_com;
+
+ if (tv->priv->has_rate) return FALSE;
+
+ txn_curr = xaccTransGetCurrency(trans);
+ split_com = xaccAccountGetCommodity(xaccSplitGetAccount(split));
+ if (split_com && txn_curr && !gnc_commodity_equiv(split_com, txn_curr))
+ return TRUE;
+
+ reg_com = tv->priv->reg_comm;
+ if (reg_com && split_com && !gnc_commodity_equiv(reg_com, split_com))
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Either sets the value and amount for split and returns TRUE, or
+ does nothing and returns FALSE. */
+static gboolean
+handle_exchange_rate(GncTreeViewTransaction *tv, gnc_numeric amount,
+ Transaction *trans, Split *split)
+{
+ XferDialog *xfer;
+ gboolean rate_split_ok, rate_reg_ok;
+ gnc_numeric rate_split, rate_reg, value;
+ gnc_commodity *xfer_comm =
+ xaccAccountGetCommodity(xaccSplitGetAccount(split));
+ gnc_commodity *reg_comm = tv->priv->reg_comm;
+ gnc_commodity *trans_curr = xaccTransGetCurrency(trans);
+
+ /* Rate from trans-curr to split-comm */
+ rate_split_ok = xaccTransGetRateForCommodity(trans, xfer_comm,
+ split, &rate_split);
+ /* Rate from trans-curr to reg-comm */
+ rate_reg_ok = xaccTransGetRateForCommodity(trans, reg_comm,
+ split, &rate_reg);
+
+ if (rate_reg_ok && rate_split_ok) {
+ value = gnc_numeric_div(
+ amount, rate_reg, gnc_commodity_get_fraction(trans_curr),
+ GNC_DENOM_REDUCE);
+ amount = gnc_numeric_mul(value, rate_split, GNC_DENOM_AUTO,
+ GNC_HOW_RND_ROUND);
+ } else {
+ rate_split = gnc_numeric_create(1, 1);
+
+ //g_message("reg amt: %s", gnc_numeric_to_string(amount));
+ /* create the exchange-rate dialog */
+ xfer = gnc_xfer_dialog(NULL, NULL); //FIXME: get parent window
+ gnc_xfer_dialog_is_exchange_dialog(xfer, &rate_split);
+
+ /* fill in the dialog entries */
+ gnc_xfer_dialog_set_description(xfer, xaccTransGetDescription(trans));
+ gnc_xfer_dialog_set_memo(xfer, xaccSplitGetMemo(split));
+ gnc_xfer_dialog_set_num(xfer, xaccTransGetNum(trans));
+ gnc_xfer_dialog_set_date(
+ xfer, timespecToTime_t(xaccTransRetDatePostedTS(trans)));
+
+ value = amount;
+ if (gnc_xfer_dialog_run_exchange_dialog(
+ xfer, &rate_split, &value, reg_comm, trans, xfer_comm))
+ return FALSE;
+ amount = gnc_numeric_mul(value, rate_split,
+ GNC_DENOM_AUTO, GNC_HOW_RND_ROUND);
+ }
+ xaccSplitSetAmount(split, amount);
+ xaccSplitSetValue(split, value);
+ g_message("split amt=%s; split val=%s", gnc_numeric_to_string(amount),
+ gnc_numeric_to_string(value));
+ return TRUE;
+}
+
+static void
+set_value_for(GncTreeViewTransaction *tv, Transaction *trans, Split *split,
+ gnc_numeric input)
+{
+ Account *anchor = tv->priv->anchor;
+ Account *acct = xaccSplitGetAccount(split);
+ gnc_commodity *currency = xaccTransGetCurrency(trans);
+ gnc_numeric value, amount, rate;
+
+ if (gnc_numeric_zero_p(input)) {
+ xaccSplitSetValue(split, input);
+ xaccSplitSetAmount(split, input);
+ return;
+ }
+
+ if (needs_exchange_rate(tv, trans, split)) {
+ if (handle_exchange_rate(tv, input, trans, split)) {
+ ;
+ }
+ return;
+ }
+
+ /* Context determines the interpretation of the input. If the
+ treeview is anchored to an account then the input is
+ interpreted as being in the Account's commodity. Otherwise,
+ it's interpreted as being in the Transaction's currency. */
+ if (anchor) {
+ gnc_commodity *reg_com = tv->priv->reg_comm;
+ gnc_commodity *split_com = xaccAccountGetCommodity(acct);
+ /* Convert from the anchor account's commodity to
+ trans currency */
+ //xaccSplitSetAmount(split, input);
+ if (gnc_commodity_equiv(currency, reg_com))
+ value = input;
+ else {
+ if (!xaccTransGetRateForCommodity(trans, reg_com, NULL, &rate))
+ return;
+ //rate = xaccTransGetAccountConvRate(trans, anchor);
+ if (gnc_numeric_zero_p(rate)) {
+ // FIXME: probably wrong.
+ xaccTransSetCurrency(trans, reg_com);
+ value = input;
+ } else
+ value = gnc_numeric_div(
+ input, rate,
+ GNC_DENOM_AUTO, //?
+ //gnc_commodity_get_fraction(currency),
+ GNC_HOW_RND_ROUND);
+ }
+ xaccSplitSetValue(split, value);
+ //return;
+ if (gnc_commodity_equiv(split_com, reg_com))
+ amount = input;
+ else {
+ rate = xaccTransGetAccountConvRate(trans, acct);
+ amount = gnc_numeric_mul(
+ value, rate,
+ xaccAccountGetCommoditySCU(acct), GNC_RND_ROUND);
+ }
+ xaccSplitSetAmount(split, amount);
+ } else {
+ //FIXME: untested; assumes entry in the trans currency
+ //gnc_commodity *split_com = xaccAccountGetCommodity(acct);
+ value = input;
+ xaccSplitSetValue(split, value);
+ //g_assert(split_com == currency);
+ //FIXME: obsolete
+ /* For a split belonging to another account */
+ rate = xaccTransGetAccountConvRate(trans, acct);
+ amount = gnc_numeric_mul(
+ value, rate,
+ xaccAccountGetCommoditySCU(acct), GNC_RND_ROUND);
+ if (gnc_numeric_check(amount) == GNC_ERROR_OK) {
+ xaccSplitSetAmount(split, amount);
+ }
+ }
+}
+
+static void
+set_amount_for(GncTreeViewTransaction *tv, Transaction *trans, Split *split,
+ gnc_numeric input)
+{
+ Account *acct = xaccSplitGetAccount(split);
+ gnc_commodity *split_com = xaccAccountGetCommodity(acct);
+ gnc_commodity *currency = xaccTransGetCurrency(trans);
+
+ xaccSplitSetAmount(split, input);
+ if (gnc_commodity_equiv(currency, split_com))
+ xaccSplitSetValue(split, input);
+
+ return;
+}
+
+static void
+set_rate_for(GncTreeViewTransaction *tv, Transaction *trans, Split *split,
+ gnc_numeric input, gboolean is_blank)
+{
+ gnc_commodity *split_comm;
+
+ gnc_numeric old_rate = get_rate_for(tv, trans, split, is_blank);
+ gnc_numeric factor = gnc_numeric_div(input, old_rate, GNC_DENOM_AUTO,
+ GNC_HOW_RND_ROUND);
+ split_comm = xaccAccountGetCommodity(xaccSplitGetAccount(split));
+ xaccTransAdjustRateForCommodity(trans, split_comm, factor);
+
+#if JUNK
+ reg_comm = tv->priv->reg_comm;
+ if (xaccTransGetRateForCommodity(
+ trans, reg_comm, split, ®_rate)) {
+ input = gnc_numeric_div(input, reg_rate,
+ GNC_DENOM_AUTO, GNC_HOW_RND_ROUND);
+ /* input is now the rate from the transaction currency to the
+ split_com */
+ }
+
+ if (gnc_numeric_zero_p(val) && gnc_numeric_zero_p(amt)) {
+ gnc_numeric one = gnc_numeric_create(1, 1);
+ xaccSplitSetAmount(split, one);
+ amt = one;
+ }
+
+ if (gnc_numeric_zero_p(val)) {
+ val = gnc_numeric_div(input, amt,
+ GNC_DENOM_AUTO, GNC_HOW_RND_ROUND);
+ } else {
+ amt = gnc_numeric_mul(input, val,
+ GNC_DENOM_AUTO, GNC_HOW_RND_ROUND);
+ }
+
+ amt = gnc_numeric_mul(get_value_for(tv, trans, split, FALSE /*FIXME*/),
+ input,
+ GNC_DENOM_AUTO, GNC_HOW_RND_ROUND);
+ set_amount_for(tv, trans, split, amt);
+
+ //xaccSplitSetValue(split, val);
+ //xaccSplitSetAmount(split, amt);
+
+#endif
+}
+
+/* Connected to "edited" from cellrenderer. For reference, see
+ split-register-model-save.c */
+static void
+gtvt_edited_cb(GtkCellRendererText *cell, const gchar *path_string,
+ const gchar *new_text, gpointer data)
+{
+ GtkTreeIter iter;
+ Split *split;
+ Transaction *trans;
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(data);
+ ViewCol viewcol;
+ gboolean is_trans, is_split, is_blank;
+ GncTreeModelTransaction *model;
+ char *error_loc = NULL;
+ Account *anchor = tv->priv->anchor;
+
+ g_return_if_fail(get_model_iter_from_view_string(tv, path_string, &iter));
+
+ viewcol = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell),
+ "view_column"));
+ model = get_trans_model_from_view(tv);
+ g_return_if_fail(model);
+
+ gnc_tree_model_transaction_get_split_and_trans (
+ model, &iter, &is_split, &is_blank, &split, &trans);
+ is_trans = !is_split;
+
+ switch (viewcol) {
+ case COL_DATE:
+ if (is_trans) {
+ //TimeSpec ts;
+ //xaccTransSetDatePostedTS (trans, &ts);
+ }
+ break;
+ case COL_NUM: // aka "ACTION"
+ begin_edit(tv, split, trans);
+ if (is_split) {
+ xaccSplitSetAction(split, new_text);
+ }
+ if (is_trans) {
+ xaccTransSetNum(trans, new_text);
+ /* TODO: If the next number is visible in the blank trans,
+ and this is not the blank split, and this trans is
+ using that next number, we may want to increment number
+ of the blank trans. */
+ }
+ break;
+ case COL_DESCRIPTION: // aka "MEMO"
+ begin_edit(tv, split, trans);
+ if (is_split)
+ xaccSplitSetMemo(split, new_text);
+ if (is_trans)
+ xaccTransSetDescription(trans, new_text);
+ break;
+ case COL_NOTES:
+ if (is_trans)
+ begin_edit(tv, split, trans);
+ xaccTransSetNotes(trans, new_text);
+ break;
+ case COL_AMOUNT:
+ case COL_RATE:
+ case COL_CREDIT:
+ case COL_DEBIT: {
+ Account *acct;
+ gnc_numeric input;
+ Split *split2 = NULL;
+
+ if (!gnc_exp_parser_parse (new_text, &input, &error_loc))
+ break;
+
+ if (is_trans && anchor) {
+ if (!get_split_pair(tv, trans, &split2, &split)) {
+ PERR("couldn't get split pair");
+ break;
+ }
+ }
+ begin_edit(tv, NULL, trans); // open trans even if split not a child
+ mark_split_dirty(tv, split, trans);
+
+ acct = xaccSplitGetAccount(split);
+ if (!acct) {
+ if (anchor) {
+ g_assert_not_reached();
+ xaccSplitSetAccount(split, anchor);
+ acct = xaccSplitGetAccount(split);
+ } else
+ break; //Well, what else is there to do?
+ }
+
+ if (viewcol == COL_CREDIT)
+ input = gnc_numeric_neg(input);
+
+ if (viewcol == COL_AMOUNT) {
+ set_amount_for(tv, trans, split, input);
+ break;
+ }
+
+ if (viewcol == COL_RATE) {
+ set_rate_for(tv, trans, split, input, is_blank);
+ break;
+ }
+
+ set_value_for(tv, trans, split, input);
+ if (split2) {
+ xaccSplitSetParent(split2, trans);
+ set_value_for(tv, trans, split2, gnc_numeric_neg(input));
+ }
+ }
+ break;
+ case COL_RECN:
+ if (is_split) {
+ begin_edit(tv, split, trans);
+ xaccSplitSetReconcile(split, *new_text);
+ }
+ break;
+ default:
+ //g_assert_not_reached();
+ break;
+ }
+}
+
+/* Returns TRUE if dialog was cancelled. Does nothing is 'new_trans'
+ is the dirty trans. */
+static gboolean
+transaction_changed_confirm(GncTreeViewTransaction *tv,
+ Transaction *new_trans)
+{
+ GtkWidget *dialog;
+ gint response;
+ const char *title = _("Save the changed transaction?");
+ const char *message = _(
+ "The current transaction has changed. Would you like to "
+ "record the changes, or discard the changes?");
+
+ if (!tv->priv->dirty_trans || tv->priv->dirty_trans == new_trans)
+ return FALSE;
+
+ g_print("commiting dirty trans\n");
+ dialog = gtk_message_dialog_new(NULL, /* FIXME: parent */
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ "%s", message);
+ gtk_dialog_add_buttons(
+ GTK_DIALOG(dialog),_("_Discard Changes"), GTK_RESPONSE_REJECT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ _("_Record Changes"), GTK_RESPONSE_ACCEPT, NULL);
+ response = gnc_dialog_run(GTK_DIALOG(dialog), "transaction_changed");
+ gtk_widget_destroy(dialog);
+
+ switch (response) {
+ case GTK_RESPONSE_ACCEPT:
+ xaccTransCommitEdit(tv->priv->dirty_trans);
+ tv->priv->dirty_trans = NULL;
+ break;
+
+ case GTK_RESPONSE_REJECT:
+ xaccTransRollbackEdit(tv->priv->dirty_trans);
+ tv->priv->dirty_trans = NULL;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ default:
+ return TRUE; //FIXME
+ }
+
+ return FALSE;
+}
+
+static void
+//motion_cb(GtkTreeView *tv, gpointer data)
+motion_cb(GtkTreeSelection *sel, gpointer data)
+{
+ //GtkTreeView *tv = _tv;
+ //gboolean is_split; //, is_expanded;
+ // GtkTreePath *s_path;
+ GtkTreeIter iter;
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(data);
+ GncTreeModelTransaction *model; // = GNC_TREE_MODEL_TRANSACTION(data);
+ Split *split;
+ Transaction *trans = NULL;
+
+ model = get_trans_model_from_view(tv);
+ mark_split_dirty(tv, NULL, NULL);
+
+ if (get_model_iter_from_selection(tv, sel, &iter)) {
+ gboolean is_split, is_blank;
+ gnc_tree_model_transaction_get_split_and_trans (
+ model, &iter, &is_split, &is_blank, &split, &trans);
+
+ if (transaction_changed_confirm(tv, trans)) {
+ /* Restore cursor position */
+ if (gnc_tree_model_transaction_get_iter_from_trans(
+ model, tv->priv->dirty_trans, &iter)) {
+ GtkTreePath *path;
+ path = get_view_path_from_model_iter(tv, &iter);
+ gtk_tree_selection_select_path(sel, path);
+ }
+ } else
+ gnc_tree_model_transaction_set_blank_split_parent(model, trans);
+ }
+}
+
+/* Note: These next three functions are for handling the editable
+ account combo box cell. There are at least two ways of doing this:
+ We could just catch the "edited" signal offered by
+ gtk_cell_renderer_text, but then we'd get only the account name
+ string for the selected account, and we'd have to lookup the actual
+ account object. Or, we can jump through these hoops below to get
+ access to the GncTreeModelAccount underlying the combobox. TODO:
+ maybe the paths that are common with the gtvt_edited_cb can be
+ factored. */
+
+/* Note2: I'm not really sure these 3 functions are the way to go at
+ all. In particular, I imagine that if string completion were added
+ we'd maybe want to do this differently. */
+/* Connected to "editing-done" from the ComboBox. */
+static void
+editing_done_cb(GtkCellEditable *ce, gpointer user_data)
+{
+ GtkComboBox *cbox = GTK_COMBO_BOX(ce);
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(user_data);
+ GtkTreeIter f_iter, a_iter;
+
+ g_print("done edit");
+ if (tv->priv->acct_edit_path &&
+ gtk_combo_box_get_active_iter(cbox, &f_iter)) {
+ GtkTreeModelFilter *f_model;
+ GncTreeModelAccount *acc_model;
+ GncTreeModelTransaction *model;
+ Account *old_acct = NULL, *new_acct;
+ GtkTreeIter tv_iter;
+ gboolean is_split, is_blank;
+ Split *split = NULL, *split2 = NULL;
+ Transaction *trans;
+
+ f_model = GTK_TREE_MODEL_FILTER(gtk_combo_box_get_model(cbox));
+ gtk_tree_model_filter_convert_iter_to_child_iter(f_model, &a_iter,
+ &f_iter);
+ acc_model = GNC_TREE_MODEL_ACCOUNT(
+ gtk_tree_model_filter_get_model(f_model));
+ new_acct = gnc_tree_model_account_get_account(acc_model, &a_iter);
+
+ g_return_if_fail(get_model_iter_from_view_string(
+ tv, tv->priv->acct_edit_path, &tv_iter));
+
+ model = get_trans_model_from_view(tv);
+ gnc_tree_model_transaction_get_split_and_trans (
+ model, &tv_iter, &is_split, &is_blank, &split, &trans);
+
+ if (!is_split) {
+ if (!get_split_pair(tv, trans, &split, &split2))
+ PERR("couldn't get split pair");
+ }
+ old_acct = xaccSplitGetAccount(split);
+
+ if (old_acct != new_acct) {
+ gnc_numeric input, reg_rate;
+ gnc_commodity *reg_comm = tv->priv->reg_comm;
+
+ PINFO("setting %s to %s", xaccAccountGetName(old_acct),
+ xaccAccountGetName(new_acct));
+ input = get_value_for(tv, trans, split, is_blank);
+
+ /* Important: It's possible that this split contains the
+ only representation of the exchange rate from the
+ transaction currency into the register commodity. If
+ we allowed the loss of this info, we wouldn't know what
+ to display for any split. */
+ if (xaccTransGetRateForCommodity(
+ trans, reg_comm, split, ®_rate)) {
+ begin_edit(tv, NULL, trans);
+ mark_split_dirty(tv, split, trans);
+ xaccSplitSetAccount(split, new_acct);
+ set_value_for(tv, trans, split, input);
+ if (split2) {
+ xaccSplitSetParent(split2, trans);
+ set_value_for(tv, trans, split2, gnc_numeric_neg(input));
+ }
+ } else {
+ // CHECKME: false alarm when new account has same comm as old?
+ g_print("Can't change anchoring split to account of "
+ "different commodity");
+ }
+ }
+ }
+}
+
+static void
+remove_edit(GtkCellEditable *ce, gpointer user_data)
+{
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(user_data);
+
+ g_print("remove edit\n");
+ g_object_set_data(G_OBJECT(tv->priv->temp_cr), "cell-editable", NULL);
+ tv->priv->temp_cr = NULL;
+ g_free(tv->priv->acct_edit_path);
+ tv->priv->acct_edit_path = NULL;
+}
+
+/* Explain: GtkEntry has a cursor that blinks upon
+ g_timeout_dispatch(). It complains if it blinks after the GtkEntry
+ loses focus. So, we can't pop up any dialogs while the blinking
+ cursor is around. The solution is to force the editing to be
+ finished before raising the dialog. That finalizes the
+ gtkcelleditable. */
+static void
+finish_edit(GtkTreeViewColumn *col)
+{
+ GtkCellRenderer *cr;
+ GtkCellEditable *ce;
+
+ if (!col) return;
+ cr = gnc_tree_view_column_get_renderer(col);
+ if ((ce = GTK_CELL_EDITABLE(g_object_get_data(G_OBJECT(cr),
+ "cell-editable")))) {
+ gtk_cell_editable_editing_done(ce);
+ }
+}
+
+static void
+get_editable_start_editing_cb(GtkCellRenderer *cr, GtkCellEditable *editable,
+ const gchar *path_string, gpointer user_data)
+{
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(user_data);
+
+ g_print("start_edit");
+ g_object_set_data(G_OBJECT(cr), "cell-editable", editable);
+ tv->priv->temp_cr = cr;
+ g_signal_connect(G_OBJECT(editable), "remove-widget",
+ (GCallback) remove_edit, tv);
+}
+
+static void
+start_edit(GtkCellRenderer *cr, GtkCellEditable *editable,
+ const gchar *path_string, gpointer user_data)
+{
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(user_data);
+
+ get_editable_start_editing_cb(cr, editable, path_string, user_data);
+ g_signal_connect(G_OBJECT(editable), "editing-done",
+ (GCallback) editing_done_cb, tv);
+ tv->priv->acct_edit_path = g_strdup(path_string); //FIXME: use rowref?
+ return;
+}
+
+static void
+delete_row_cb(GtkMenuItem *menuitem, gpointer *userdata)
+{
+ GncTreeViewTransaction *tv = GNC_TREE_VIEW_TRANSACTION(userdata);
+ GncTreeModelTransaction *model;
+ GtkTreeIter iter;
+ GtkTreeSelection *sel;
+ Transaction *trans;
+ Split *split;
+
+ model = get_trans_model_from_view(tv);
+ g_return_if_fail(model);
+
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
+
+ if (get_model_iter_from_selection(tv, sel, &iter)) {
+ gboolean is_split, is_blank;
+ gnc_tree_model_transaction_get_split_and_trans(
+ model, &iter, &is_split, &is_blank, &split, &trans);
+ begin_edit(tv, split, trans);
+ if (is_split) {
+ gnc_commodity *reg_comm = tv->priv->reg_comm;
+ gnc_numeric reg_rate;
+
+ if (xaccTransGetRateForCommodity(
+ trans, reg_comm, split, ®_rate)) {
+ mark_split_dirty(tv, NULL, NULL); // unnecessary?
+ xaccSplitDestroy(split);
+ } else {
+ // FIXME: dialog
+ g_print("Can't remove split that anchors to reg_comm");
+ }
+ } else {
+ xaccTransDestroy(trans);
+ g_assert(tv->priv->dirty_trans == trans);
+ xaccTransCommitEdit(trans);
+ tv->priv->dirty_trans = NULL;
+ }
+ }
+}
+
+static void
+do_popup_menu (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
+{
+ GtkWidget *menu, *menuitem;
+
+ menu = gtk_menu_new ();
+ menuitem = gtk_menu_item_new_with_label(_("delete"));
+
+ g_signal_connect(menuitem, "activate", (GCallback) delete_row_cb,
+ treeview);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ gtk_menu_set_title(GTK_MENU(menu), "title"); //FIXME
+
+ gtk_widget_show_all(menu);
+
+ /* gdk_event_get_time() accepts a NULL argument */
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ event ? event->button : 0,
+ gdk_event_get_time((GdkEvent*)event));
+}
+
+static gboolean
+gtvt_button_press_event_handler(GtkWidget *treeview, GdkEventButton *event,
+ gpointer userdata)
+{
+ /* Ignore double-clicks and triple-clicks */
+ if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
+ GtkTreeSelection *sel;
+
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+
+ if (gtk_tree_selection_count_selected_rows(sel) <= 1) {
+ GtkTreePath *path;
+
+ /* Get tree path for row that was clicked */
+ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
+ (gint) event->x,
+ (gint) event->y,
+ &path, NULL, NULL, NULL)) {
+ gtk_tree_selection_unselect_all(sel);
+ gtk_tree_selection_select_path(sel, path);
+ gtk_tree_path_free(path);
+ }
+ }
+ do_popup_menu(treeview, event, userdata);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtvt_popup_menu_handler(GtkWidget *widget, gpointer userdata)
+{
+ do_popup_menu(widget, NULL, userdata);
+ return TRUE;
+}
+
+/* Creates a treeview with the list of fields */
+static GncTreeViewTransaction *
+gnc_tree_view_transaction_set_cols(GncTreeViewTransaction *tv,
+ const ViewCol col_list[])
+{
+ int i = 0;
+
+ while (col_list && col_list[i] != -1) {
+ GtkCellRenderer *cr;
+ GtkTreeViewColumn *col;
+ ColDef def;
+ int j, ncol = G_N_ELEMENTS(all_tree_view_transaction_columns);
+
+ for (j = 0; j < ncol; j++) {
+ if (col_list[i] == all_tree_view_transaction_columns[j].viewcol) {
+ def = all_tree_view_transaction_columns[j];
+ break;
+ }
+ }
+ if (j == ncol) {
+ PERR("Failed to find column definition.");
+ i++;
+ continue;
+ }
+
+ if (col_list[i] == COL_ACCOUNT) {
+ //FIXME: we need to store a ref to the f_model?
+ GtkTreeModel *acc_model, *f_model;
+ GtkTreePath *virtual_root_path = NULL;
+ AccountGroup *grp;
+
+ grp = gnc_book_get_group(tv->priv->book);
+
+ acc_model = gnc_tree_model_account_new (grp);
+ virtual_root_path = gtk_tree_path_new_first ();
+ f_model = gtk_tree_model_filter_new (acc_model, virtual_root_path);
+ g_object_unref(G_OBJECT(acc_model));
+ gtk_tree_path_free(virtual_root_path);
+ col = gnc_tree_view_add_combo_column (
+ GNC_TREE_VIEW(tv), def.title, def.pref_name, def.sizer,
+ def.modelcol, def.visibility_model_col,
+ f_model, GNC_TREE_MODEL_ACCOUNT_COL_NAME,
+ def.sort_fn);
+ g_object_unref(G_OBJECT(f_model));
+ } else {
+ col = gnc_tree_view_add_text_column (
+ GNC_TREE_VIEW(tv), def.title, def.pref_name, NULL, def.sizer,
+ def.modelcol, def.visibility_model_col, def.sort_fn);
+ }
+
+ g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
+ cr = gnc_tree_view_column_get_renderer(col);
+ if (def.editing_started_cb) {
+ g_signal_connect(G_OBJECT(cr), "editing-started",
+ (GCallback) def.editing_started_cb, tv);
+ }
+
+ // This can die when prefs are used.
+ g_object_set(G_OBJECT(col), "resizable", TRUE, NULL);
+
+ if (def.edited_cb) {
+ g_object_set(G_OBJECT(cr), "editable", TRUE, NULL);
+ g_signal_connect(G_OBJECT(cr), "edited",
+ (GCallback) def.edited_cb, tv);
+ }
+ g_object_set_data(G_OBJECT(cr), "view_column",
+ GINT_TO_POINTER(def.viewcol));
+ gtk_tree_view_column_set_cell_data_func(
+ col, cr, cdf, tv, NULL);
+ i++;
+ }
+
+ gnc_tree_view_configure_columns(GNC_TREE_VIEW(tv));
+
+ //g_signal_connect(tv, "cursor-changed", G_CALLBACK(motion_cb), NULL);
+ g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)),
+ "changed", G_CALLBACK(motion_cb), tv);
+
+ //gtk_tree_selection_set_mode(gtk_tree_view_get_selection(tv),
+ // GTK_SELECTION_BROWSE);
+ g_signal_connect(G_OBJECT(tv), "popup-menu",
+ G_CALLBACK(gtvt_popup_menu_handler), NULL);
+ g_signal_connect(G_OBJECT(tv), "button-press-event",
+ G_CALLBACK(gtvt_button_press_event_handler), NULL);
+ g_signal_connect_after(G_OBJECT(tv), "key-press-event",
+ G_CALLBACK(gtvt_key_press_cb), NULL);
+ return tv;
+}
+
+static ViewCol col_list[] = {
+ COL_DATE, COL_NUM, COL_DESCRIPTION, COL_ACCOUNT, COL_RECN,
+ COL_AMOUNT, COL_VALUE, COL_RATE, COL_CREDIT, COL_DEBIT,
+ COL_BALANCE, -1};
+
+GncTreeViewTransaction *
+gnc_tree_view_transaction_new_with_model(GncTreeModelTransaction *model)
+{
+ GtkTreeModel *s_model;
+ GncTreeViewTransaction *tv;
+
+ tv = g_object_new(gnc_tree_view_transaction_get_type(), NULL);
+
+ s_model = gnc_tree_model_sort_new_with_model(GTK_TREE_MODEL(model));
+ //g_object_unref(G_OBJECT(model));
+ gnc_tree_view_set_model(GNC_TREE_VIEW(tv), s_model);
+ g_object_unref(G_OBJECT(s_model));
+
+ tv->priv->book = gnc_tree_model_transaction_get_book(model);
+ tv->priv->anchor = gnc_tree_model_transaction_get_anchor(model);
+ tv->priv->reg_comm = xaccAccountGetCommodity(tv->priv->anchor);
+ tv->priv->has_rate = TRUE; //?
+
+ gnc_tree_view_transaction_set_cols(tv, col_list);
+ return tv;
+}
+
+/* CONTROL */
+static gboolean
+gtvt_key_press_cb(GtkWidget *treeview, GdkEventKey *event, gpointer userdata)
+{
+ GtkTreeView *tv = GTK_TREE_VIEW(treeview);
+ GtkTreeViewColumn *col;
+ GtkTreePath *path = NULL;
+
+ if (event->type != GDK_KEY_PRESS) return TRUE;
+
+ switch (event->keyval) {
+ case GDK_Tab:
+ case GDK_ISO_Left_Tab:
+ case GDK_KP_Tab:
+ case GDK_Return:
+ case GDK_KP_Enter:
+ gtk_tree_view_get_cursor(tv, &path, &col);
+ if (!path) return TRUE;
+ finish_edit(col);
+ break;
+ default: return TRUE;
+ }
+ gnc_tree_view_keynav(GNC_TREE_VIEW(tv), &col, path, event);
+
+ if (!path || !gnc_tree_view_path_is_valid(GNC_TREE_VIEW(tv), path)) {
+ /* no need to restore cursor because we won't move. */
+ transaction_changed_confirm(GNC_TREE_VIEW_TRANSACTION(tv), NULL);
+ } else
+ gtk_tree_view_set_cursor(tv, path, col, TRUE);
+
+ return TRUE;
+}
+
+Account *
+gnc_tree_view_transaction_get_anchor(GncTreeViewTransaction *tv)
+{
+ return tv->priv->anchor; /* cached from model */
+}
Added: gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.h
===================================================================
--- gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.h 2006-05-29 20:59:33 UTC (rev 14248)
+++ gnucash/branches/register-rewrite/src/gnome-utils/gnc-tree-view-transaction.h 2006-05-29 21:18:25 UTC (rev 14249)
@@ -0,0 +1,76 @@
+/********************************************************************\
+ * gnc-tree-view-transaction.h -- GtkTreeView implementation to *
+ * display Transactions in a GtkTreeView. *
+ * Copyright (C) 2006 Chris Shoemaker <c.shoemaker at cox.net> *
+ * *
+ * 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 2 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, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+\********************************************************************/
+
+#ifndef __GNC_TREE_VIEW_TRANSACTION_H
+#define __GNC_TREE_VIEW_TRANSACTION_H
+
+#include <gtk/gtktreemodel.h>
+#include <gtk/gtktreeview.h>
+#include "gnc-tree-view.h"
+
+#include "Group.h"
+#include "gnc-ui-util.h"
+#include "gnc-tree-model-transaction.h"
+
+G_BEGIN_DECLS
+
+/* type macros */
+#define GNC_TYPE_TREE_VIEW_TRANSACTION (gnc_tree_view_transaction_get_type ())
+#define GNC_TREE_VIEW_TRANSACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNC_TYPE_TREE_VIEW_TRANSACTION, GncTreeViewTransaction))
+#define GNC_TREE_VIEW_TRANSACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNC_TYPE_TREE_VIEW_TRANSACTION, GncTreeViewTransactionClass))
+#define GNC_IS_TREE_VIEW_TRANSACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNC_TYPE_TREE_VIEW_TRANSACTION))
+#define GNC_IS_TREE_VIEW_TRANSACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNC_TYPE_TREE_VIEW_TRANSACTION))
+#define GNC_TREE_VIEW_TRANSACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_TREE_VIEW_TRANSACTION, GncTreeViewTransactionClass))
+
+/* typedefs & structures */
+typedef struct GncTreeViewTransactionPrivate GncTreeViewTransactionPrivate;
+typedef struct {
+ GncTreeView parent;
+
+ GncTreeViewTransactionPrivate *priv;
+
+ int stamp;
+} GncTreeViewTransaction;
+
+typedef struct {
+ GncTreeViewClass parent;
+} GncTreeViewTransactionClass;
+
+
+
+/* Standard g_object type */
+GType gnc_tree_view_transaction_get_type (void);
+
+GncTreeViewTransaction *
+gnc_tree_view_transaction_new_with_model(GncTreeModelTransaction *model);
+
+Account *
+gnc_tree_view_transaction_get_anchor(GncTreeViewTransaction *tv);
+
+/** @} */
+
+
+G_END_DECLS
+
+#endif /* __GNC_TREE_VIEW_TRANSACTION_H */
More information about the gnucash-changes
mailing list