This commit was manufactured by cvs2svn to create branch '1.8'.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/branches/1.8@8452 57a11ea4-9604-0410-9ed3-97b8803252fd
zzzoldreleases/1.8
unknown user 23 years ago
parent 80ad9bfeab
commit 698fce624f

@ -0,0 +1,888 @@
/********************************************************************\
* gnc-query-list.c -- A query display list. *
* Copyright (C) 2003 Derek Atkins <derek@ihtfp.com> *
* *
* 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 "config.h"
#include <gnome.h>
#include "dialog-utils.h"
#include "gnc-ui-util.h"
#include "gnc-engine-util.h"
#include "gnc-component-manager.h"
#include "messages.h"
#include "gnc-query-list.h"
#include "search-param.h"
#include "QueryObject.h"
/* Signal codes */
enum
{
LINE_TOGGLED,
DOUBLE_CLICK_ENTRY,
LAST_SIGNAL
};
struct _GNCQueryListPriv {
QueryAccess get_guid;
gint component_id;
};
/* Impossible to get at runtime. Assume this is a reasonable number */
#define ARROW_SIZE 14
#define VSCROLLBAR_SLOP 40
/** Static Globals ****************************************************/
static GtkCListClass *parent_class = NULL;
static guint query_list_signals[LAST_SIGNAL] = {0};
/** Static function declarations **************************************/
static void gnc_query_list_init(GNCQueryList *list);
static void gnc_query_list_init_clist (GNCQueryList *list);
static void gnc_query_list_class_init(GNCQueryListClass *klass);
static void gnc_query_list_select_row(GtkCList *clist, gint row,
gint column, GdkEvent *event);
static void gnc_query_list_unselect_row(GtkCList *clist, gint row,
gint column, GdkEvent *event);
static void gnc_query_list_destroy(GtkObject *object);
static void gnc_query_list_fill(GNCQueryList *list);
static void gnc_query_list_click_column_cb(GtkWidget *w, gint column,
gpointer data);
static void gnc_query_list_size_allocate_cb(GtkWidget *w,
GtkAllocation *allocation,
gpointer data);
static void gnc_query_list_set_query_sort (GNCQueryList *list, gboolean new_column);
GtkType
gnc_query_list_get_type (void)
{
static GtkType gnc_query_list_type = 0;
if (!gnc_query_list_type)
{
static const GtkTypeInfo gnc_query_list_info =
{
"GNCQueryList",
sizeof (GNCQueryList),
sizeof (GNCQueryListClass),
(GtkClassInitFunc) gnc_query_list_class_init,
(GtkObjectInitFunc) gnc_query_list_init,
/* reserved_1 */ NULL,
/* reserved_2 */ NULL,
(GtkClassInitFunc) NULL
};
gnc_query_list_type = gtk_type_unique(GTK_TYPE_CLIST,
&gnc_query_list_info);
}
return gnc_query_list_type;
}
/********************************************************************\
* gnc_query_list_new *
* creates the query list *
* *
* Args: param_list - the list of params *
* query - the query to use to find entries *
* Returns: the query list widget, or NULL if there was a problem. *
\********************************************************************/
void
gnc_query_list_construct (GNCQueryList *list, GList *param_list, Query *query)
{
g_return_if_fail(list);
g_return_if_fail(param_list);
g_return_if_fail(query);
g_return_if_fail(IS_GNC_QUERY_LIST(list));
/* more configuration */
list->query = gncQueryCopy(query);
list->column_params = param_list;
/* cache the function to get the guid of this query type */
list->priv->get_guid =
gncQueryObjectGetParameterGetter (gncQueryGetSearchFor (query),
QUERY_PARAM_GUID);
/* Initialize the CList */
gnc_query_list_init_clist(list);
}
GtkWidget *
gnc_query_list_new(GList *param_list, Query *query)
{
GNCQueryList *list;
g_return_val_if_fail(param_list, NULL);
g_return_val_if_fail(query, NULL);
list = GNC_QUERY_LIST(gtk_type_new(gnc_query_list_get_type()));
gnc_query_list_construct(list, param_list, query);
return GTK_WIDGET(list);
}
void gnc_query_list_reset_query (GNCQueryList *list, Query *query)
{
g_return_if_fail(list);
g_return_if_fail(query);
g_return_if_fail (IS_GNC_QUERY_LIST(list));
gncQueryDestroy(list->query);
list->query = gncQueryCopy(query);
gnc_query_list_set_query_sort(list, TRUE);
}
static void
update_booleans (GNCQueryList *list, gint row)
{
GtkCList *clist = GTK_CLIST(list);
gpointer entry;
GList *node;
gint i;
gboolean result;
entry = gtk_clist_get_row_data (clist, row);
for (i = 0, node = list->column_params; node; node = node->next, i++)
{
GNCSearchParam *param = node->data;
const char *type = gnc_search_param_get_param_type (param);
/* if this is a boolean, ignore it now -- we'll use a checkmark later */
if (safe_strcmp (type, QUERYCORE_BOOLEAN))
continue;
result = (gboolean)(gnc_search_param_compute_value(param, entry));
gnc_clist_set_check (clist, row, i, result);
}
}
static void
gnc_query_list_column_title (GNCQueryList *list, gint column, const gchar *title)
{
GtkWidget *hbox, *label, *arrow;
hbox = gtk_hbox_new(FALSE, 2);
gtk_widget_show(hbox);
gtk_clist_set_column_widget(GTK_CLIST(list), column, hbox);
label = gtk_label_new(title);
gtk_widget_show(label);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_ETCHED_IN);
list->title_arrows[column] = arrow;
if (column == 0)
gtk_widget_show(arrow);
gtk_box_pack_end(GTK_BOX(hbox), arrow, FALSE, FALSE, 0);
}
static void
gnc_query_list_refresh_handler (GHashTable *changes, gpointer user_data)
{
GNCQueryList *list = (GNCQueryList *)user_data;
g_return_if_fail (IS_GNC_QUERY_LIST(list));
gnc_query_list_refresh (list);
}
static void
gnc_query_list_init (GNCQueryList *list)
{
list->query = NULL;
list->no_toggle = FALSE;
list->always_unselect = FALSE;
list->num_columns = 0;
list->column_params = NULL;
list->sort_column = 0;
list->increasing = TRUE;
list->title_arrows = NULL;
list->prev_allocation = -1;
list->title_widths = NULL;
list->numeric_abs = FALSE;
list->numeric_inv_sort = FALSE;
list->priv = g_new0(GNCQueryListPriv, 1);
list->priv->component_id =
gnc_register_gui_component ("gnc-query-list-cm-class",
gnc_query_list_refresh_handler,
NULL, list);
}
static void
gnc_query_list_init_clist (GNCQueryList *list)
{
GtkCList *clist = GTK_CLIST (list);
GtkStyle *style;
GList *node;
gchar **titles;
gint i;
/* compute the number of columns and fill in the rest of the list */
list->num_columns = g_list_length(list->column_params);
list->title_arrows = g_new0(GtkWidget*, list->num_columns);
list->title_widths = g_new0(gint, list->num_columns);
/* build an array of titles */
titles = g_new0(gchar*, list->num_columns);
for (i = 0, node = list->column_params; node; node = node->next, i++) {
GNCSearchParam *param = node->data;
titles[i] = (gchar *)param->title;
}
/* construct the clist */
gtk_clist_construct (clist, list->num_columns, titles);
gtk_clist_set_shadow_type (clist, GTK_SHADOW_IN);
/* build all the column titles */
for (i = 0; i < list->num_columns; i++)
gnc_query_list_column_title(list, i, titles[i]);
/* set the column justification */
for (i = 0, node = list->column_params; node; node = node->next, i++) {
GNCSearchParam *param = node->data;
gtk_clist_set_column_justification (clist, i, param->justify);
if (param->passive)
gtk_clist_column_title_passive (clist, i);
if (param->non_resizeable)
gtk_clist_set_column_resizeable (clist, i, FALSE);
}
gtk_signal_connect (GTK_OBJECT (clist), "click_column",
GTK_SIGNAL_FUNC(gnc_query_list_click_column_cb),
NULL);
gtk_signal_connect (GTK_OBJECT (clist), "size_allocate",
GTK_SIGNAL_FUNC(gnc_query_list_size_allocate_cb),
NULL);
style = gtk_widget_get_style (GTK_WIDGET(list));
{
GdkFont *font = NULL;
gint width;
font = style->font;
if (font != NULL)
{
for (i = 0, node = list->column_params; node; node = node->next, i++)
{
GNCSearchParam *param = node->data;
width = gdk_string_width (font, titles[i]) + 5;
if (!param->passive)
width += ARROW_SIZE;
gtk_clist_set_column_min_width (clist, i, width);
list->title_widths[i] = width;
}
}
}
g_free(titles);
}
static void
gnc_query_list_class_init (GNCQueryListClass *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);
query_list_signals[LINE_TOGGLED] =
gtk_signal_new("line_toggled",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET(GNCQueryListClass,
line_toggled),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1,
GTK_TYPE_POINTER);
query_list_signals[DOUBLE_CLICK_ENTRY] =
gtk_signal_new("double_click_entry",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET(GNCQueryListClass,
double_click_entry),
gtk_marshal_NONE__POINTER,
GTK_TYPE_NONE, 1,
GTK_TYPE_POINTER);
gtk_object_class_add_signals(object_class,
query_list_signals,
LAST_SIGNAL);
object_class->destroy = gnc_query_list_destroy;
clist_class->select_row = gnc_query_list_select_row;
clist_class->unselect_row = gnc_query_list_unselect_row;
klass->line_toggled = NULL;
klass->double_click_entry = NULL;
}
static void
gnc_query_list_toggle (GNCQueryList *list)
{
gpointer entry;
gint row;
g_return_if_fail (IS_GNC_QUERY_LIST(list));
if (list->no_toggle)
return;
row = list->current_row;
entry = gtk_clist_get_row_data (GTK_CLIST(list), row);
list->current_entry = entry;
gtk_signal_emit (GTK_OBJECT (list), query_list_signals[LINE_TOGGLED], entry);
update_booleans (list, row);
}
static void
gnc_query_list_select_row (GtkCList *clist, gint row, gint column,
GdkEvent *event)
{
GNCQueryList *list = GNC_QUERY_LIST(clist);
list->current_row = row;
gnc_query_list_toggle (list);
if (event == NULL) {
/* User pressed the space key */
parent_class->scroll_vertical(clist, GTK_SCROLL_STEP_FORWARD, 0.0);
}
/* This will trigger an unselect event for the currently selected row */
parent_class->select_row (clist, row, column, event);
if (event && (event->type == GDK_2BUTTON_PRESS))
{
gpointer entry;
entry = gtk_clist_get_row_data (clist, row);
gtk_signal_emit(GTK_OBJECT(list),
query_list_signals[DOUBLE_CLICK_ENTRY], entry);
}
}
static void
gnc_query_list_unselect_row (GtkCList *clist, gint row, gint column,
GdkEvent *event)
{
GNCQueryList *list = GNC_QUERY_LIST(clist);
if (row == list->current_row)
{
gnc_query_list_toggle (list);
if (event == NULL) {
/* User pressed the space key */
parent_class->scroll_vertical(clist, GTK_SCROLL_STEP_FORWARD, 0.0);
}
if (!list->always_unselect)
return;
}
parent_class->unselect_row (clist, row, column, event);
if (event && (event->type == GDK_2BUTTON_PRESS))
{
gpointer entry;
entry = gtk_clist_get_row_data (clist, row);
gtk_signal_emit (GTK_OBJECT(list),
query_list_signals[DOUBLE_CLICK_ENTRY], entry);
}
}
static void
gnc_query_list_destroy (GtkObject *object)
{
GNCQueryList *list = GNC_QUERY_LIST(object);
if (list->priv && list->priv->component_id >= 0)
gnc_unregister_gui_component (list->priv->component_id);
if (list->priv)
{
g_free (list->priv);
list->priv = NULL;
}
if (list->query)
{
xaccFreeQuery(list->query);
list->query = NULL;
}
if (list->column_params)
{
/* XXX: free the params list??? */
}
if (list->title_arrows)
{
g_free(list->title_arrows);
list->title_arrows = NULL;
}
if (list->title_widths)
{
g_free(list->title_widths);
list->title_widths = NULL;
}
if (GTK_OBJECT_CLASS(parent_class)->destroy)
GTK_OBJECT_CLASS(parent_class)->destroy (object);
}
gint
gnc_query_list_get_needed_height (GNCQueryList *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_QUERY_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_query_list_get_num_entries (GNCQueryList *list)
{
g_return_val_if_fail (list != NULL, 0);
g_return_val_if_fail (IS_GNC_QUERY_LIST(list), 0);
return list->num_entries;
}
gpointer
gnc_query_list_get_current_entry (GNCQueryList *list)
{
g_return_val_if_fail (list != NULL, NULL);
g_return_val_if_fail (IS_GNC_QUERY_LIST(list), NULL);
return list->current_entry;
}
/********************************************************************\
* gnc_query_list_recompute_widths *
* Given a new widget width, recompute the widths of each column. *
* Give any excess allocation to the description field. This also *
* handles the case of allocating column widths when the list is *
* first filled with data. *
* *
* Args: list - a GncQueryList widget *
* allocated - the allocated width for this list *
* Returns: nothing *
\********************************************************************/
static void
gnc_query_list_recompute_widths (GNCQueryList *list, gint allocated)
{
GtkCList *clist = GTK_CLIST(list);
gint total_width, desc_width = 0, excess, i;
/* Prevent loops when allocation is bigger than total widths */
if (allocated == list->prev_allocation)
return;
/* Enforce column minimum widths */
total_width = 0;
for (i = 0; i < list->num_columns; i++)
{
gint width;
width = gtk_clist_optimal_column_width(clist, i);
if (width < list->title_widths[i])
width = list->title_widths[i];
total_width += width;
gtk_clist_set_column_width (clist, i, width);
if (i == 2)
desc_width = width;
}
/* Did the list use its full allocation?
*
* Add/subtract any underage/overage to/from the description column
*/
if (allocated <= 1)
allocated = list->prev_allocation;
list->prev_allocation = allocated;
excess = allocated - total_width - VSCROLLBAR_SLOP;
/* XXX: Choose a generic column to resize */
gtk_clist_set_column_width (clist, 2, desc_width + excess);
}
/********************************************************************\
* gnc_query_list_size_allocate_cb *
* The allocated size has changed. Need to recompute the *
* column widths *
* *
* Args: w - a GncQueryList widget *
* allocation - a widget allocation desctiption *
* data - unused *
* Returns: nothing *
\********************************************************************/
static void
gnc_query_list_size_allocate_cb (GtkWidget *w,
GtkAllocation *allocation,
gpointer data)
{
GNCQueryList *list = GNC_QUERY_LIST(w);
g_return_if_fail (list != NULL);
gnc_query_list_recompute_widths(list, allocation->width);
}
/********************************************************************\
* gnc_query_list_refresh *
* refreshes the list *
* *
* Args: list - list to refresh *
* Returns: nothing *
\********************************************************************/
void
gnc_query_list_refresh (GNCQueryList *list)
{
GtkCList *clist = GTK_CLIST(list);
GtkAdjustment *adjustment;
gfloat save_value = 0.0;
gpointer *old_focus_entry;
gpointer *old_entry;
gint old_focus_row;
gint new_row;
g_return_if_fail (list != NULL);
g_return_if_fail (IS_GNC_QUERY_LIST(list));
adjustment = gtk_clist_get_vadjustment (GTK_CLIST(list));
if (adjustment != NULL)
save_value = adjustment->value;
old_focus_row = clist->focus_row;
old_focus_entry = gtk_clist_get_row_data (clist, old_focus_row);
gtk_clist_freeze (clist);
gtk_clist_clear (clist);
old_entry = list->current_entry;
list->num_entries = 0;
list->current_row = -1;
list->current_entry = NULL;
gnc_query_list_fill (list);
gnc_query_list_recompute_widths (list, -1);
if (adjustment)
{
save_value = CLAMP (save_value, adjustment->lower, adjustment->upper);
gtk_adjustment_set_value (adjustment, save_value);
}
if (old_entry)
{
new_row = gtk_clist_find_row_from_data (clist, old_entry);
if (new_row >= 0)
{
list->no_toggle = TRUE;
gtk_clist_select_row (clist, new_row, 0);
list->no_toggle = FALSE;
list->current_entry = old_entry;
}
}
if (old_focus_entry)
{
new_row = gtk_clist_find_row_from_data (clist, old_focus_entry);
if (new_row < 0)
new_row = old_focus_row;
if (new_row >= 0)
clist->focus_row = new_row;
}
gtk_clist_thaw (clist);
}
/********************************************************************\
* gnc_query_list_set_query_sort *
* sets the sorting order of entries in the list *
* *
* Args: list - list 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_list_set_query_sort (GNCQueryList *list, gboolean new_column)
{
gboolean sort_order = list->increasing;
GList *node;
GNCSearchParam *param;
/* Find the column parameter definition */
node = g_list_nth(list->column_params, list->sort_column);
param = node->data;
/* If we're asked to invert numerics, and if this is a numeric or
* debred column, then invert the sort order.
*/
if (list->numeric_inv_sort) {
const char *type = gnc_search_param_get_param_type (param);
if (!safe_strcmp(type, QUERYCORE_NUMERIC) ||
!safe_strcmp(type, QUERYCORE_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);
gncQuerySetSortOrder (list->query, p1, p2, NULL);
}
xaccQuerySetSortIncreasing (list->query,
sort_order,
sort_order,
sort_order);
/*
* Recompute the list. Is this really necessary? Why not just sort
* the rows already in the clist? Answer: it would be an n-squared
* algorithm to get the clist to match the resulting list.
*/
gnc_query_list_refresh(list);
}
/********************************************************************\
* gnc_query_list_set_sort_column *
* sets the sorting order of entries in the list *
* *
* Args: list - list to change the sort order for *
* column - the column to sort on *
* Returns: nothing *
\********************************************************************/
static void
gnc_query_list_set_sort_column (GNCQueryList *list, gint sort_column)
{
gint column;
gboolean new_column = FALSE;
g_return_if_fail (list != NULL);
g_return_if_fail (IS_GNC_QUERY_LIST(list));
g_return_if_fail (list->query != NULL);
/* Clear all arrows */
for (column = 0; column < list->num_columns; column++)
{
if (list->title_arrows[column])
gtk_widget_hide(list->title_arrows[column]);
}
/* Is this a new column or a re-click on the existing column? */
column = sort_column;
new_column = (list->sort_column != sort_column);
list->increasing = new_column ? TRUE : !list->increasing;
list->sort_column = sort_column;
/* Set the appropriate arrow */
gtk_arrow_set(GTK_ARROW(list->title_arrows[column]),
list->increasing ? GTK_ARROW_DOWN : GTK_ARROW_UP,
GTK_SHADOW_ETCHED_IN);
gtk_widget_show(list->title_arrows[column]);
gnc_query_list_set_query_sort (list, new_column);
}
static void
gnc_query_list_click_column_cb(GtkWidget *w, gint column, gpointer data)
{
GNCQueryList *list = GNC_QUERY_LIST(w);
gnc_query_list_set_sort_column(list, column);
}
static void
gnc_query_list_fill(GNCQueryList *list)
{
gchar *strings[list->num_columns + 1];
GList *entries, *item;
const GUID *guid;
gint i;
/* Clear all watches */
gnc_gui_component_clear_watches (list->priv->component_id);
/* Reverse the list now because 'append()' takes too long */
entries = gncQueryRun(list->query);
for (item = entries; item; item = item->next)
{
GList *node;
gint row;
for (i = 0, node = list->column_params; node; node = node->next)
{
GNCSearchParam *param = node->data;
GSList *converters = gnc_search_param_get_converters (param);
const char *type = gnc_search_param_get_param_type (param);
gpointer res = item->data;
QueryAccess fcn = NULL;
/* if this is a boolean, ignore it now -- we'll use a checkmark later */
if (!safe_strcmp (type, QUERYCORE_BOOLEAN)) {
strings[i++] = g_strdup("");
continue;
}
/* Do all the object conversions */
for (; converters; converters = converters->next) {
fcn = converters->data;
if (converters->next)
res = fcn (res);
}
/* Now convert this to a text value for the row */
if (!safe_strcmp(type, QUERYCORE_DEBCRED) ||
!safe_strcmp(type, QUERYCORE_NUMERIC))
{
gnc_numeric (*nfcn)(gpointer) = (gnc_numeric(*)(gpointer))fcn;
gnc_numeric value = nfcn(res);
if (list->numeric_abs)
value = gnc_numeric_abs (value);
strings[i++] = g_strdup(xaccPrintAmount(value,gnc_default_print_info(FALSE)));
} else
strings[i++] = gncQueryCoreToString (type, res, fcn);
}
row = gtk_clist_append (GTK_CLIST(list), (gchar **) strings);
gtk_clist_set_row_data (GTK_CLIST(list), row, item->data);
/* Free up our strings */
for (i = 0; i < list->num_columns; i++) {
if (strings[i])
g_free (strings[i]);
}
/* Now update any checkmarks */
update_booleans (list, row);
/* and set a watcher on this item */
guid = (const GUID*)((list->priv->get_guid)(item->data));
gnc_gui_component_watch_entity (list->priv->component_id, guid,
GNC_EVENT_MODIFY | GNC_EVENT_DESTROY);
list->num_entries++;
}
}
/********************************************************************\
* gnc_query_list_unselect_all *
* unselect all items in the list *
* *
* Args: list - list to unselect all *
* Returns: nothing *
\********************************************************************/
void
gnc_query_list_unselect_all(GNCQueryList *list)
{
g_return_if_fail (list != NULL);
g_return_if_fail (IS_GNC_QUERY_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_entry = NULL;
}
gboolean gnc_query_list_item_in_list (GNCQueryList *list, gpointer item)
{
g_return_val_if_fail(list, FALSE);
g_return_val_if_fail(item, FALSE);
g_return_val_if_fail(IS_GNC_QUERY_LIST(list), FALSE);
return (gtk_clist_find_row_from_data(GTK_CLIST(list), item) != -1);
}
void gnc_query_list_refresh_item (GNCQueryList *list, gpointer item)
{
gint row;
g_return_if_fail(list);
g_return_if_fail(item);
g_return_if_fail(IS_GNC_QUERY_LIST(list));
row = gtk_clist_find_row_from_data(GTK_CLIST(list), item);
if (row != -1)
update_booleans (list, row);
}
void
gnc_query_list_set_numerics (GNCQueryList *list, gboolean abs, gboolean inv_sort)
{
g_return_if_fail(list);
g_return_if_fail(IS_GNC_QUERY_LIST(list));
list->numeric_abs = abs;
list->numeric_inv_sort = inv_sort;
}

@ -0,0 +1,6 @@
*.lo
*.la
.deps
.libs
Makefile
Makefile.in

@ -0,0 +1,44 @@
SUBDIRS = .
pkglib_LTLIBRARIES=libgncmod-log-replay.la
libgncmod_log_replay_la_SOURCES = \
gnc-log-replay.c \
gncmod-log-replay.c
noinst_HEADERS = \
gnc-log-replay.h
libgncmod_log_replay_la_LDFLAGS = -module
libgncmod_log_replay_la_LIBADD = \
${top_builddir}/src/gnc-module/libgncmodule.la \
${top_builddir}/src/engine/libgncmod-engine.la \
${top_builddir}/src/import-export/libgncmod-generic-import.la \
${GLIB_LIBS}
gncscmdir = ${GNC_SCM_INSTALL_DIR}/log-replay
gncscm_DATA = \
log-replay.scm
AM_CFLAGS = \
-I${top_srcdir}/src \
-I${top_srcdir}/src/engine \
-I${top_srcdir}/src/gnc-module \
-I${top_srcdir}/src/app-utils \
-I${top_srcdir}/src/app-file \
-I${top_srcdir}/src/gnome \
-I${top_srcdir}/src/gnome-utils \
-I${top_srcdir}/src/import-export \
${GNOME_INCLUDEDIR} \
${GTKHTML_CFLAGS} \
${GLADE_CFLAGS} \
${GUILE_INCS} \
${GLIB_CFLAGS}
EXTRA_DIST = \
.cvsignore \
${gncscm_DATA}
CLEANFILES = g-wrapped .scm-links

@ -0,0 +1,573 @@
/********************************************************************\
* 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 *
\********************************************************************/
/** @addtogroup Import_Export
@{ */
/** @internal
@file gnc-log-replay.c
@brief .log file replay code
@author Copyright (c) 2003 Benoit Grégoire <bock@step.polymtl.ca>
*/
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <glib.h>
#include <libguile.h>
#include <gmodule.h>
#include "Account.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "global-options.h"
#include "gnc-log-replay.h"
#include "gnc-file-dialog.h"
#include "gnc-engine-util.h"
#include "gnc-book.h"
#include "gnc-ui-util.h"
#include "dialog-utils.h"
/*static short module = MOD_IMPORT;*/
static short module = MOD_TEST;
/* fprintf (trans_log, "mod guid time_now " \
"date_entered date_posted " \
"acc_guid acc_name num description " \
"memo action reconciled " \
"amount value date_reconciled\n");
"%c\t%s/%s\t%s\t%s\t%s\t%s\t%s\t%s\t"
"%s\t%s\t%s\t%c\t%lld/%lld\t%lld/%lld\t%s\n",
*/
#define STRING_FIELD_SIZE 256
typedef struct _split_record
{
enum _enum_action {LOG_BEGIN_EDIT, LOG_ROLLBACK, LOG_COMMIT, LOG_DELETE} log_action;
int log_action_present;
GUID trans_guid;
int trans_guid_present;
GUID split_guid;
int split_guid_present;
Timespec log_date;
int log_date_present;
Timespec date_entered;
int date_entered_present;
Timespec date_posted;
int date_posted_present;
GUID acc_guid;
int acc_guid_present;
char acc_name[STRING_FIELD_SIZE];
int acc_name_present;
char trans_num[STRING_FIELD_SIZE];
int trans_num_present;
char trans_descr[STRING_FIELD_SIZE];
int trans_descr_present;
char trans_notes[STRING_FIELD_SIZE];
int trans_notes_present;
char split_memo[STRING_FIELD_SIZE];
int split_memo_present;
char split_action[STRING_FIELD_SIZE];
int split_action_present;
char split_reconcile;
int split_reconcile_present;
gnc_numeric amount;
int amount_present;
gnc_numeric value;
int value_present;
Timespec date_reconciled;
int date_reconciled_present;
} split_record;
/********************************************************************\
* gnc_file_log_replay_import
* Entry point
\********************************************************************/
SCM scm_gnc_file_log_replay ()
{
gnc_file_log_replay();
return SCM_EOL;
}
static char *olds;
/* This version of strtok will only match SINGLE occurence of delim,
returning a 0 length valid string between two consecutive ocurence of delim.
It will also return a 0 length string instead of NULL when it reaches the end of s
*/
static char * my_strtok (s, delim)
char *s;
const char *delim;
{
char *token;
/*DEBUG("strtok(): Start...");*/
if (s == NULL)
s = olds;
/* Scan leading delimiters. */
/*s += strspn (s, delim);*/ /*Don't do it, or we will loose count.*/
if (*s == '\0')
{
olds = s;
return s;
}
/* Find the end of the token. */
token = s;
s = strpbrk (token, delim);
if (s == NULL)
{
/* This token finishes the string. */
olds = strchr (token, '\0');
}
else
{
/* Terminate the token and make OLDS point past it. */
*s = '\0';
olds = s + 1;
}
return token;
}
static split_record interpret_split_record( char *record_line)
{
char * tok_ptr;
split_record record;
memset(&record,0,sizeof(record));
DEBUG("interpret_split_record(): Start...");
if(strlen(tok_ptr = my_strtok(record_line,"\t"))!=0)
{
switch(tok_ptr[0])
{
case 'B': record.log_action=LOG_BEGIN_EDIT;
break;
case 'D': record.log_action=LOG_DELETE;
break;
case 'C': record.log_action=LOG_COMMIT;
break;
case 'R': record.log_action=LOG_ROLLBACK;
break;
}
record.log_action_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_guid(tok_ptr, &(record.trans_guid));
record.trans_guid_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_guid(tok_ptr, &(record.split_guid));
record.split_guid_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.log_date = gnc_iso8601_to_timespec_local(tok_ptr);
record.log_date_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.date_entered = gnc_iso8601_to_timespec_local(tok_ptr);
record.date_entered_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.date_posted = gnc_iso8601_to_timespec_local(tok_ptr);
record.date_posted_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_guid(tok_ptr, &(record.acc_guid));
record.acc_guid_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.acc_name,tok_ptr,STRING_FIELD_SIZE-1);
record.acc_name_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.trans_num,tok_ptr,STRING_FIELD_SIZE-1);
record.trans_num_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.trans_descr,tok_ptr,STRING_FIELD_SIZE-1);
record.trans_descr_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.trans_notes,tok_ptr,STRING_FIELD_SIZE-1);
record.trans_notes_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.split_memo,tok_ptr,STRING_FIELD_SIZE-1);
record.split_memo_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
strncpy(record.split_action,tok_ptr,STRING_FIELD_SIZE-1);
record.split_action_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.split_reconcile = tok_ptr[0];
record.split_reconcile_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_gnc_numeric(tok_ptr, &(record.amount));
record.amount_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
string_to_gnc_numeric(tok_ptr, &(record.value));
record.value_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
record.date_reconciled = gnc_iso8601_to_timespec_local(tok_ptr);
record.date_reconciled_present=TRUE;
}
if(strlen(tok_ptr = my_strtok(NULL,"\t"))!=0)
{
PERR("interpret_split_record(): Expected number of fields exceeded!");
}
DEBUG("interpret_split_record(): End");
return record;
}
static void dump_split_record(split_record record)
{
char * string_ptr = NULL;
char string_buf[256];
DEBUG("dump_split_record(): Start...");
if(record.log_action_present)
{
switch(record.log_action)
{
case LOG_BEGIN_EDIT: DEBUG("Log action: LOG_BEGIN_EDIT");
break;
case LOG_DELETE: DEBUG("Log action: LOG_DELETE");
break;
case LOG_COMMIT: DEBUG("Log action: LOG_COMMIT");
break;
case LOG_ROLLBACK: DEBUG("Log action: LOG_ROLLBACK");
break;
}
}
if(record.trans_guid_present)
{
string_ptr = guid_to_string (&(record.trans_guid));
DEBUG("Transaction GUID: %s", string_ptr);
g_free(string_ptr);
}
if(record.split_guid_present)
{
string_ptr = guid_to_string (&(record.split_guid));
DEBUG("Split GUID: %s", string_ptr);
g_free(string_ptr);
}
if(record.log_date_present)
{
gnc_timespec_to_iso8601_buff (record.log_date, string_buf);
DEBUG("Log entry date: %s", string_buf);
}
if(record.date_entered_present)
{
gnc_timespec_to_iso8601_buff (record.date_entered, string_buf);
DEBUG("Date entered: %s", string_buf);
}
if(record.date_posted_present)
{
gnc_timespec_to_iso8601_buff (record.date_posted, string_buf);
DEBUG("Date posted: %s", string_buf);
}
if(record.acc_guid_present)
{
string_ptr = guid_to_string (&(record.acc_guid));
DEBUG("Account GUID: %s", string_ptr);
g_free(string_ptr);
}
if(record.acc_name_present)
{
DEBUG("Account name: %s", record.acc_name);
}
if(record.trans_num_present)
{
DEBUG("Transaction number: %s", record.trans_num);
}
if(record.trans_descr_present)
{
DEBUG("Transaction description: %s", record.trans_descr);
}
if(record.trans_notes_present)
{
DEBUG("Transaction notes: %s", record.trans_notes);
}
if(record.split_memo_present)
{
DEBUG("Split memo: %s", record.split_memo);
}
if(record.split_action_present)
{
DEBUG("Split action: %s", record.split_action);
}
if(record.split_reconcile_present)
{
DEBUG("Split reconcile: %c", record.split_reconcile);
}
if(record.amount_present)
{
string_ptr = gnc_numeric_to_string(record.amount);
DEBUG("Record amount: %s", string_ptr);
g_free(string_ptr);
}
if(record.value_present)
{
string_ptr = gnc_numeric_to_string(record.value);
DEBUG("Record value: %s", string_ptr);
g_free(string_ptr);
}
if(record.date_reconciled_present)
{
gnc_timespec_to_iso8601_buff (record.date_reconciled, string_buf);
DEBUG("Reconciled date: %s", string_buf);
}
}
/* File pointer must already be at the begining of a record */
static void process_trans_record( FILE *log_file)
{
char read_buf[256];
char *read_retval;
const char * record_end_str = "===== END";
int first_record=TRUE;
int record_ended = FALSE;
int split_num = 0;
split_record record;
Transaction * trans = NULL;
Split * split = NULL;
Account * acct = NULL;
GNCBook * book = gnc_get_current_book();
DEBUG("process_trans_record(): Begin...\n");
while( record_ended == FALSE)
{
read_retval = fgets(read_buf,sizeof(read_buf),log_file);
if(read_retval!=NULL && strncmp(record_end_str,read_buf,strlen(record_end_str))!=0)/* If we are not at the end of the record */
{
split_num++;
/*DEBUG("process_trans_record(): Line read: %s%s",read_buf ,"\n");*/
record = interpret_split_record( read_buf);
dump_split_record( record);
if(record.log_action_present)
{
switch(record.log_action)
{
case LOG_BEGIN_EDIT: DEBUG("process_trans_record():Ignoring log action: LOG_BEGIN_EDIT"); /*Do nothing, there is no point*/
break;
case LOG_ROLLBACK: DEBUG("process_trans_record():Ignoring log action: LOG_ROLLBACK");/*Do nothing, since we didn't do the begin_edit either*/
break;
case LOG_DELETE: DEBUG("process_trans_record(): Playing back LOG_DELETE");
if((trans=xaccTransLookup (&(record.trans_guid), book))!=NULL
&& first_record==TRUE)
{
xaccTransBeginEdit(trans);
xaccTransDestroy(trans);
}
else if(first_record==TRUE)
{
PERR("The transaction to delete was not found!");
}
break;
case LOG_COMMIT: DEBUG("process_trans_record(): Playing back LOG_COMMIT");
if(record.trans_guid_present == TRUE
&& (trans=xaccTransLookupDirect (record.trans_guid, book)) != NULL
&& first_record == TRUE)
{
DEBUG("process_trans_record(): Transaction to be edited was found");/*Destroy the current transaction, we will create a new one to replace it*/
xaccTransBeginEdit(trans);
xaccTransDestroy(trans);
xaccTransCommitEdit(trans);
}
if(record.trans_guid_present == TRUE
&& first_record==TRUE)
{
DEBUG("process_trans_record(): Creating the new transaction");
trans = xaccMallocTransaction (book);
xaccTransBeginEdit(trans);
xaccTransSetGUID (trans, &(record.trans_guid));
/*Fill the transaction info*/
if(record.date_entered_present)
{
xaccTransSetDateEnteredTS(trans,&(record.date_entered));
}
if(record.date_posted_present)
{
xaccTransSetDatePostedTS(trans,&(record.date_posted));
}
if(record.trans_num_present)
{
xaccTransSetNum(trans,record.trans_num);
}
if(record.trans_descr_present)
{
xaccTransSetDescription(trans,record.trans_descr);
}
if(record.trans_notes_present)
{
xaccTransSetNotes(trans,record.trans_notes);
}
}
if(record.split_guid_present == TRUE) /*Fill the split info*/
{
split=xaccMallocSplit(book);
xaccSplitSetGUID (split, &(record.split_guid));
if(record.acc_guid_present)
{
acct = xaccAccountLookupDirect(record.acc_guid,book);
xaccAccountInsertSplit(acct,split);
}
xaccTransAppendSplit(trans,split);
if(record.split_memo_present)
{
xaccSplitSetMemo(split,record.split_memo);
}
if(record.split_action_present)
{
xaccSplitSetAction(split,record.split_action);
}
if(record.date_reconciled_present)
{
xaccSplitSetDateReconciledTS (split, &(record.date_reconciled));
}
if(record.split_reconcile_present)
{
xaccSplitSetReconcile(split, record.split_reconcile);
}
if(record.amount_present)
{
xaccSplitSetAmount(split, record.amount);
}
if(record.value_present)
{
xaccSplitSetValue(split, record.value);
}
}
first_record=FALSE;
break;
}
}
else
{
PERR("Corrupted record");
}
}
else /* The record ended */
{
record_ended = TRUE;
DEBUG("process_trans_record(): Record ended\n");
if(trans!=NULL)/*If we played with a transaction, commit it here*/
{
xaccTransCommitEdit(trans);
}
}
}
}
void gnc_file_log_replay (void)
{
const char *selected_filename;
char *default_dir;
char read_buf[256];
char *read_retval;
FILE *log_file;
char * expected_header = "mod trans_guid split_guid time_now date_entered date_posted acc_guid acc_name num description notes memo action reconciled amount value date_reconciled";
char * record_start_str = "===== START";
gnc_should_log(MOD_IMPORT, GNC_LOG_DEBUG);
DEBUG("gnc_file_log_replay(): Begin...\n");
default_dir = gnc_lookup_string_option("__paths", "Log Files", NULL);
if (default_dir == NULL)
gnc_init_default_directory(&default_dir);
selected_filename = gnc_file_dialog(_("Select a .log file to replay"),
NULL,
default_dir);
if(selected_filename!=NULL)
{
/* Remember the directory as the default. */
gnc_extract_directory(&default_dir, selected_filename);
gnc_set_string_option("__paths", "Log Files", default_dir);
g_free(default_dir);
/*strncpy(file,selected_filename, 255);*/
DEBUG("Filename found: %s",selected_filename);
DEBUG("Opening selected file");
log_file = fopen(selected_filename, "r");
if(ferror(log_file)!=0)
{
perror("File open failed");
}
else
{
if((read_retval = fgets(read_buf,sizeof(read_buf),log_file)) == NULL)
{
DEBUG("Read error or EOF");
}
else
{
if(strncmp(expected_header,read_buf,strlen(expected_header))!=0)
{
PERR("File header not recognised:\n%s",read_buf);
PERR("Expected:\n%s",expected_header);
}
else
{
do
{
read_retval = fgets(read_buf,sizeof(read_buf),log_file);
/*DEBUG("Chunk read: %s",read_retval);*/
if(strncmp(record_start_str,read_buf,strlen(record_start_str))==0)/* If a record started */
{
process_trans_record(log_file);
}
}while(feof(log_file)==0);
}
}
fclose(log_file);
}
}
}
/** @} */

