/********************************************************************\
 * gnc-query-view.c -- A query display view.                        *
 * Copyright (C) 2003 Derek Atkins <derek@ihtfp.com>                *
 * Copyright (C) 2012 Robert Fewell                                 *
 *                                                                  *
 * 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@gnu.org                   *
\********************************************************************/

#include <config.h>

#include <gtk/gtk.h>

#include "dialog-utils.h"
#include "gnc-ui-util.h"
#include "qof.h"
#include "gnc-component-manager.h"
#include "gnc-query-view.h"
#include "search-param.h"

/* This static indicates the debugging module that this .o belongs to.  */
static QofLogModule log_module = GNC_MOD_GUI;

/* Signal codes */
enum
{
    COLUMN_TOGGLED,
    ROW_SELECTED,
    DOUBLE_CLICK_ENTRY,
    LAST_SIGNAL
};

typedef struct _GNCQueryViewPrivate GNCQueryViewPrivate;

struct _GNCQueryViewPrivate
{
    const QofParam *get_guid;
    gint        component_id;
};

G_DEFINE_TYPE_WITH_PRIVATE(GNCQueryView, gnc_query_view, GTK_TYPE_TREE_VIEW)

#define GNC_QUERY_VIEW_GET_PRIVATE(o)  \
   ((GNCQueryViewPrivate*)gnc_query_view_get_instance_private((GNCQueryView*)o))

/** Static Globals ****************************************************/
static guint query_view_signals[LAST_SIGNAL] = {0};

/** Static function declarations **************************************/
static void gnc_query_view_init_view (GNCQueryView *qview);
static void gnc_query_view_select_row_cb (GtkTreeSelection *selection,
                                          gpointer user_data);
static void gnc_query_view_toggled_cb (GtkCellRendererToggle *cell_renderer,
                                       gchar *path, gpointer user_data);
static void gnc_query_view_double_click_cb (GtkTreeView *tree_view,
                                             GtkTreePath       *path,
                                             GtkTreeViewColumn *column,
                                             gpointer           user_data);

static void gnc_query_view_destroy (GtkWidget *widget);
static void gnc_query_view_fill (GNCQueryView *qview);
static void gnc_query_view_set_query_sort (GNCQueryView *qview,
                                           gboolean new_column);


/********************************************************************\
 * gnc_query_view_new                                               *
 *   creates the query view                                         *
 *                                                                  *
 * Args: param_list - the list of params                            *
 *       query      - the query to use to find entries              *
 * Returns: the query view widget, or NULL if there was a problem.  *
\********************************************************************/
void
gnc_query_view_construct (GNCQueryView *qview, GList *param_list, Query *query)
{
    GNCQueryViewPrivate *priv;

    g_return_if_fail (qview);
    g_return_if_fail (param_list);
    g_return_if_fail (query);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    /* more configuration */
    qview->query = qof_query_copy (query);
    qview->column_params = param_list;

    /* cache the function to get the guid of this query type */
    priv = GNC_QUERY_VIEW_GET_PRIVATE(qview);
    priv->get_guid = qof_class_get_parameter (qof_query_get_search_for (query),
                                              QOF_PARAM_GUID);

    /* Initialize the Tree View */
    gnc_query_view_init_view (qview);

    /* Set initial sort order */
    gnc_query_view_set_query_sort (qview, TRUE);
}

