You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/src/gnome/reconcile-list.c

639 lines
18 KiB

/********************************************************************\
* reconcile-list.c -- A list of accounts to be reconciled for *
* GnuCash. *
* Copyright (C) 1998,1999 Jeremy Collins *
* Copyright (C) 1998-2000 Linas Vepstas *
* *
* 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 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
#include "top-level.h"
#include <gnome.h>
#include "gnucash.h"
#include "messages.h"
#include "Query.h"
#include "reconcile-listP.h"
#include "FileDialog.h"
#include "date.h"
#include "util.h"
static GtkCListClass *parent_class = NULL;
static guint reconcile_list_signals[LAST_SIGNAL] = {0};
GtkType
gnc_reconcile_list_get_type()
{
static GtkType gnc_reconcile_list_type = 0;
if (!gnc_reconcile_list_type)
{
static const GtkTypeInfo gnc_reconcile_list_info =
{
"GNCReconcileList",
sizeof (GNCReconcileList),
sizeof (GNCReconcileListClass),
(GtkClassInitFunc) gnc_reconcile_list_class_init,
(GtkObjectInitFunc) gnc_reconcile_list_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL
};
gnc_reconcile_list_type = gtk_type_unique(GTK_TYPE_CLIST,
&gnc_reconcile_list_info);
}
return gnc_reconcile_list_type;
}
/********************************************************************\
* gnc_reconcile_list_new *
* creates the account tree *
* *
* Args: account - the account to use in filling up the splits. *
* type - the type of list, RECLIST_DEBIT or RECLIST_CREDIT*
* Returns: the account tree widget, or NULL if there was a problem.*
\********************************************************************/
GtkWidget *
gnc_reconcile_list_new(Account *account, GNCReconcileListType type)
{
GNCReconcileList *list;
assert(account != NULL);
assert((type == RECLIST_DEBIT) || (type == RECLIST_CREDIT));
list = GNC_RECONCILE_LIST(gtk_type_new(gnc_reconcile_list_get_type()));
list->account = account;
list->list_type = type;
list->query = xaccMallocQuery();
xaccQuerySetGroup(list->query, gncGetCurrentGroup());
/* match the account */
xaccQueryAddSingleAccountMatch(list->query, account, QUERY_OR);
if (type == RECLIST_CREDIT)
xaccQueryAddAmountMatch(list->query, 0.0, AMT_SGN_MATCH_CREDIT,
AMT_MATCH_ATLEAST, QUERY_AND);
else
xaccQueryAddAmountMatch(list->query, 0.0, AMT_SGN_MATCH_DEBIT,
AMT_MATCH_ATLEAST, QUERY_AND);
return GTK_WIDGET(list);
}
static void
gnc_reconcile_list_init(GNCReconcileList *list)
{
GtkCList *clist = GTK_CLIST(list);
gchar * titles[] =
{
DATE_STR,
NUM_STR,
DESC_STR,
AMT_STR,
"?",
NULL
};
list->num_splits = 0;
list->num_columns = 0;
list->reconciled = g_hash_table_new(NULL, NULL);
list->current_row = -1;
list->current_split = NULL;
list->no_toggle = FALSE;
list->always_unselect = FALSE;
list->query = NULL;
while (titles[list->num_columns] != NULL)
list->num_columns++;
gtk_clist_construct(clist, list->num_columns, titles);
gtk_clist_set_shadow_type (clist, GTK_SHADOW_IN);
gtk_clist_set_column_justification(clist, 3, GTK_JUSTIFY_RIGHT);
gtk_clist_set_column_justification(clist, 4, GTK_JUSTIFY_CENTER);
gtk_clist_column_titles_passive(clist);
{
GtkStyle *st = gtk_widget_get_style(GTK_WIDGET(list));
GdkFont *font = NULL;
gint width;
gint i;
if (st != NULL)
font = st->font;
if (font != NULL)
for (i = 0; i < list->num_columns; i++)
{
width = gdk_string_width(font, titles[i]);
gtk_clist_set_column_min_width(GTK_CLIST(list), i, width + 5);
if (i == 4)
gtk_clist_set_column_max_width(GTK_CLIST(list), i, width + 5);
}
}
{
GdkColormap *cm = gtk_widget_get_colormap(GTK_WIDGET(list));
GtkStyle *style = gtk_widget_get_style(GTK_WIDGET(list));
list->reconciled_style = gtk_style_copy(style);
#if !USE_NO_COLOR
style = list->reconciled_style;
/* A dark green */
style->fg[GTK_STATE_NORMAL].red = 0;
style->fg[GTK_STATE_NORMAL].green = 40000;
style->fg[GTK_STATE_NORMAL].blue = 0;
gdk_colormap_alloc_color(cm, &style->fg[GTK_STATE_NORMAL], FALSE, TRUE);
/* A nice yellow */
style->fg[GTK_STATE_SELECTED].red = 65280;
style->fg[GTK_STATE_SELECTED].green = 62976;
style->fg[GTK_STATE_SELECTED].blue = 36608;
gdk_colormap_alloc_color(cm, &style->fg[GTK_STATE_SELECTED], FALSE, TRUE);
#endif
}
}
static void
gnc_reconcile_list_class_init(GNCReconcileListClass *klass)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkContainerClass *container_class;
GtkCListClass *clist_class;
object_class = (GtkObjectClass*) klass;
widget_class = (GtkWidgetClass*) klass;
container_class = (GtkContainerClass*) klass;
clist_class = (GtkCListClass*) klass;
parent_class = gtk_type_class(GTK_TYPE_CLIST);
reconcile_list_signals[TOGGLE_RECONCILED] =
gtk_signal_new("toggle_reconciled",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET(GNCReconcileListClass,
toggle_reconciled),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1,
GTK_TYPE_POINTER);
gtk_object_class_add_signals(object_class,
reconcile_list_signals,
LAST_SIGNAL);
object_class->destroy = gnc_reconcile_list_destroy;
clist_class->select_row = gnc_reconcile_list_select_row;
clist_class->unselect_row = gnc_reconcile_list_unselect_row;
klass->toggle_reconciled = NULL;
}
static void
gnc_reconcile_list_set_row_style(GNCReconcileList *list, gint row,
gboolean reconciled)
{
if (reconciled)
gtk_clist_set_cell_style(GTK_CLIST(list), row, 4, list->reconciled_style);
else
gtk_clist_set_cell_style(GTK_CLIST(list), row, 4,
gtk_widget_get_style(GTK_WIDGET(list)));
}
static void
gnc_reconcile_list_toggle(GNCReconcileList *list)
{
Split *split, *current;
char recn_str[2];
gboolean reconciled;
char recn;
gint row;
assert(IS_GNC_RECONCILE_LIST(list));
assert(list->reconciled != NULL);
if (list->no_toggle)
return;
row = list->current_row;
split = gtk_clist_get_row_data(GTK_CLIST(list), row);
current = g_hash_table_lookup(list->reconciled, split);
list->current_split = split;
if (current == NULL)
{
reconciled = TRUE;
g_hash_table_insert(list->reconciled, split, split);
}
else
{
reconciled = FALSE;
g_hash_table_remove(list->reconciled, split);
}
recn = xaccSplitGetReconcile(split);
g_snprintf(recn_str, 2, "%c", reconciled ? YREC : recn);
gtk_clist_set_text(GTK_CLIST(list), row, 4, recn_str);
gnc_reconcile_list_set_row_style(list, row, reconciled);
gtk_signal_emit(GTK_OBJECT(list),
reconcile_list_signals[TOGGLE_RECONCILED],
split);
}
static void
gnc_reconcile_list_select_row(GtkCList *clist, gint row, gint column,
GdkEvent *event)
{
GNCReconcileList *list = GNC_RECONCILE_LIST(clist);
list->current_row = row;
gnc_reconcile_list_toggle(list);
GTK_CLIST_CLASS(parent_class)->select_row(clist, row, column, event);
}
static void
gnc_reconcile_list_unselect_row(GtkCList *clist, gint row, gint column,
GdkEvent *event)
{
GNCReconcileList *list = GNC_RECONCILE_LIST(clist);
if (row == list->current_row)
{
gnc_reconcile_list_toggle(list);
if (!list->always_unselect)
return;
}
GTK_CLIST_CLASS(parent_class)->unselect_row(clist, row, column, event);
}
static void
gnc_reconcile_list_destroy(GtkObject *object)
{
GNCReconcileList *list = GNC_RECONCILE_LIST(object);
if (list->reconciled_style != NULL)
{
gtk_style_unref(list->reconciled_style);
list->reconciled_style = NULL;
}
if (list->reconciled != NULL)
{
g_hash_table_destroy(list->reconciled);
list->reconciled = NULL;
}
if (list->query != NULL)
{
xaccFreeQuery(list->query);
list->query = NULL;
}
if (GTK_OBJECT_CLASS(parent_class)->destroy)
(* GTK_OBJECT_CLASS(parent_class)->destroy) (object);
}
gint
gnc_reconcile_list_get_needed_height(GNCReconcileList *list, gint num_rows)
{
GtkCList *clist;
gint list_height;
gint title_height;
g_return_val_if_fail(list != NULL, 0);
g_return_val_if_fail(IS_GNC_RECONCILE_LIST(list), 0);
if (!GTK_WIDGET_REALIZED(list))
return 0;
clist = GTK_CLIST(list);
/* sync with gtkclist.c */
title_height = (clist->column_title_area.height +
(GTK_WIDGET(list)->style->klass->ythickness +
GTK_CONTAINER(list)->border_width) * 2);
list_height = (clist->row_height * num_rows) + (num_rows + 1);
return title_height + list_height;
}
gint
gnc_reconcile_list_get_num_splits(GNCReconcileList *list)
{
g_return_val_if_fail(list != NULL, 0);
g_return_val_if_fail(IS_GNC_RECONCILE_LIST(list), 0);
return list->num_splits;
}
Split *
gnc_reconcile_list_get_current_split(GNCReconcileList *list)
{
g_return_val_if_fail(list != NULL, NULL);
g_return_val_if_fail(IS_GNC_RECONCILE_LIST(list), NULL);
return list->current_split;
}
/********************************************************************\
* gnc_reconcile_list_refresh *
* refreshes the list *
* *
* Args: list - list to refresh *
* Returns: nothing *
\********************************************************************/
void
gnc_reconcile_list_refresh(GNCReconcileList *list)
{
GtkCList *clist = GTK_CLIST(list);
GtkAdjustment *adjustment;
gfloat save_value = 0.0;
Split *old_split;
gint new_row;
g_return_if_fail(list != NULL);
g_return_if_fail(IS_GNC_RECONCILE_LIST(list));
adjustment = gtk_clist_get_vadjustment(GTK_CLIST(list));
if (adjustment != NULL)
save_value = adjustment->value;
gtk_clist_freeze(clist);
gtk_clist_clear(clist);
old_split = list->current_split;
list->num_splits = 0;
list->current_row = -1;
list->current_split = NULL;
gnc_reconcile_list_fill(list);
gtk_clist_columns_autosize(clist);
if (adjustment != NULL)
{
save_value = CLAMP(save_value, adjustment->lower, adjustment->upper);
gtk_adjustment_set_value(adjustment, save_value);
}
if (old_split != NULL)
{
new_row = gtk_clist_find_row_from_data(clist, old_split);
if (new_row >= 0)
{
list->no_toggle = TRUE;
gtk_clist_select_row(clist, new_row, 0);
list->no_toggle = FALSE;
list->current_split = old_split;
}
}
gtk_clist_thaw(clist);
}
/********************************************************************\
* gnc_reconcile_list_reconciled_balance *
* returns the reconciled balance of the list *
* *
* Args: list - list to get reconciled balance of *
* Returns: reconciled balance (double) *
\********************************************************************/
double
gnc_reconcile_list_reconciled_balance(GNCReconcileList *list)
{
GtkCList *clist = GTK_CLIST(list);
Split *split;
double total = 0.0;
int account_type;
int i;
g_return_val_if_fail(list != NULL, 0.0);
g_return_val_if_fail(IS_GNC_RECONCILE_LIST(list), 0.0);
if (list->reconciled == NULL)
return 0.0;
account_type = xaccAccountGetType(list->account);
for (i = 0; i < list->num_splits; i++)
{
split = gtk_clist_get_row_data(clist, i);
if (g_hash_table_lookup(list->reconciled, split) == NULL)
continue;
if ((account_type == STOCK) || (account_type == MUTUAL) ||
(account_type == CURRENCY))
total += xaccSplitGetShareAmount(split);
else
total += xaccSplitGetValue(split);
}
return DABS(total);
}
/********************************************************************\
* gnc_reconcile_list_commit *
* commit the reconcile information in the list *
* *
* Args: list - list to commit *
* date - date to set as the reconcile date *
* Returns: nothing *
\********************************************************************/
void
gnc_reconcile_list_commit(GNCReconcileList *list, time_t date)
{
GtkCList *clist = GTK_CLIST(list);
Split *split;
int i;
g_return_if_fail(list != NULL);
g_return_if_fail(IS_GNC_RECONCILE_LIST(list));
if (list->reconciled == NULL)
return;
for (i = 0; i < list->num_splits; i++)
{
split = gtk_clist_get_row_data(clist, i);
if (g_hash_table_lookup(list->reconciled, split) != NULL)
{
xaccSplitSetReconcile(split, YREC);
xaccSplitSetDateReconciledSecs(split, date);
}
}
}
/********************************************************************\
* gnc_reconcile_list_unselect_all *
* unselect all splits in the list *
* *
* Args: list - list to unselect all *
* Returns: nothing *
\********************************************************************/
void
gnc_reconcile_list_unselect_all(GNCReconcileList *list)
{
g_return_if_fail(list != NULL);
g_return_if_fail(IS_GNC_RECONCILE_LIST(list));
list->no_toggle = TRUE;
list->always_unselect = TRUE;
gtk_clist_unselect_all(GTK_CLIST(list));
list->always_unselect = FALSE;
list->no_toggle = FALSE;
list->current_split = NULL;
}
/********************************************************************\
* gnc_reconcile_list_changed *
* returns true if any splits have been reconciled *
* *
* Args: list - list to get changed status for *
* Returns: true if any reconciled splits *
\********************************************************************/
gboolean
gnc_reconcile_list_changed(GNCReconcileList *list)
{
g_return_val_if_fail(list != NULL, FALSE);
g_return_val_if_fail(IS_GNC_RECONCILE_LIST(list), FALSE);
return g_hash_table_size(list->reconciled) != 0;
}
/********************************************************************\
* gnc_reconcile_list_set_sort_order *
* sets the sorting order of splits in the list *
* *
* Args: list - list to change the sort order for *
* Returns: nothing *
\********************************************************************/
void
gnc_reconcile_list_set_sort_order(GNCReconcileList *list, sort_type_t key)
{
g_return_if_fail(list != NULL);
g_return_if_fail(IS_GNC_RECONCILE_LIST(list));
g_return_if_fail(list->query != NULL);
xaccQuerySetSortOrder(list->query, key,
(key == BY_STANDARD) ? BY_NONE : BY_STANDARD,
BY_NONE);
if (list->list_type == RECLIST_DEBIT)
return;
xaccQuerySetSortIncreasing(list->query, !(key == BY_AMOUNT));
}
static void
gnc_reconcile_list_fill(GNCReconcileList *list)
{
gchar *strings[list->num_columns + 1];
GNCPrintAmountFlags flags = PRTSEP;
GNCAccountType account_type;
gboolean reconciled;
Transaction *trans;
Split **splits;
Split *split;
const char *currency;
char recn_str[2];
char recn;
double amount;
int row;
account_type = xaccAccountGetType(list->account);
currency = xaccAccountGetCurrency(list->account);
strings[4] = recn_str;
strings[5] = NULL;
if ((account_type == STOCK) || (account_type == MUTUAL) ||
(account_type == CURRENCY))
flags |= PRTSHR;
splits = xaccQueryGetSplits(list->query);
for ( ; *splits != NULL; splits++)
{
split = *splits;
recn = xaccSplitGetReconcile(split);
if ((recn != NREC) && (recn != CREC))
continue;
if((account_type == STOCK) || (account_type == MUTUAL))
amount = xaccSplitGetShareAmount(split);
else
amount = xaccSplitGetValue(split);
if ((amount < 0) && (list->list_type == RECLIST_DEBIT))
continue;
if ((amount >= 0) && (list->list_type == RECLIST_CREDIT))
continue;
trans = xaccSplitGetParent(split);
strings[0] = xaccTransGetDateStr(trans);
strings[1] = (char *) xaccTransGetNum(trans);
strings[2] = (char *) xaccTransGetDescription(trans);
strings[3] = xaccPrintAmount(DABS(amount), flags, currency);
reconciled = g_hash_table_lookup(list->reconciled, split) != NULL;
g_snprintf(recn_str, 2, "%c", reconciled ? YREC : recn);
row = gtk_clist_append(GTK_CLIST(list), strings);
gtk_clist_set_row_data(GTK_CLIST(list), row, (gpointer) split);
gnc_reconcile_list_set_row_style(list, row, reconciled);
list->num_splits++;
}
}