@ -0,0 +1,34 @@
/********************************************************************\
* 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 *
\********************************************************************/
/** @file
@brief .log replay module interface
*
gnc-log-replay.h
@author Copyright (c) 2003 Benoit Grégoire <bock@step.polymtl.ca>
*/
#ifndef OFX_IMPORT_H
#define OFX_IMPORT_H
/** The gnc_file_log_replay() routine will pop up a standard file
* selection dialogue asking the user to pick a log file to replay. If one
* is selected the the .log file is opened and read. It's contents
* are then silently merged in the current log file. */
void gnc_file_log_replay (void);
SCM scm_gnc_file_log_replay (void);
#endif

@ -0,0 +1,92 @@
/********************************************************************\
* 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 *
\********************************************************************/
/** @addtogroup Import_Export
@{ */
/**@internal
@file gncmod-log-replay.c
@brief module definition/initialization for the log replay module
@author Copyright (c) 2003 Benoit Grégoire bock@step.polymtl.ca
*/
#include "config.h"
#include <glib.h>
#include <libguile.h>
#include "guile-mappings.h"
#include "gnc-log-replay.h"
#include "gnc-module.h"
#include "gnc-module-api.h"
/* version of the gnc module system interface we require */
int libgncmod_log_replay_LTX_gnc_module_system_interface = 0;
/* module versioning uses libtool semantics. */
int libgncmod_log_replay_LTX_gnc_module_current = 0;
int libgncmod_log_replay_LTX_gnc_module_revision = 0;
int libgncmod_log_replay_LTX_gnc_module_age = 0;
//static GNCModule bus_core;
//static GNCModule file;
/* forward references */
char *libgncmod_log_replay_LTX_gnc_module_path(void);
char *libgncmod_log_replay_LTX_gnc_module_description(void);
int libgncmod_log_replay_LTX_gnc_module_init(int refcount);
int libgncmod_log_replay_LTX_gnc_module_end(int refcount);
char *
libgncmod_log_replay_LTX_gnc_module_path(void)
{
return g_strdup("gnucash/import-export/log-replay");
}
char *
libgncmod_log_replay_LTX_gnc_module_description(void)
{
return g_strdup("C code for log file replay");
}
int
libgncmod_log_replay_LTX_gnc_module_init(int refcount)
{
if(!gnc_module_load("gnucash/engine", 0))
{
return FALSE;
}
if(!gnc_module_load("gnucash/app-utils", 0))
{
return FALSE;
}
if(!gnc_module_load("gnucash/gnome-utils", 0))
{
return FALSE;
}
if(!gnc_module_load("gnucash/import-export", 0))
{
return FALSE;
}
scm_c_eval_string("(load-from-path \"log-replay/log-replay.scm\")");
scm_c_define_gsubr("gnc:log-replay", 0, 0, 0, scm_gnc_file_log_replay);
return TRUE;
}
int
libgncmod_log_replay_LTX_gnc_module_end(int refcount)
{
return TRUE;
}
/** @}*/

@ -0,0 +1,10 @@
(define (add-log-replay-menu-item)
(gnc:add-extension
(gnc:make-menu-item(N_ "Replay GnuCash .log file")
(N_ "Replay a gnucash log file after a crash. This cannot be undone.")
(list gnc:window-name-main "File" "_Import" "")
(lambda ()
(gnc:log-replay)))))
(gnc:hook-add-dangler gnc:*ui-startup-hook* add-log-replay-menu-item)
Loading…
Cancel
Save