GtkWidget *
gnc_query_view_new (GList *param_list, Query *query)
{
    GNCQueryView  *qview;
    GtkListStore  *liststore;
    GList         *node;
    gint           columns, i;
    gsize          array_size;
    GType         *types;

    g_return_val_if_fail (param_list, NULL);
    g_return_val_if_fail (query, NULL);

    /* Add 1 to param_list length for extra pointer column */
    columns = g_list_length (param_list) + 1;
    qview = GNC_QUERY_VIEW(g_object_new (gnc_query_view_get_type (), NULL));

    array_size = sizeof(GType) * columns;
    types = g_slice_alloc (array_size);

    types[0] = G_TYPE_POINTER;

    /* Get the types for the list store */
    for (i = 0, node = param_list; node; node = node->next, i++)
    {
        GNCSearchParamSimple *param = node->data;
        const char *type;

        g_assert (GNC_IS_SEARCH_PARAM_SIMPLE(param));

        type = gnc_search_param_get_param_type ((GNCSearchParam *) param);

        if (g_strcmp0 (type, QOF_TYPE_BOOLEAN) == 0)
            types[i+1] = G_TYPE_BOOLEAN;
        else
            types[i+1] = G_TYPE_STRING;
    }

    /* Create the list store and add to treeview */
    liststore = gtk_list_store_newv (columns, types );
    gtk_tree_view_set_model (GTK_TREE_VIEW(qview), GTK_TREE_MODEL(liststore));
    g_object_unref (liststore);

    /* Free array */
    g_slice_free1 (array_size, types);

    gnc_query_view_construct (qview, param_list, query);

    return GTK_WIDGET(qview);
}

void gnc_query_view_reset_query (GNCQueryView *qview, Query *query)
{
    g_return_if_fail (qview);
    g_return_if_fail (query);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    qof_query_destroy (qview->query);
    qview->query = qof_query_copy (query);

    gnc_query_view_set_query_sort (qview, TRUE);
}

static void
gnc_query_view_refresh_handler (GHashTable *changes, gpointer user_data)
{
    GNCQueryView *qview = (GNCQueryView *)user_data;
    g_return_if_fail (qview);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    gnc_query_view_set_query_sort (qview, TRUE);
}

static void
gnc_query_view_init (GNCQueryView *qview)
{
    GNCQueryViewPrivate *priv;

    // Set the name for this widget so it can be easily manipulated with css
    gtk_widget_set_name (GTK_WIDGET(qview), "gnc-id-query-view-view");

    qview->query = NULL;

    qview->num_columns = 0;
    qview->column_params = NULL;

    qview->use_scroll_to_selection = FALSE;

    qview->sort_column = 0;
    qview->increasing = FALSE;

    qview->numeric_abs = FALSE;
    qview->numeric_inv_sort = FALSE;

    priv = GNC_QUERY_VIEW_GET_PRIVATE(qview);
    priv->component_id = gnc_register_gui_component ("gnc-query-view-cm-class",
                                                     gnc_query_view_refresh_handler,
                                                     NULL, qview);
}

static gint
sort_iter_compare_func (GtkTreeModel *model,
                        GtkTreeIter  *a,
                        GtkTreeIter  *b,
                        gpointer      userdata)
{
    /* This is really a dummy sort function, it leaves the list as is. */
    return 0;
}

/********************************************************************\
 * gnc_query_sort_order                                             *
 *   allows the sort order to be specified                          *
 *                                                                  *
 * Args: qview   - the view to sort                                 *
 *       column  - the sort column in the tree view, 1 ->           *
 *       order   - GTK_SORT_ASCENDING or GTK_SORT_DESCENDING        *
\********************************************************************/
void
gnc_query_sort_order (GNCQueryView *qview, gint column, GtkSortType order)
{
    GtkTreeSortable *sortable;
    gint sortcol;

    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    sortable = GTK_TREE_SORTABLE(gtk_tree_view_get_model (GTK_TREE_VIEW(qview)));

    if((column > qview->num_columns) || (column == 0) )
        sortcol = 1;
    else
        sortcol = column;

    gtk_tree_sortable_set_sort_column_id (sortable, sortcol, order);
}

static void
gnc_query_sort_cb (GtkTreeSortable *sortable, gpointer user_data)
{
    GNCQueryView *qview = GNC_QUERY_VIEW(user_data);
    GtkSortType   type;
    gint          sortcol;
    gboolean      new_column = FALSE;

    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));
    g_return_if_fail (qview->query != NULL);

    gtk_tree_sortable_get_sort_column_id (sortable, &sortcol, &type);

    /* We need to subtract 1 for the added pointer column in the liststore
       which is not displayed to align back to params */
    sortcol = sortcol - 1;

    if(type == GTK_SORT_ASCENDING)
        qview->increasing = TRUE;
    else
        qview->increasing = FALSE;

    /* Is this a new column or a re-click on the existing column? */
    new_column = (qview->sort_column != sortcol);

    /* Save the column */
    qview->sort_column = sortcol;

    gnc_query_view_set_query_sort (qview, new_column);
}

