mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
1053 lines
38 KiB
C
1053 lines
38 KiB
C
/********************************************************************\
|
|
* reconcile-view.c -- A view of accounts to be reconciled for *
|
|
* GnuCash. *
|
|
* Copyright (C) 1998,1999 Jeremy Collins *
|
|
* Copyright (C) 1998-2000 Linas Vepstas *
|
|
* 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 <glib/gi18n.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
#include "gnc-date.h"
|
|
#include "qof.h"
|
|
#include "qofbook.h"
|
|
#include "Transaction.h"
|
|
#include "gnc-ui-util.h"
|
|
#include "gnc-prefs.h"
|
|
#include "reconcile-view.h"
|
|
#include "search-param.h"
|
|
#include "gnc-component-manager.h"
|
|
|
|
#define GNC_PREF_CHECK_CLEARED "check-cleared"
|
|
|
|
/* Signal codes */
|
|
enum
|
|
{
|
|
TOGGLE_RECONCILED,
|
|
LINE_SELECTED,
|
|
DOUBLE_CLICK_SPLIT,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
|
|
/** Static Globals ****************************************************/
|
|
static GNCQueryViewClass *parent_class = NULL;
|
|
static guint reconcile_view_signals[LAST_SIGNAL] = {0};
|
|
|
|
/** Static function declarations **************************************/
|
|
static void gnc_reconcile_view_init (GNCReconcileView *view);
|
|
static void gnc_reconcile_view_class_init (GNCReconcileViewClass *klass);
|
|
static void gnc_reconcile_view_finalize (GObject *object);
|
|
static gpointer gnc_reconcile_view_is_reconciled (gpointer item,
|
|
gpointer user_data);
|
|
static void gnc_reconcile_view_line_toggled (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data);
|
|
static void gnc_reconcile_view_double_click_entry (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data);
|
|
static void gnc_reconcile_view_row_selected (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data);
|
|
static gboolean gnc_reconcile_view_key_press_cb (GtkWidget *widget,
|
|
GdkEventKey *event,
|
|
gpointer user_data);
|
|
static gboolean gnc_reconcile_view_tooltip_cb (GNCQueryView *qview,
|
|
gint x, gint y,
|
|
gboolean keyboard_mode,
|
|
GtkTooltip* tooltip,
|
|
gpointer* user_data);
|
|
|
|
GType
|
|
gnc_reconcile_view_get_type (void)
|
|
{
|
|
static GType gnc_reconcile_view_type = 0;
|
|
|
|
if (gnc_reconcile_view_type == 0)
|
|
{
|
|
static const GTypeInfo gnc_reconcile_view_info =
|
|
{
|
|
sizeof (GNCReconcileViewClass),
|
|
NULL,
|
|
NULL,
|
|
(GClassInitFunc) gnc_reconcile_view_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GNCReconcileView),
|
|
0,
|
|
(GInstanceInitFunc) gnc_reconcile_view_init
|
|
};
|
|
|
|
gnc_reconcile_view_type = g_type_register_static (GNC_TYPE_QUERY_VIEW,
|
|
"GncReconcileView",
|
|
&gnc_reconcile_view_info,
|
|
0);
|
|
}
|
|
return gnc_reconcile_view_type;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gnc_reconcile_view_tooltip_cb (GNCQueryView *qview, gint x, gint y,
|
|
gboolean keyboard_mode, GtkTooltip *tooltip,
|
|
gpointer *user_data)
|
|
{
|
|
GtkTreeModel* model;
|
|
GtkTreeIter iter;
|
|
|
|
// If the Description is longer than can be display, show it in a tooltip
|
|
if (gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW(qview), &x, &y,
|
|
keyboard_mode, &model, NULL, &iter))
|
|
{
|
|
GtkTreeViewColumn *col;
|
|
GList *cols;
|
|
gint col_pos, col_width;
|
|
gchar* desc_text = NULL;
|
|
|
|
/* Are we in keyboard tooltip mode, displays tooltip below/above treeview CTRL+F1 */
|
|
if (keyboard_mode == FALSE)
|
|
{
|
|
if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(qview), x, y,
|
|
NULL, &col, NULL, NULL) == FALSE)
|
|
return FALSE;
|
|
}
|
|
else
|
|
gtk_tree_view_get_cursor (GTK_TREE_VIEW(qview), NULL, &col);
|
|
|
|
cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(qview));
|
|
col_width = gtk_tree_view_column_get_width (col);
|
|
col_pos = g_list_index (cols, col);
|
|
g_list_free (cols);
|
|
|
|
/* If column is not description, do not show tooltip */
|
|
if (col_pos != (REC_DESC - 1)) // allow for the pointer model column at 0
|
|
return FALSE;
|
|
|
|
gtk_tree_model_get (model, &iter, REC_DESC, &desc_text, -1);
|
|
|
|
if (desc_text)
|
|
{
|
|
PangoLayout* layout;
|
|
gint text_width;
|
|
gint root_x, root_y;
|
|
gint cur_x, cur_y;
|
|
|
|
layout = gtk_widget_create_pango_layout (GTK_WIDGET(qview), desc_text);
|
|
pango_layout_get_pixel_size (layout, &text_width, NULL);
|
|
g_object_unref (layout);
|
|
|
|
/* If text_width + 10 <= column_width, do not show tooltip */
|
|
if ((text_width + 10) <= col_width)
|
|
{
|
|
g_free (desc_text);
|
|
return FALSE;
|
|
}
|
|
|
|
if (keyboard_mode == FALSE)
|
|
{
|
|
GdkSeat *seat;
|
|
GdkDevice *pointer;
|
|
GtkWindow *tip_win = NULL;
|
|
GdkWindow *parent_window;
|
|
GList *win_list, *node;
|
|
|
|
parent_window = gtk_widget_get_parent_window (GTK_WIDGET(qview));
|
|
|
|
seat = gdk_display_get_default_seat (gdk_window_get_display (parent_window));
|
|
pointer = gdk_seat_get_pointer (seat);
|
|
|
|
gdk_window_get_device_position (parent_window, pointer, &cur_x, &cur_y, NULL);
|
|
|
|
gdk_window_get_origin (parent_window, &root_x, &root_y);
|
|
|
|
/* Get a list of toplevel windows */
|
|
win_list = gtk_window_list_toplevels ();
|
|
|
|
/* Look for the gtk-tooltip window, we do this as gtk_widget_get_tooltip_window
|
|
does not seem to work for the default tooltip window, custom yes */
|
|
for (node = win_list; node != NULL; node = node->next)
|
|
{
|
|
if (g_strcmp0 (gtk_widget_get_name (node->data), "gtk-tooltip") == 0)
|
|
tip_win = node->data;
|
|
}
|
|
g_list_free (win_list);
|
|
|
|
gtk_tooltip_set_text (tooltip, desc_text);
|
|
|
|
if (GTK_IS_WINDOW(tip_win))
|
|
{
|
|
GdkMonitor *mon;
|
|
GdkRectangle monitor;
|
|
GtkRequisition requisition;
|
|
gint x, y;
|
|
|
|
gtk_widget_get_preferred_size (GTK_WIDGET(tip_win), &requisition, NULL);
|
|
|
|
x = root_x + cur_x + 10;
|
|
y = root_y + cur_y + 10;
|
|
|
|
mon = gdk_display_get_monitor_at_point (gdk_window_get_display (parent_window), x, y);
|
|
gdk_monitor_get_geometry (mon, &monitor);
|
|
|
|
if (x + requisition.width > monitor.x + monitor.width)
|
|
x -= x - (monitor.x + monitor.width) + requisition.width;
|
|
else if (x < monitor.x)
|
|
x = monitor.x;
|
|
|
|
if (y + requisition.height > monitor.y + monitor.height)
|
|
y -= y - (monitor.y + monitor.height) + requisition.height;
|
|
|
|
gtk_window_move (tip_win, x, y);
|
|
}
|
|
}
|
|
gtk_tooltip_set_text (tooltip, desc_text);
|
|
g_free (desc_text);
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
gint
|
|
gnc_reconcile_view_get_column_width (GNCReconcileView *view, gint column)
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW(view);
|
|
GtkTreeViewColumn *col;
|
|
|
|
// allow for pointer model column at column 0
|
|
col = gtk_tree_view_get_column (GTK_TREE_VIEW(qview), (column - 1));
|
|
return gtk_tree_view_column_get_width (col);
|
|
}
|
|
|
|
void
|
|
gnc_reconcile_view_add_padding (GNCReconcileView *view, gint column, gint xpadding)
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW(view);
|
|
GtkTreeViewColumn *col;
|
|
GList *renderers;
|
|
GtkCellRenderer *cr0;
|
|
gint xpad, ypad;
|
|
|
|
// allow for pointer model column at column 0
|
|
col = gtk_tree_view_get_column (GTK_TREE_VIEW(qview), (column - 1));
|
|
renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(col));
|
|
cr0 = g_list_nth_data (renderers, 0);
|
|
g_list_free (renderers);
|
|
|
|
gtk_cell_renderer_get_padding (cr0, &xpad, &ypad);
|
|
gtk_cell_renderer_set_padding (cr0, xpadding, ypad);
|
|
}
|
|
|
|
/****************************************************************************\
|
|
* gnc_reconcile_view_new *
|
|
* creates the account tree *
|
|
* *
|
|
* Args: account - the account to use in filling up the splits. *
|
|
* type - the type of view, RECLIST_DEBIT or RECLIST_CREDIT *
|
|
* statement_date - date of statement *
|
|
* Returns: the account tree widget, or NULL if there was a problem. *
|
|
\****************************************************************************/
|
|
static void
|
|
gnc_reconcile_view_construct (GNCReconcileView *view, Query *query)
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW(view);
|
|
GtkTreeViewColumn *col;
|
|
GtkTreeSelection *selection;
|
|
GList *renderers;
|
|
GtkCellRenderer *cr0;
|
|
gboolean inv_sort = FALSE;
|
|
|
|
if (view->view_type == RECLIST_CREDIT)
|
|
inv_sort = TRUE;
|
|
|
|
/* Construct the view */
|
|
gnc_query_view_construct (qview, view->column_list, query);
|
|
gnc_query_view_set_numerics (qview, TRUE, inv_sort);
|
|
|
|
/* Set the description field to have spare space,
|
|
REC_DESC -1 to allow for the pointer model column at 0 */
|
|
col = gtk_tree_view_get_column (GTK_TREE_VIEW(qview), (REC_DESC - 1));
|
|
gtk_tree_view_column_set_expand (col, TRUE);
|
|
|
|
/* Get the renderer of the description column and set ellipsize value */
|
|
renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(col));
|
|
cr0 = g_list_nth_data (renderers, 0);
|
|
g_list_free (renderers);
|
|
g_object_set (cr0, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
|
|
|
|
gtk_widget_set_has_tooltip (GTK_WIDGET(qview), TRUE);
|
|
|
|
/* Set the selection method */
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
|
|
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
|
|
|
|
/* Now set up the signals for the QueryView */
|
|
g_signal_connect (G_OBJECT (qview), "column_toggled",
|
|
G_CALLBACK (gnc_reconcile_view_line_toggled), view);
|
|
g_signal_connect (G_OBJECT(qview), "double_click_entry",
|
|
G_CALLBACK(gnc_reconcile_view_double_click_entry), view);
|
|
g_signal_connect (G_OBJECT(qview), "row_selected",
|
|
G_CALLBACK(gnc_reconcile_view_row_selected), view);
|
|
g_signal_connect (G_OBJECT(qview), "key_press_event",
|
|
G_CALLBACK(gnc_reconcile_view_key_press_cb), view);
|
|
g_signal_connect (G_OBJECT(qview), "query-tooltip",
|
|
G_CALLBACK(gnc_reconcile_view_tooltip_cb), view);
|
|
}
|
|
|
|
static gint
|
|
sort_date_helper (time64 date_a, time64 date_b)
|
|
{
|
|
gint ret = 0;
|
|
|
|
if (date_a < date_b)
|
|
ret = -1;
|
|
else if (date_a > date_b)
|
|
ret = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gint
|
|
sort_iter_compare_func (GtkTreeModel *model,
|
|
GtkTreeIter *a,
|
|
GtkTreeIter *b,
|
|
gpointer user_data)
|
|
{
|
|
gboolean rec_a, rec_b;
|
|
Split *split_a, *split_b;
|
|
time64 date_a, date_b;
|
|
gint ret = 0;
|
|
|
|
gtk_tree_model_get (model, a, REC_POINTER, &split_a, REC_RECN, &rec_a, -1);
|
|
gtk_tree_model_get (model, b, REC_POINTER, &split_b, REC_RECN, &rec_b, -1);
|
|
|
|
date_a = xaccTransGetDate (xaccSplitGetParent (split_a));
|
|
date_b = xaccTransGetDate (xaccSplitGetParent (split_b));
|
|
|
|
if (rec_a > rec_b)
|
|
ret = -1;
|
|
else if (rec_b > rec_a)
|
|
ret = 1;
|
|
else ret = sort_date_helper (date_a, date_b);
|
|
|
|
return ret;
|
|
}
|
|
|
|
GtkWidget *
|
|
gnc_reconcile_view_new (Account *account, GNCReconcileViewType type,
|
|
time64 statement_date)
|
|
{
|
|
GNCReconcileView *view;
|
|
GtkListStore *liststore;
|
|
GtkTreeSortable *sortable;
|
|
GtkTreeViewColumn *col;
|
|
gboolean include_children, auto_check;
|
|
GList *accounts = NULL;
|
|
GList *splits;
|
|
Query *query;
|
|
QofNumericMatch sign;
|
|
|
|
g_return_val_if_fail (account, NULL);
|
|
g_return_val_if_fail ((type == RECLIST_DEBIT) ||
|
|
(type == RECLIST_CREDIT), NULL);
|
|
|
|
view = g_object_new (GNC_TYPE_RECONCILE_VIEW, NULL);
|
|
|
|
/* Create the list store with 6 columns and add to treeview,
|
|
column 0 will be a pointer to the entry */
|
|
liststore = gtk_list_store_new (6, G_TYPE_POINTER, G_TYPE_STRING,
|
|
G_TYPE_STRING, G_TYPE_STRING,
|
|
G_TYPE_STRING, G_TYPE_BOOLEAN);
|
|
|
|
gtk_tree_view_set_model (GTK_TREE_VIEW(view), GTK_TREE_MODEL(liststore));
|
|
g_object_unref (liststore);
|
|
|
|
view->account = account;
|
|
view->view_type = type;
|
|
view->statement_date = statement_date;
|
|
|
|
query = qof_query_create_for (GNC_ID_SPLIT);
|
|
qof_query_set_book (query, gnc_get_current_book ());
|
|
|
|
include_children = xaccAccountGetReconcileChildrenStatus (account);
|
|
if (include_children)
|
|
accounts = gnc_account_get_descendants (account);
|
|
|
|
/* match the account */
|
|
accounts = g_list_prepend (accounts, account);
|
|
|
|
xaccQueryAddAccountMatch (query, accounts, QOF_GUID_MATCH_ANY, QOF_QUERY_AND);
|
|
|
|
g_list_free (accounts);
|
|
|
|
sign = (type == RECLIST_CREDIT) ? QOF_NUMERIC_MATCH_CREDIT :
|
|
QOF_NUMERIC_MATCH_DEBIT;
|
|
|
|
xaccQueryAddNumericMatch (query, gnc_numeric_zero (), sign, QOF_COMPARE_GTE,
|
|
QOF_QUERY_AND, SPLIT_AMOUNT, NULL);
|
|
|
|
/* limit the matches only to Cleared and Non-reconciled splits */
|
|
xaccQueryAddClearedMatch (query, CLEARED_NO | CLEARED_CLEARED, QOF_QUERY_AND);
|
|
|
|
/* Initialize the QueryList */
|
|
gnc_reconcile_view_construct (view, query);
|
|
|
|
/* find the list of splits to auto-reconcile */
|
|
auto_check = gnc_prefs_get_bool (GNC_PREFS_GROUP_RECONCILE, GNC_PREF_CHECK_CLEARED);
|
|
|
|
if (auto_check)
|
|
{
|
|
time64 statement_date_day_end = gnc_time64_get_day_end (statement_date);
|
|
for (splits = qof_query_run (query); splits; splits = splits->next)
|
|
{
|
|
Split *split = splits->data;
|
|
char recn = xaccSplitGetReconcile (split);
|
|
time64 trans_date = xaccTransGetDate (xaccSplitGetParent (split));
|
|
|
|
/* Just an extra verification that our query is correct ;) */
|
|
g_assert (recn == NREC || recn == CREC);
|
|
|
|
if (recn == CREC && gnc_difftime (trans_date, statement_date_day_end) <= 0)
|
|
g_hash_table_insert (view->reconciled, split, split);
|
|
}
|
|
}
|
|
|
|
/* set up a separate sort function for the recn column as it is
|
|
* derived from a search function */
|
|
sortable = GTK_TREE_SORTABLE(gtk_tree_view_get_model (GTK_TREE_VIEW(view)));
|
|
col = gtk_tree_view_get_column (GTK_TREE_VIEW(view), REC_RECN -1);
|
|
gtk_tree_sortable_set_sort_func (sortable, REC_RECN, sort_iter_compare_func,
|
|
GINT_TO_POINTER (REC_RECN), NULL);
|
|
|
|
/* Free the query -- we don't need it anymore */
|
|
qof_query_destroy (query);
|
|
|
|
return GTK_WIDGET(view);
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_init (GNCReconcileView *view)
|
|
{
|
|
GNCSearchParamSimple *param;
|
|
GList *columns = NULL;
|
|
gboolean num_action = qof_book_use_split_action_for_num_field (gnc_get_current_book());
|
|
|
|
view->reconciled = g_hash_table_new (NULL, NULL);
|
|
view->account = NULL;
|
|
view->sibling = NULL;
|
|
|
|
param = gnc_search_param_simple_new ();
|
|
gnc_search_param_set_param_fcn (param, QOF_TYPE_BOOLEAN,
|
|
gnc_reconcile_view_is_reconciled, view);
|
|
gnc_search_param_set_title ((GNCSearchParam *) param, C_("Column header for 'Reconciled'", "R"));
|
|
gnc_search_param_set_justify ((GNCSearchParam *) param, GTK_JUSTIFY_CENTER);
|
|
gnc_search_param_set_passive ((GNCSearchParam *) param, FALSE);
|
|
gnc_search_param_set_non_resizeable ((GNCSearchParam *) param, TRUE);
|
|
columns = g_list_prepend (columns, param);
|
|
|
|
columns = gnc_search_param_prepend_with_justify (columns, _("Amount"),
|
|
GTK_JUSTIFY_RIGHT,
|
|
NULL, GNC_ID_SPLIT,
|
|
SPLIT_AMOUNT, NULL);
|
|
columns = gnc_search_param_prepend (columns, _("Description"), NULL,
|
|
GNC_ID_SPLIT, SPLIT_TRANS,
|
|
TRANS_DESCRIPTION, NULL);
|
|
columns = num_action ?
|
|
gnc_search_param_prepend_with_justify (columns, _("Num"),
|
|
GTK_JUSTIFY_CENTER,
|
|
NULL, GNC_ID_SPLIT,
|
|
SPLIT_ACTION, NULL) :
|
|
gnc_search_param_prepend_with_justify (columns, _("Num"),
|
|
GTK_JUSTIFY_CENTER,
|
|
NULL, GNC_ID_SPLIT,
|
|
SPLIT_TRANS, TRANS_NUM, NULL);
|
|
columns = gnc_search_param_prepend (columns, _("Date"),
|
|
NULL, GNC_ID_SPLIT,
|
|
SPLIT_TRANS,
|
|
TRANS_DATE_POSTED, NULL);
|
|
|
|
view->column_list = columns;
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_class_init (GNCReconcileViewClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
object_class = G_OBJECT_CLASS(klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
reconcile_view_signals[TOGGLE_RECONCILED] =
|
|
g_signal_new ("toggle_reconciled",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GNCReconcileViewClass,
|
|
toggle_reconciled),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_POINTER);
|
|
|
|
reconcile_view_signals[LINE_SELECTED] =
|
|
g_signal_new ("line_selected",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GNCReconcileViewClass,
|
|
line_selected),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_POINTER);
|
|
|
|
reconcile_view_signals[DOUBLE_CLICK_SPLIT] =
|
|
g_signal_new ("double_click_split",
|
|
G_OBJECT_CLASS_TYPE(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GNCReconcileViewClass,
|
|
double_click_split),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_POINTER);
|
|
|
|
object_class->finalize = gnc_reconcile_view_finalize;
|
|
|
|
klass->toggle_reconciled = NULL;
|
|
klass->line_selected = NULL;
|
|
klass->double_click_split = NULL;
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_toggle_split (GNCReconcileView *view, Split *split)
|
|
{
|
|
Split *current;
|
|
|
|
g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
|
|
g_return_if_fail (view->reconciled != NULL);
|
|
|
|
current = g_hash_table_lookup (view->reconciled, split);
|
|
|
|
if (current == NULL)
|
|
g_hash_table_insert (view->reconciled, split, split);
|
|
else
|
|
g_hash_table_remove (view->reconciled, split);
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_toggle (GNCReconcileView *view, Split *split)
|
|
{
|
|
g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
|
|
g_return_if_fail (view->reconciled != NULL);
|
|
|
|
gnc_reconcile_view_toggle_split (view, split);
|
|
|
|
g_signal_emit (G_OBJECT(view),
|
|
reconcile_view_signals[TOGGLE_RECONCILED], 0, split);
|
|
}
|
|
|
|
static gboolean
|
|
follow_select_tree_path (GNCReconcileView *view)
|
|
{
|
|
if (view->rowref)
|
|
{
|
|
GtkTreePath *tree_path = gtk_tree_row_reference_get_path (view->rowref);
|
|
GNCQueryView qview = view->qview;
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(&qview));
|
|
|
|
gtk_tree_selection_unselect_all (selection);
|
|
gtk_tree_selection_select_path (selection, tree_path);
|
|
|
|
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(&qview),
|
|
tree_path, NULL, FALSE, 0.0, 0.0);
|
|
|
|
gtk_tree_path_free (tree_path);
|
|
gtk_tree_row_reference_free (view->rowref);
|
|
view->rowref = NULL;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_line_toggled (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data)
|
|
{
|
|
GNCReconcileView *view;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gpointer entry;
|
|
GtkTreePath *tree_path;
|
|
|
|
g_return_if_fail (user_data);
|
|
g_return_if_fail (GNC_IS_QUERY_VIEW(qview));
|
|
|
|
view = user_data;
|
|
|
|
model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
|
|
gtk_tree_model_iter_nth_child (model, &iter, NULL, qview->toggled_row);
|
|
|
|
tree_path = gtk_tree_model_get_path (model, &iter);
|
|
view->rowref = gtk_tree_row_reference_new (model, tree_path);
|
|
gtk_tree_path_free (tree_path);
|
|
|
|
gtk_list_store_set (GTK_LIST_STORE(model), &iter, qview->toggled_column,
|
|
GPOINTER_TO_INT(item), -1);
|
|
|
|
tree_path = gtk_tree_row_reference_get_path (view->rowref);
|
|
|
|
if (gtk_tree_model_get_iter (model, &iter, tree_path))
|
|
{
|
|
gtk_tree_model_get (model, &iter, REC_POINTER, &entry, -1);
|
|
gnc_reconcile_view_toggle (view, entry);
|
|
}
|
|
|
|
// See if sorting on rec column, -1 to allow for the model pointer column at 0
|
|
if (qview->sort_column == REC_RECN - 1)
|
|
g_idle_add ((GSourceFunc)follow_select_tree_path, view);
|
|
else
|
|
{
|
|
gtk_tree_row_reference_free (view->rowref);
|
|
view->rowref = NULL;
|
|
}
|
|
|
|
gtk_tree_path_free (tree_path);
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_double_click_entry (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data)
|
|
{
|
|
GNCReconcileView *view;
|
|
|
|
/* item is the entry */
|
|
g_return_if_fail (user_data);
|
|
g_return_if_fail (GNC_IS_QUERY_VIEW(qview));
|
|
|
|
view = user_data;
|
|
|
|
g_signal_emit(G_OBJECT(view),
|
|
reconcile_view_signals[DOUBLE_CLICK_SPLIT], 0, item);
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_row_selected (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data)
|
|
{
|
|
GNCReconcileView *view;
|
|
|
|
/* item is the number of selected entries */
|
|
g_return_if_fail (user_data);
|
|
g_return_if_fail (GNC_IS_QUERY_VIEW(qview));
|
|
|
|
view = user_data;
|
|
|
|
g_signal_emit (G_OBJECT(view),
|
|
reconcile_view_signals[LINE_SELECTED], 0, item);
|
|
}
|
|
|
|
void
|
|
gnc_reconcile_view_set_list (GNCReconcileView *view, gboolean reconcile)
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW(view);
|
|
GtkTreeSelection *selection;
|
|
GtkTreeModel *model;
|
|
gpointer entry;
|
|
gboolean toggled;
|
|
GList *node;
|
|
GList *list_of_rows;
|
|
GList *rr_list = NULL;
|
|
GtkTreePath *last_tree_path = NULL;
|
|
|
|
model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
|
|
list_of_rows = gtk_tree_selection_get_selected_rows (selection, &model);
|
|
|
|
/* First create a list of Row references */
|
|
for (node = list_of_rows; node; node = node->next)
|
|
{
|
|
GtkTreeRowReference *rowref = gtk_tree_row_reference_new (model, node->data);
|
|
rr_list = g_list_prepend (rr_list, rowref);
|
|
gtk_tree_path_free (node->data);
|
|
}
|
|
|
|
rr_list = g_list_reverse (rr_list);
|
|
for (node = rr_list; node; node = node->next)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
GtkTreeRowReference *rowref = node->data;
|
|
|
|
path = gtk_tree_row_reference_get_path (rowref);
|
|
|
|
if (gtk_tree_model_get_iter (model, &iter, path))
|
|
{
|
|
/* now iter is a valid row iterator */
|
|
gtk_tree_model_get (model, &iter, REC_POINTER, &entry,
|
|
REC_RECN, &toggled, -1);
|
|
|
|
gtk_list_store_set (GTK_LIST_STORE(model), &iter, REC_RECN, reconcile, -1);
|
|
|
|
if (last_tree_path)
|
|
gtk_tree_path_free (last_tree_path);
|
|
last_tree_path = gtk_tree_row_reference_get_path (rowref);
|
|
|
|
if (reconcile != toggled)
|
|
gnc_reconcile_view_toggle (view, entry);
|
|
}
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
if (last_tree_path)
|
|
{
|
|
// See if sorting on rec column, -1 to allow for the model pointer column at 0
|
|
if (qview->sort_column == REC_RECN -1)
|
|
{
|
|
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(qview),
|
|
last_tree_path, NULL, FALSE, 0.0, 0.0);
|
|
}
|
|
gtk_tree_path_free (last_tree_path);
|
|
last_tree_path = NULL;
|
|
}
|
|
g_list_foreach (rr_list, (GFunc) gtk_tree_row_reference_free, NULL);
|
|
g_list_free (rr_list);
|
|
|
|
// Out of site toggles on selected rows may not appear correctly drawn so
|
|
// queue a draw for the treeview widget
|
|
gtk_widget_queue_draw (GTK_WIDGET(qview));
|
|
g_list_free (list_of_rows);
|
|
}
|
|
|
|
gint
|
|
gnc_reconcile_view_num_selected (GNCReconcileView *view )
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW(view);
|
|
GtkTreeSelection *selection;
|
|
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
|
|
return gtk_tree_selection_count_selected_rows (selection);
|
|
}
|
|
|
|
static gboolean
|
|
gnc_reconcile_view_set_toggle (GNCReconcileView *view)
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW(view);
|
|
GtkTreeSelection *selection;
|
|
GtkTreeModel *model;
|
|
gboolean toggled;
|
|
GList *node;
|
|
GList *list_of_rows;
|
|
gint num_toggled = 0;
|
|
gint num_selected = 0;
|
|
|
|
model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
|
|
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
|
|
list_of_rows = gtk_tree_selection_get_selected_rows (selection, &model);
|
|
num_selected = gtk_tree_selection_count_selected_rows (selection);
|
|
|
|
/* We get a list of TreePaths */
|
|
for (node = list_of_rows; node; node = node->next)
|
|
{
|
|
GtkTreeIter iter;
|
|
toggled = FALSE;
|
|
if (gtk_tree_model_get_iter (model, &iter, node->data))
|
|
{
|
|
/* now iter is a valid row iterator */
|
|
gtk_tree_model_get (model, &iter, REC_RECN, &toggled, -1);
|
|
|
|
if (toggled)
|
|
num_toggled++;
|
|
}
|
|
gtk_tree_path_free (node->data);
|
|
}
|
|
g_list_free (list_of_rows);
|
|
|
|
if (num_toggled == num_selected)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gnc_reconcile_view_key_press_cb (GtkWidget *widget, GdkEventKey *event,
|
|
gpointer user_data)
|
|
{
|
|
GNCReconcileView *view = GNC_RECONCILE_VIEW(user_data);
|
|
gboolean toggle;
|
|
|
|
switch (event->keyval)
|
|
{
|
|
case GDK_KEY_space:
|
|
g_signal_stop_emission_by_name (widget, "key_press_event");
|
|
|
|
toggle = gnc_reconcile_view_set_toggle (view);
|
|
gnc_reconcile_view_set_list (view, toggle);
|
|
return TRUE;
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_reconcile_view_finalize (GObject *object)
|
|
{
|
|
GNCReconcileView *view = GNC_RECONCILE_VIEW(object);
|
|
|
|
g_list_free (view->column_list);
|
|
if (view->reconciled != NULL)
|
|
{
|
|
g_hash_table_destroy (view->reconciled);
|
|
view->reconciled = NULL;
|
|
}
|
|
G_OBJECT_CLASS(parent_class)->finalize (object);
|
|
}
|
|
|
|
gint
|
|
gnc_reconcile_view_get_num_splits (GNCReconcileView *view)
|
|
{
|
|
g_return_val_if_fail (view != NULL, 0);
|
|
g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), 0);
|
|
|
|
return gnc_query_view_get_num_entries (GNC_QUERY_VIEW(view));
|
|
}
|
|
|
|
Split *
|
|
gnc_reconcile_view_get_current_split (GNCReconcileView *view)
|
|
{
|
|
g_return_val_if_fail (view != NULL, NULL);
|
|
g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), NULL);
|
|
|
|
return gnc_query_view_get_selected_entry (GNC_QUERY_VIEW(view));
|
|
}
|
|
|
|
/********************************************************************\
|
|
* gnc_reconcile_view_is_reconciled *
|
|
* Is the item a reconciled split? *
|
|
* *
|
|
* Args: item - the split to be checked *
|
|
* user_data - a pointer to the GNCReconcileView *
|
|
* Returns: whether the split is to be reconciled. *
|
|
\********************************************************************/
|
|
static gpointer
|
|
gnc_reconcile_view_is_reconciled (gpointer item, gpointer user_data)
|
|
{
|
|
GNCReconcileView *view = user_data;
|
|
Split *current;
|
|
|
|
g_return_val_if_fail (item, NULL);
|
|
g_return_val_if_fail (view, NULL);
|
|
g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), NULL);
|
|
|
|
if (!view->reconciled)
|
|
return NULL;
|
|
|
|
current = g_hash_table_lookup (view->reconciled, item);
|
|
return GINT_TO_POINTER(current != NULL);
|
|
}
|
|
|
|
/********************************************************************\
|
|
* gnc_reconcile_view_refresh *
|
|
* refreshes the view *
|
|
* *
|
|
* Args: view - view to refresh *
|
|
* Returns: nothing *
|
|
\********************************************************************/
|
|
static gboolean
|
|
grv_refresh_helper (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
GNCQueryView *qview = user_data;
|
|
|
|
return !gnc_query_view_item_in_view (qview, key);
|
|
}
|
|
|
|
void
|
|
gnc_reconcile_view_refresh (GNCReconcileView *view)
|
|
{
|
|
GNCQueryView *qview;
|
|
|
|
g_return_if_fail (view != NULL);
|
|
g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
|
|
|
|
qview = GNC_QUERY_VIEW(view);
|
|
gnc_query_view_refresh (qview);
|
|
|
|
/* Ensure last selected split, if any, can be seen */
|
|
gnc_query_force_scroll_to_selection (qview);
|
|
|
|
/* Now verify that everything in the reconcile hash is still in qview */
|
|
if (view->reconciled)
|
|
g_hash_table_foreach_remove (view->reconciled, grv_refresh_helper, qview);
|
|
}
|
|
|
|
/********************************************************************\
|
|
* gnc_reconcile_view_reconciled_balance *
|
|
* returns the reconciled balance of the view *
|
|
* *
|
|
* Args: view - view to get reconciled balance of *
|
|
* Returns: reconciled balance (gnc_numeric) *
|
|
\********************************************************************/
|
|
static void
|
|
grv_balance_hash_helper (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
Split *split = key;
|
|
gnc_numeric *total = user_data;
|
|
|
|
*total = gnc_numeric_add_fixed (*total, xaccSplitGetAmount (split));
|
|
}
|
|
|
|
gnc_numeric
|
|
gnc_reconcile_view_reconciled_balance (GNCReconcileView *view)
|
|
{
|
|
gnc_numeric total = gnc_numeric_zero ();
|
|
|
|
g_return_val_if_fail (view != NULL, total);
|
|
g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), total);
|
|
|
|
if (view->reconciled == NULL)
|
|
return total;
|
|
|
|
g_hash_table_foreach (view->reconciled, grv_balance_hash_helper, &total);
|
|
|
|
return gnc_numeric_abs (total);
|
|
}
|
|
|
|
/********************************************************************\
|
|
* gnc_reconcile_view_commit *
|
|
* Commit the reconcile information in the view. Only change the *
|
|
* state of those items marked as reconciled. All others should *
|
|
* retain their previous state (none, cleared, voided, etc.). *
|
|
* *
|
|
* Args: view - view to commit *
|
|
* date - date to set as the reconcile date *
|
|
* Returns: nothing *
|
|
\********************************************************************/
|
|
static void
|
|
grv_commit_hash_helper (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
Split *split = key;
|
|
time64 *date = user_data;
|
|
|
|
xaccSplitSetReconcile (split, YREC);
|
|
xaccSplitSetDateReconciledSecs (split, *date);
|
|
}
|
|
|
|
void
|
|
gnc_reconcile_view_commit (GNCReconcileView *view, time64 date)
|
|
{
|
|
g_return_if_fail (view != NULL);
|
|
g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
|
|
|
|
if (view->reconciled == NULL)
|
|
return;
|
|
|
|
gnc_suspend_gui_refresh ();
|
|
g_hash_table_foreach (view->reconciled, grv_commit_hash_helper, &date);
|
|
gnc_resume_gui_refresh ();
|
|
}
|
|
|
|
/********************************************************************\
|
|
* gnc_reconcile_view_postpone *
|
|
* postpone the reconcile information in the view by setting *
|
|
* reconciled splits to cleared status *
|
|
* *
|
|
* Args: view - view to commit *
|
|
* Returns: nothing *
|
|
\********************************************************************/
|
|
void
|
|
gnc_reconcile_view_postpone (GNCReconcileView *view)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
int num_splits;
|
|
int i;
|
|
gpointer entry = NULL;
|
|
|
|
g_return_if_fail (view != NULL);
|
|
g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
|
|
|
|
if (view->reconciled == NULL)
|
|
return;
|
|
|
|
model = gtk_tree_view_get_model (GTK_TREE_VIEW(GNC_QUERY_VIEW(view)));
|
|
gtk_tree_model_get_iter_first (model, &iter);
|
|
|
|
num_splits = gnc_query_view_get_num_entries (GNC_QUERY_VIEW(view));
|
|
|
|
gnc_suspend_gui_refresh ();
|
|
for (i = 0; i < num_splits; i++)
|
|
{
|
|
char recn;
|
|
|
|
gtk_tree_model_get (model, &iter, REC_POINTER, &entry, -1);
|
|
|
|
// Don't change splits past reconciliation date that haven't been
|
|
// set to be reconciled
|
|
if (gnc_difftime (view->statement_date,
|
|
xaccTransGetDate (xaccSplitGetParent (entry))) >= 0 ||
|
|
g_hash_table_lookup (view->reconciled, entry))
|
|
{
|
|
recn = g_hash_table_lookup (view->reconciled, entry) ? CREC : NREC;
|
|
xaccSplitSetReconcile (entry, recn);
|
|
}
|
|
gtk_tree_model_iter_next (model, &iter);
|
|
}
|
|
gnc_resume_gui_refresh ();
|
|
}
|
|
|
|
/********************************************************************\
|
|
* gnc_reconcile_view_unselect_all *
|
|
* unselect all splits in the view *
|
|
* *
|
|
* Args: view - view to unselect all *
|
|
* Returns: nothing *
|
|
\********************************************************************/
|
|
void
|
|
gnc_reconcile_view_unselect_all (GNCReconcileView *view)
|
|
{
|
|
g_return_if_fail (view != NULL);
|
|
g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
|
|
|
|
gnc_query_view_unselect_all (GNC_QUERY_VIEW(view));
|
|
}
|
|
|
|
/********************************************************************\
|
|
* gnc_reconcile_view_changed *
|
|
* returns true if any splits have been reconciled *
|
|
* *
|
|
* Args: view - view to get changed status for *
|
|
* Returns: true if any reconciled splits *
|
|
\********************************************************************/
|
|
gboolean
|
|
gnc_reconcile_view_changed (GNCReconcileView *view)
|
|
{
|
|
g_return_val_if_fail (view != NULL, FALSE);
|
|
g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), FALSE);
|
|
|
|
return g_hash_table_size (view->reconciled) != 0;
|
|
}
|