[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 (&gtmits_buffer_key);
+    if (string == NULL) {
+        string = malloc(ITER_STRING_LEN + 1);
+        g_static_private_set (&gtmits_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, &reg_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, &reg_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, &reg_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