static void
gnc_query_view_init_view (GNCQueryView *qview)
{
    GtkTreeView         *view = GTK_TREE_VIEW(qview);
    GtkTreeSortable     *sortable;
    GtkTreeSelection    *selection;
    GtkTreeViewColumn   *col;
    GtkCellRenderer     *renderer;
    GList               *node;
    gint                 i;

    sortable = GTK_TREE_SORTABLE(gtk_tree_view_get_model (GTK_TREE_VIEW(view)));

    /* compute the number of columns and fill in the rest of the view */
    qview->num_columns = g_list_length (qview->column_params);

    // Set grid lines option to preference
    gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(view), gnc_tree_view_get_grid_lines_pref ());

    for (i = 0, node = qview->column_params; node; node = node->next, i++)
    {
        const char *type;
        gfloat algn = 0;
        GNCSearchParamSimple *param = node->data;

        g_assert (GNC_IS_SEARCH_PARAM_SIMPLE(param));

        col = gtk_tree_view_column_new ();

        /* Set the column title */
        gtk_tree_view_column_set_title (col, (gchar *) ((GNCSearchParam *) param)->title);

        /* pack tree view column into tree view */
        gtk_tree_view_append_column (view, col);

        /* Get justification */
        if (((GNCSearchParam *) param)->justify == GTK_JUSTIFY_CENTER)
            algn = 0.5;
        else if (((GNCSearchParam *) param)->justify == GTK_JUSTIFY_RIGHT)
        {
            /* GTK_JUSTIFY_RIGHT is only used for monetary values so right align
             * the column title and data for both ltr and rtl */
            if (gtk_widget_get_direction (GTK_WIDGET(view)) != GTK_TEXT_DIR_RTL)
                algn = 1.0;
        }

        /* Set the column title alignment to that of the column */
        gtk_tree_view_column_set_alignment (col, algn);

        /* Set column resizable */
        if (((GNCSearchParam *) param)->non_resizeable)
        {
            gtk_tree_view_column_set_resizable (col, FALSE);
            gtk_tree_view_column_set_expand (col, FALSE);
        }
        else
            gtk_tree_view_column_set_resizable (col, TRUE);

        /* Set column clickable */
        if (((GNCSearchParam *) param)->passive)
            gtk_tree_view_column_set_clickable (col, FALSE);
        else
        {
            gtk_tree_view_column_set_clickable (col, TRUE);
            /* Add sortable columns */
            gtk_tree_view_column_set_sort_column_id (col, i+1);
            gtk_tree_sortable_set_sort_func (sortable, i+1,
                                             sort_iter_compare_func,
                                             GINT_TO_POINTER(i+1), NULL);
        }

        type = gnc_search_param_get_param_type (((GNCSearchParam *) param));

        if (g_strcmp0 (type, QOF_TYPE_BOOLEAN) == 0)
        {
            renderer = gtk_cell_renderer_toggle_new ();

            /* pack cell renderer toggle into tree view column */
            gtk_tree_view_column_pack_start (col, renderer, TRUE);
            gtk_tree_view_column_add_attribute (col, renderer, "active", i+1);
            g_object_set (renderer, "xalign", algn, NULL);
            g_object_set_data (G_OBJECT(renderer), "column", GINT_TO_POINTER(i+1));
            g_signal_connect (renderer, "toggled",
                              G_CALLBACK(gnc_query_view_toggled_cb), view);
        }
        else
        {
            renderer = gtk_cell_renderer_text_new ();

            /* pack cell renderer text into tree view column */
            gtk_tree_view_column_pack_start (col, renderer, TRUE);
            gtk_tree_view_column_add_attribute (col, renderer, "text", i+1);
            g_object_set (renderer, "xalign", algn, NULL);
            g_object_set_data (G_OBJECT(renderer), "column", GINT_TO_POINTER(i+1));
        }
    }

    /* set initial sort order */
    gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL);
    gtk_tree_sortable_set_sort_column_id (sortable, 1, GTK_SORT_DESCENDING);

    g_signal_connect (sortable, "sort-column-changed",
                      G_CALLBACK(gnc_query_sort_cb),
                      view);

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
    g_signal_connect (selection, "changed",
                      G_CALLBACK(gnc_query_view_select_row_cb),
                      NULL);

    g_signal_connect (view, "row-activated",
                      G_CALLBACK(gnc_query_view_double_click_cb),
                      NULL);
}

