diff --git a/src/gnome-utils/gnc-query-list.c b/src/gnome-utils/gnc-query-list.c new file mode 100644 index 0000000000..25d8dec2f8 --- /dev/null +++ b/src/gnome-utils/gnc-query-list.c @@ -0,0 +1,888 @@ +/********************************************************************\ + * gnc-query-list.c -- A query display list. * + * Copyright (C) 2003 Derek Atkins * + * * + * 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 + +#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; +} diff --git a/src/import-export/log-replay/.cvsignore b/src/import-export/log-replay/.cvsignore new file mode 100644 index 0000000000..69fb0bd826 --- /dev/null +++ b/src/import-export/log-replay/.cvsignore @@ -0,0 +1,6 @@ +*.lo +*.la +.deps +.libs +Makefile +Makefile.in diff --git a/src/import-export/log-replay/Makefile.am b/src/import-export/log-replay/Makefile.am new file mode 100644 index 0000000000..c580d24b45 --- /dev/null +++ b/src/import-export/log-replay/Makefile.am @@ -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 diff --git a/src/import-export/log-replay/gnc-log-replay.c b/src/import-export/log-replay/gnc-log-replay.c new file mode 100644 index 0000000000..1baa7cde27 --- /dev/null +++ b/src/import-export/log-replay/gnc-log-replay.c @@ -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 +*/ +#define _GNU_SOURCE + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#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); + } + } + +} + + +/** @} */ diff --git a/src/import-export/log-replay/gnc-log-replay.h b/src/import-export/log-replay/gnc-log-replay.h new file mode 100644 index 0000000000..77516e6e73 --- /dev/null +++ b/src/import-export/log-replay/gnc-log-replay.h @@ -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 + */ +#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 diff --git a/src/import-export/log-replay/gncmod-log-replay.c b/src/import-export/log-replay/gncmod-log-replay.c new file mode 100644 index 0000000000..b3ce0c46f1 --- /dev/null +++ b/src/import-export/log-replay/gncmod-log-replay.c @@ -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 +#include +#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; +} +/** @}*/ diff --git a/src/import-export/log-replay/log-replay.scm b/src/import-export/log-replay/log-replay.scm new file mode 100644 index 0000000000..f12b60b451 --- /dev/null +++ b/src/import-export/log-replay/log-replay.scm @@ -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) +