static void
gnc_query_view_class_init (GNCQueryViewClass *klass)
{
    GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;

    query_view_signals[COLUMN_TOGGLED] =
        g_signal_new("column_toggled",
                     G_TYPE_FROM_CLASS(widget_class),
                     G_SIGNAL_RUN_FIRST,
                     G_STRUCT_OFFSET(GNCQueryViewClass, column_toggled),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__POINTER,
                     G_TYPE_NONE,
                     1,
                     G_TYPE_POINTER);

    query_view_signals[ROW_SELECTED] =
        g_signal_new("row_selected",
                     G_TYPE_FROM_CLASS(widget_class),
                     G_SIGNAL_RUN_FIRST,
                     G_STRUCT_OFFSET(GNCQueryViewClass, row_selected),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__POINTER,
                     G_TYPE_NONE,
                     1,
                     G_TYPE_POINTER);

    query_view_signals[DOUBLE_CLICK_ENTRY] =
        g_signal_new("double_click_entry",
                     G_TYPE_FROM_CLASS(widget_class),
                     G_SIGNAL_RUN_FIRST,
                     G_STRUCT_OFFSET(GNCQueryViewClass, double_click_entry),
                     NULL, NULL,
                     g_cclosure_marshal_VOID__POINTER,
                     G_TYPE_NONE,
                     1,
                     G_TYPE_POINTER);

    widget_class->destroy = gnc_query_view_destroy;

    klass->column_toggled = NULL;
    klass->row_selected = NULL;
    klass->double_click_entry = NULL;
}

static void
gnc_query_view_select_row_cb (GtkTreeSelection *selection, gpointer user_data)
{
    GNCQueryView *qview = GNC_QUERY_VIEW(gtk_tree_selection_get_tree_view (selection));
    gint number_of_rows = gtk_tree_selection_count_selected_rows (selection);

    g_signal_emit (qview, query_view_signals[ROW_SELECTED], 0,
                   GINT_TO_POINTER(number_of_rows));
}

static void
gnc_query_view_double_click_cb (GtkTreeView       *view,
                                GtkTreePath       *path,
                                GtkTreeViewColumn *column,
                                gpointer           user_data)
{
    GNCQueryView     *qview = GNC_QUERY_VIEW(view);
    GtkTreeModel     *model;
    GtkTreeIter       iter;
    gpointer          entry = NULL;

    model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));

    if (gtk_tree_model_get_iter (model, &iter, path))
        gtk_tree_model_get (model, &iter, 0, &entry, -1);

    g_signal_emit (qview, query_view_signals[DOUBLE_CLICK_ENTRY], 0, entry);
}

static void
gnc_query_view_toggled_cb (GtkCellRendererToggle *cell_renderer,
                           gchar                 *path,
                           gpointer               user_data)
{
    GNCQueryView     *qview = GNC_QUERY_VIEW(user_data);
    GtkTreeModel     *model;
    GtkTreeIter       iter;
    GtkTreePath      *treepath;
    gint             *indices;
    gpointer          entry = NULL;
    gboolean          toggled;
    gint              column;

    model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));

    column = GPOINTER_TO_INT(g_object_get_data (G_OBJECT(cell_renderer), "column"));

    toggled = gtk_cell_renderer_toggle_get_active (cell_renderer);

    treepath = gtk_tree_path_new_from_string (path);

    if (gtk_tree_model_get_iter (model, &iter, treepath))
    {
        gtk_tree_model_get (model, &iter, 0, &entry, -1);
        indices = gtk_tree_path_get_indices (treepath);
        qview->toggled_row = indices[0];
        qview->toggled_column = column;

        if(toggled)
            g_signal_emit (qview, query_view_signals[COLUMN_TOGGLED], 0, GINT_TO_POINTER(0));
        else
            g_signal_emit (qview, query_view_signals[COLUMN_TOGGLED], 0, GINT_TO_POINTER(1));
    }
    gtk_tree_path_free (treepath);
}

static void
gnc_query_view_destroy (GtkWidget *widget)
{
    GNCQueryView        *qview = GNC_QUERY_VIEW(widget);
    GNCQueryViewPrivate *priv;

    priv = GNC_QUERY_VIEW_GET_PRIVATE(qview);
    if (priv->component_id > 0)
    {
        gnc_unregister_gui_component (priv->component_id);
        priv->component_id = 0;
    }

    /* Remove the query */
    if (qview->query)
    {
        qof_query_destroy (qview->query);
        qview->query = NULL;
    }

    GTK_WIDGET_CLASS(gnc_query_view_parent_class)->destroy (widget);
}

gint
gnc_query_view_get_num_entries (GNCQueryView *qview)
{
    GtkTreeModel *model;

    g_return_val_if_fail (qview != NULL, 0);
    g_return_val_if_fail (GNC_IS_QUERY_VIEW(qview), 0);

    model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
    return gtk_tree_model_iter_n_children (model, NULL);
}

gpointer
gnc_query_view_get_selected_entry (GNCQueryView *qview)
{
    gpointer entry = NULL;
    GList *entries = NULL;
    gint num_entries = 0;

    g_return_val_if_fail (qview != NULL, NULL);
    g_return_val_if_fail (GNC_IS_QUERY_VIEW(qview), NULL);

    entries = gnc_query_view_get_selected_entry_list (qview);
    if (entries)
        entry = entries->data;

    num_entries = g_list_length (entries);
    if (num_entries > 1)
        PWARN ("Expected only one selected entry but found %i. "
               "Discarding all but the first one.", num_entries);

    g_list_free (entries);

    return entry;
}

typedef struct
{
    GList *entries;
} acc_data;

static void
accumulate_entries (GtkTreeModel *model, GtkTreePath *path,
                    GtkTreeIter *iter, gpointer data)
{
    acc_data *acc_entries = (acc_data*)data;
    gpointer entry = NULL;
    GList *entries = acc_entries->entries;

    gtk_tree_model_get (model, iter, 0, &entry, -1);
    entries = g_list_prepend (entries, entry);
    acc_entries->entries = entries;
}

GList *
gnc_query_view_get_selected_entry_list (GNCQueryView *qview)
{
    GtkTreeSelection *selection;
    acc_data acc_entries;

    g_return_val_if_fail (qview != NULL, NULL);
    g_return_val_if_fail (GNC_IS_QUERY_VIEW(qview), NULL);

    acc_entries.entries = NULL;
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
    gtk_tree_selection_selected_foreach (selection, accumulate_entries,
                                         &acc_entries);
    acc_entries.entries = g_list_reverse (acc_entries.entries);
    return acc_entries.entries;
}

void
gnc_query_use_scroll_to_selection (GNCQueryView *qview, gboolean scroll)
{
    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    qview->use_scroll_to_selection = scroll;
}

static void
scroll_to_selection (GNCQueryView *qview, gboolean override_scroll)
{
    GtkTreeSelection  *selection;
    GList *path_list, *node;

    if (!qview->use_scroll_to_selection && !override_scroll)
        return;

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));

    /* Ensure last selected item, if any, can be seen */
    path_list = gtk_tree_selection_get_selected_rows (selection, NULL);
    node = g_list_last (path_list);

    if (node)
    {
        GtkTreePath *tree_path = node->data;
        gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(qview),
                                      tree_path, NULL, FALSE, 0.0, 0.0);
    }
    g_list_free_full (path_list, (GDestroyNotify) gtk_tree_path_free);
}

void
gnc_query_scroll_to_selection (GNCQueryView *qview)
{
    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    scroll_to_selection (qview, FALSE);
}

void
gnc_query_force_scroll_to_selection (GNCQueryView *qview)
{
    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    scroll_to_selection (qview, TRUE);
}

static void
gnc_query_view_refresh_selected (GNCQueryView *qview, GList *old_entry)
{
    GtkTreeModel     *model;
    GtkTreeIter       iter;
    GtkTreeSelection *selection;
    GList            *node;
    gboolean          valid;

    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));

    if (g_list_length (old_entry) > 0)
    {
        /* Walk the list of old entries */
        for (node = old_entry; node; node = node->next)
        {
            gpointer pointer;

            valid = gtk_tree_model_get_iter_first (model, &iter);

            while (valid)
            {
                // Walk through the liststore, reading each row
                gtk_tree_model_get (model, &iter, 0, &pointer, -1);

                if (pointer == node->data)
                {
                    gtk_tree_selection_select_iter (selection, &iter);
                    break;
                }
                valid = gtk_tree_model_iter_next (model, &iter);
            }
        }
        gnc_query_scroll_to_selection (qview);
    }
}

/********************************************************************\
 * gnc_query_view_refresh                                           *
 *   refreshes the view                                             *
 *                                                                  *
 * Args: qview - view to refresh                                    *
 * Returns: nothing                                                 *
\********************************************************************/
void
gnc_query_view_refresh (GNCQueryView *qview)
{
    GtkTreeModel     *model;
    GList            *selected_entries;

    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    selected_entries = gnc_query_view_get_selected_entry_list (qview);
    model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
    gtk_list_store_clear (GTK_LIST_STORE(model));

    gnc_query_view_fill (qview);
    gnc_query_view_refresh_selected (qview, selected_entries);
    g_list_free (selected_entries);
}

/********************************************************************\
 * gnc_query_view_set_query_sort                                    *
 *   sets the sorting order of entries in the view                  *
 *                                                                  *
 * Args: qview      - view to change the sort order for             *
 *   new_column - is this a new column (so should we set the    *
 *                    query sort order or just set the 'increasing' *
 * Returns: nothing                                                 *
\********************************************************************/
static void
gnc_query_view_set_query_sort (GNCQueryView *qview, gboolean new_column)
{
    gboolean              sort_order = qview->increasing;
    GList                *node;
    GNCSearchParamSimple *param;

    /* Find the column parameter definition */
    node = g_list_nth (qview->column_params, qview->sort_column);
    param = node->data;

    g_assert (GNC_IS_SEARCH_PARAM_SIMPLE(param));

    /* If param values are based on a param function, sorting is not possible */
    if (gnc_search_param_has_param_fcn (param))
    {
        gnc_query_view_refresh (qview);
        return;
    }

    /* If we're asked to invert numerics, and if this is a numeric or
     * debred column, then invert the sort order.
     */
    if (qview->numeric_inv_sort)
    {
        const char *type = gnc_search_param_get_param_type ((GNCSearchParam *) param);
        if (!g_strcmp0(type, QOF_TYPE_NUMERIC) ||
                !g_strcmp0(type, QOF_TYPE_DEBCRED))
            sort_order = !sort_order;
    }

    /* Set the sort order for the engine, if the key changed */
    if (new_column)
    {
        GSList *p1, *p2;

        p1 = gnc_search_param_get_param_path (param);
        p2 = g_slist_prepend (NULL, QUERY_DEFAULT_SORT);
        qof_query_set_sort_order (qview->query, p1, p2, NULL);
    }

    qof_query_set_sort_increasing (qview->query,
                                   sort_order,
                                   sort_order,
                                   sort_order);

    gnc_query_view_refresh (qview);
}

/********************************************************************\
 * gnc_query_view_fill                                              *
 *   Add all items to the list store                                *
 *                                                                  *
 * Args: qview - view to add item to                                *
 * Returns: nothing                                                 *
\********************************************************************/
static void
gnc_query_view_fill (GNCQueryView *qview)
{
    GNCQueryViewPrivate *priv;
    GtkTreeModel        *model;
    GtkTreeIter          iter;
    GList               *entries, *item;
    const                GncGUID *guid;
    gint                 i;

    /* Clear all watches */
    priv = GNC_QUERY_VIEW_GET_PRIVATE(qview);
    gnc_gui_component_clear_watches (priv->component_id);

    entries = qof_query_run (qview->query);

    model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));

    for (item = entries; item; item = item->next)
    {
        GList *node;
        const QofParam *gup;
        QofParam *qp = NULL;

        /* Add a row to the list store */
        gtk_list_store_append (GTK_LIST_STORE(model), &iter);
        /* Add a pointer to the data in the first column of the list store */
        gtk_list_store_set (GTK_LIST_STORE(model), &iter, 0, item->data, -1);

        for (i = 0, node = qview->column_params; node; node = node->next)
        {
            gboolean result;
            GNCSearchParamSimple *param = node->data;
            GSList *converters = NULL;
            const char *type = gnc_search_param_get_param_type ((GNCSearchParam *) param);
            gpointer res = item->data;
            gchar *qofstring;

            g_assert (GNC_IS_SEARCH_PARAM_SIMPLE(param));
            converters = gnc_search_param_get_converters (param);

            /* Test for boolean type */
            if (g_strcmp0 (type, QOF_TYPE_BOOLEAN) == 0)
            {
                result = (gboolean) GPOINTER_TO_INT(gnc_search_param_compute_value (param, res));
                gtk_list_store_set (GTK_LIST_STORE(model), &iter, i + 1, result, -1);
                i++;
                continue;
            }

            /* Do all the object conversions */
            for (; converters; converters = converters->next)
            {
                qp = converters->data;
                if (converters->next)
                    res = (qp->param_getfcn)(res, qp);
            }

            /* Now convert this to a text value for the row */
            if (qp && (g_strcmp0 (type, QOF_TYPE_DEBCRED) == 0 ||
                       g_strcmp0 (type, QOF_TYPE_NUMERIC) == 0))
            {

                gnc_numeric (*nfcn)(gpointer, QofParam *) =
                    (gnc_numeric(*)(gpointer, QofParam *))(qp->param_getfcn);
                gnc_numeric value = nfcn (res, qp);

                if (qview->numeric_abs)
                    value = gnc_numeric_abs (value);
                gtk_list_store_set (GTK_LIST_STORE(model), &iter, i + 1,
                     xaccPrintAmount (value, gnc_default_print_info (FALSE)), -1);
            }
            else
            {
                qofstring = qof_query_core_to_string (type, res, qp);
                gtk_list_store_set (GTK_LIST_STORE(model), &iter, i + 1, qofstring , -1);
                g_free (qofstring);
            }
            i++;
        }
        /* and set a watcher on this item */
        gup = priv->get_guid;
        guid = (const GncGUID*)((gup->param_getfcn)(item->data, gup));
        gnc_gui_component_watch_entity (priv->component_id, guid,
                                        QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
    }
}

/********************************************************************\
 * gnc_query_view_unselect_all                                      *
 *   unselect all items in the view                                 *
 *                                                                  *
 * Args: qview - view to unselect all                               *
 * Returns: nothing                                                 *
\********************************************************************/
void
gnc_query_view_unselect_all (GNCQueryView *qview)
{
    GtkTreeSelection *selection;

    g_return_if_fail (qview != NULL);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
    gtk_tree_selection_unselect_all (selection);
}

gboolean gnc_query_view_item_in_view (GNCQueryView *qview, gpointer item)
{
    GtkTreeModel *model;
    GtkTreeIter   iter;
    gboolean      valid;
    gpointer      pointer;

    g_return_val_if_fail (qview, FALSE);
    g_return_val_if_fail (item, FALSE);
    g_return_val_if_fail (GNC_IS_QUERY_VIEW(qview), FALSE);

    model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
    valid = gtk_tree_model_get_iter_first (model, &iter);

    while (valid)
    {
        // Walk through the list, reading each row
        gtk_tree_model_get (model, &iter, 0, &pointer, -1);

        if (pointer == item)
            return TRUE;

        valid = gtk_tree_model_iter_next (model, &iter);
    }
    return FALSE;
}

void
gnc_query_view_set_numerics (GNCQueryView *qview, gboolean abs, gboolean inv_sort)
{
    g_return_if_fail (qview);
    g_return_if_fail (GNC_IS_QUERY_VIEW(qview));

    qview->numeric_abs = abs;
    qview->numeric_inv_sort = inv_sort;
}
