Files
gnucash/src/gnome/dialog-sxsincelast.c
T
Joshua Sled 8e904bc588 Matt Martin <matt.martin@ieee.org>'s patch to fix b0rken SX-list-processing
in since-last-run dialog  [before: 'break' out of processing further SXes
if a given SX in the list has no instances; now: 'continue' to other SXes
which may have instances yet].


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@5748 57a11ea4-9604-0410-9ed3-97b8803252fd
2001-10-30 08:05:27 +00:00

2203 lines
77 KiB
C

/********************************************************************\
* dialog-sxsincelast.c - "since last run" dialog. *
* Copyright (c) 2001 Joshua Sled <jsled@asynchronous.org> *
* *
* 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 *
\********************************************************************/
/**
* . Page 1: reminders list
* . backed by: sxsld->reminderList
* . Page 2: auto-create notify ledger
* . backed by: sxsld->autoCreateList [?]
* . Page 3: to-create variable bindings
* . backed by: sxsld->toCreateData [s/Data/List/]
* . Page 4: created ledger
* . backed by: sxsld->createdList [?]
* . Page 5: obsolete list
* . backed by: sxsld->toRemoveList
* . Page 6: finish
* . implement changes?
**/
#include "config.h"
#include <gnome.h>
#include <glib.h>
#include "Group.h"
#include "Query.h"
#include "SchedXaction.h"
#include "Transaction.h"
#include "dialog-utils.h"
#include "finvar.h"
#include "gnc-book.h"
#include "gnc-component-manager.h"
#include "gnc-engine-util.h"
#include "gnc-exp-parser.h"
#include "gnc-numeric.h"
#include "gnc-ui-util.h"
#include "gnc-ui.h"
#include "split-register.h"
#include "gnc-ledger-display.h"
#include "gnucash-sheet.h"
#include "dialog-sxsincelast.h"
#ifdef HAVE_LANGINFO_D_FMT
#include <langinfo.h>
#endif
#define DIALOG_SXSINCELAST_CM_CLASS "dialog-sxsincelast"
#define DIALOG_SXSINCELAST_REMIND_CM_CLASS "dialog-sxsincelast-remind"
#define DIALOG_SXSINCELAST_OBSOLETE_CM_CLASS "dialog-sxsincelast-obsolete"
#define DIALOG_SXSINCELAST_GLADE_NAME "Since Last Run Druid"
#define SXSLD_DRUID_GLADE_NAME "sincelast_druid"
#define SINCELAST_DRUID "sincelast_druid"
#define REMINDERS_PG "reminders_page"
#define AUTO_CREATE_NOTIFY_PG "auto_create_notify_page"
#define TO_CREATE_PG "to_create_page"
#define CREATED_PG "created_page"
#define OBSOLETE_PG "obsolete_page"
#define FINISH_PG "finish_page"
#define SX_OBSOLETE_CLIST "sx_obsolete_clist"
#define TO_CREATE_CLIST "to_create_clist"
#define REMINDER_CLIST "reminder_clist"
#define SX_GLADE_FILE "sched-xact.glade"
#define SELECT_ALL_BUTTON "select_all_button"
#define UNSELECT_ALL_BUTTON "unselect_all_button"
#define OK_BUTTON "ok_button"
#define CANCEL_BUTTON "cancel_button"
#define VARIABLE_TABLE "variables_table"
#define AUTO_CREATE_VBOX "ac_vbox"
#define TO_CREATE_CLIST_WIDTH 3
#define REMINDER_CLIST_WIDTH 3
#define SX_OBSOLETE_CLIST_WIDTH 3
#define COERCE_VOID_TO_GBOOLEAN(x) ((gboolean)(*#x))
#ifdef HAVE_LANGINFO_D_FMT
# define GNC_D_FMT (nl_langinfo (D_FMT))
#else
# define GNC_D_FMT "%Y-%m-%d"
#endif
#define GNC_D_WIDTH 25
#define GNC_D_BUF_WIDTH 26
static short module = MOD_SX;
/**
* The since-last-run dialog is a Gnome Druid which steps through the various
* parts of scheduled transaction since-last-run processing; these parts are:
*
* 1. Display and select SX reminders for creation
* 2. Show/allow editing of auto-created + notification-request SXes
* 3. Show to-create SXes, allowing variable binding
* 4. Show created SXes, allowing editing
* 5. Allow deletion of any obsolete SXes
*
* Pages which aren't relevant are skipped; this is handled in the 'prep'
* signal handler: e.g., a since-last dialog with only obsolete SXes would go
* through the 'prep' methods of all it's pages to reach the Obsolete page.
**/
typedef struct _sxSinceLastData {
GtkWidget *sincelast_window;
GnomeDruid *sincelast_druid;
GladeXML *gxml;
GList /* <toCreateTuple*> */ *toCreateData;
GList /* <reminderTuple*> */ *reminderList;
GList /* <toDeleteTuple*> */ *toRemoveList;
gboolean autoCreatedSomething;
GNCLedgerDisplay *ac_ledger;
GnucashRegister *reg;
} sxSinceLastData;
typedef struct _toCreateTuple {
SchedXaction *sx;
GDate *date;
gint clistRow;
GHashTable *varBindings;
} toCreateTuple;
typedef struct _reminderTuple {
SchedXaction *sx;
GDate *endDate;
GDate *occurDate;
gboolean isSelected;
} reminderTuple;
typedef struct _toDeleteTuple {
SchedXaction *sx;
GDate *endDate;
gchar *freq_info;
gboolean isSelected;
} toDeleteTuple;
typedef struct _creation_helper_userdata {
/* the to-create tuple */
toCreateTuple *tct;
/* a [pointer to a] GList to append the GUIDs of newly-created
* transactions to, or NULL */
GList **createdGUIDs;
} createData;
/* Next reminder clist row index to create. */
static int rl_row = 0;
/* Next to-create clist row index to create. */
static int tcl_row = 0;
static void sxsincelast_init( sxSinceLastData *sxsld );
static void create_autoCreate_gen_ledger( sxSinceLastData *sxsld );
static gncUIWidget sxsld_ledger_get_parent( GNCLedgerDisplay *ld );
static gboolean sxsincelast_populate( sxSinceLastData *sxsld );
static void sxsincelast_druid_cancelled( GnomeDruid *druid, gpointer ud );
static void sxsincelast_close_handler( gpointer ud );
static void sxsincelast_ok_clicked( GtkButton *b, gpointer ud );
static void sxsincelast_cancel_clicked( GtkButton *b, gpointer ud );
static void sxsincelast_entry_changed( GtkEditable *e, gpointer ud );
static void sxsincelast_destroy( GtkObject *o, gpointer ud );
static void create_transactions_on( SchedXaction *sx,
GDate *gd,
toCreateTuple *tct,
GList **createdGUIDs );
static gboolean create_each_transaction_helper( Transaction *t, void *d );
static void sxsl_get_sx_vars( SchedXaction *sx, GHashTable *varHash );
static void hash_to_sorted_list( GHashTable *hashTable, GList **gl );
static void andequal_numerics_set( gpointer key,
gpointer value,
gpointer data );
static void print_vars_helper( gpointer key,
gpointer value,
gpointer user_data );
static void clean_sincelast_data( sxSinceLastData *sxsld );
static void clean_variable_table( sxSinceLastData *sxsld );
static void processAutoCreateList( GList *,
sxSinceLastData *sxsld,
SchedXaction * );
static void processToCreateList( GList *,
sxSinceLastData *sxsld,
SchedXaction * );
static void processReminderList( GList *,
sxSinceLastData *sxsld );
static void processRemoveList( GList *,
sxSinceLastData *sxsld );
static void processSelectedReminderList( GList *, sxSinceLastData * );
static void sxsincelast_tc_row_sel( GtkCList *clist,
gint row, gint column,
GdkEventButton *event,
gpointer user_data);
static void sxsincelast_tc_row_unsel( GtkCList *clist,
gint row, gint column,
GdkEventButton *event,
gpointer user_data );
static void sxsld_remind_row_toggle( GtkCList *clist,
gint row, gint column,
GdkEventButton *event,
gpointer user_data);
static gboolean processed_valid_reminders_listP( sxSinceLastData *sxsld );
static void create_bad_reminders_msg( gpointer data, gpointer ud );
static gboolean inform_or_add( GList *reminders,
reminderTuple *rt, gboolean okFlag,
GList *badList, GList **goodList );
int parse_vars_from_formula( const char *formula, GHashTable *varHash );
static void sx_obsolete_ok_clicked(GtkButton *button, gpointer user_data);
static void sx_obsolete_cancel_clicked(GtkButton *button, gpointer user_data);
static void sx_obsolete_row_unsel(GtkCList *clist,
gint row,
gint column,
GdkEventButton *event,
gpointer user_data);
static void sx_obsolete_close_handler(gpointer user_data);
static void sx_obsolete_select_all_clicked(GtkButton *butt, gpointer user_data);
static void sx_obsolete_unselect_all_clicked(GtkButton *button, gpointer user_data);
static void sx_obsolete_row_sel(GtkCList *clist,
gint row,
gint column,
GdkEventButton *event,
gpointer user_data);
void
gnc_ui_sxsincelast_guile_wrapper( char *foo )
{
gnc_ui_sxsincelast_dialog_create();
}
void
gnc_ui_sxsincelast_dialog_create(void)
{
sxSinceLastData *sxsld = g_new0( sxSinceLastData, 1 );
sxsld->toCreateData = sxsld->reminderList = sxsld->toRemoveList = NULL;
sxsld->gxml = gnc_glade_xml_new( SX_GLADE_FILE,
DIALOG_SXSINCELAST_GLADE_NAME );
sxsld->sincelast_window =
glade_xml_get_widget( sxsld->gxml,
DIALOG_SXSINCELAST_GLADE_NAME );
sxsld->sincelast_druid =
GNOME_DRUID( glade_xml_get_widget( sxsld->gxml,
SXSLD_DRUID_GLADE_NAME ) );
sxsincelast_init( sxsld );
}
static void
clist_set_all_cols_autoresize( GtkCList *cl, guint n_cols )
{
guint col;
for( col = 0; col< n_cols; col++ ) {
gtk_clist_set_column_auto_resize (cl, col, TRUE);
}
return;
}
typedef struct {
char *name;
char *signal;
void (*handlerFn)();
} widgetSignalHandlerTuple;
typedef struct {
char *pageName;
void (*prepareHandlerFn)();
gboolean (*backHandlerFn)();
gboolean (*nextHandlerFn)();
void (*finishHandlerFn)();
gboolean (*cancelHandlerFn)();
} druidSignalHandlerTuple;
static void
dialog_widgets_attach_handlers(GladeXML *dialog_xml,
widgetSignalHandlerTuple *handler_info,
sxSinceLastData *sxsld)
{
int i;
GtkWidget *w;
for(i = 0; handler_info[i].name != NULL; i++)
{
w = glade_xml_get_widget(dialog_xml, handler_info[i].name);
gtk_signal_connect( GTK_OBJECT(w), handler_info[i].signal,
GTK_SIGNAL_FUNC(handler_info[i].handlerFn),
sxsld);
}
}
static void
druid_pages_attach_handlers( GladeXML *dialog_xml,
druidSignalHandlerTuple *handler_info,
sxSinceLastData *sxsld )
{
int i;
GtkWidget *w;
static char *HANDLER_NAMES[] = {
"prepare", "back", "next", "finish", "cancel"
};
for(i = 0; handler_info[i].pageName != NULL; i++)
{
w = glade_xml_get_widget(dialog_xml, handler_info[i].pageName);
if ( handler_info[i].prepareHandlerFn ) {
gtk_signal_connect( GTK_OBJECT(w), "prepare",
GTK_SIGNAL_FUNC(handler_info[i].prepareHandlerFn),
sxsld);
}
if ( handler_info[i].backHandlerFn ) {
gtk_signal_connect( GTK_OBJECT(w), "back",
GTK_SIGNAL_FUNC(handler_info[i].backHandlerFn),
sxsld);
}
if ( handler_info[i].nextHandlerFn ) {
gtk_signal_connect( GTK_OBJECT(w), "next",
GTK_SIGNAL_FUNC(handler_info[i].nextHandlerFn),
sxsld);
}
if ( handler_info[i].finishHandlerFn ) {
gtk_signal_connect( GTK_OBJECT(w), "finish",
GTK_SIGNAL_FUNC(handler_info[i].finishHandlerFn),
sxsld);
}
if ( handler_info[i].cancelHandlerFn ) {
gtk_signal_connect( GTK_OBJECT(w), "cancel",
GTK_SIGNAL_FUNC(handler_info[i].cancelHandlerFn),
sxsld);
}
}
}
static void
sxsincelast_druid_cancelled( GnomeDruid *druid, gpointer ud )
{
/* FIXME: make sure the user understands the impact of what they're
* doing, perhaps dependent on what stage of the process they're in.
* Close, destroy, &c. */
DEBUG( "druid cancelled" );
}
static gboolean
theres_no_turning_back_bang( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
DEBUG( "there's no turning back!!! MuHahahahhaha" );
return TRUE;
}
static void
reminders_prep( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
GtkWidget *w;
DEBUG( "begin" );
if ( g_list_length( sxsld->reminderList ) == 0 ) {
DEBUG( "reminders." );
w = glade_xml_get_widget( sxsld->gxml,
AUTO_CREATE_NOTIFY_PG );
gnome_druid_set_page( sxsld->sincelast_druid,
GNOME_DRUID_PAGE(w) );
}
DEBUG( "done" );
}
static gboolean
reminders_next( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
DEBUG( "reminders_next" );
if ( !processed_valid_reminders_listP( sxsld ) ) {
return TRUE;
}
/* next-state determination */
return FALSE;
}
static gboolean
reminders_back( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
DEBUG( "reminders_back" );
return FALSE;
}
static void
reminders_finish( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
DEBUG( "reminders_finish" );
return FALSE;
}
static gboolean
reminders_cancel( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
DEBUG( "reminders_cancel" );
return FALSE;
}
static void
auto_create_prep( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
GtkWidget *w;
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
if ( ! sxsld->autoCreatedSomething ) {
w = glade_xml_get_widget( sxsld->gxml, TO_CREATE_PG );
gnome_druid_set_page( sxsld->sincelast_druid,
GNOME_DRUID_PAGE(w) );
return;
}
}
static void
to_create_prep( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
GtkWidget *w;
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
if ( g_list_length( sxsld->toCreateData ) == 0 ) {
w = glade_xml_get_widget( sxsld->gxml, CREATED_PG );
gnome_druid_set_page( sxsld->sincelast_druid,
GNOME_DRUID_PAGE(w) );
return;
}
}
static void
created_prep( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
GtkWidget *w;
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
if ( /* FIXME: signal that we had to create a bunch of stuff from the
previous page. */ 1 ) {
w = glade_xml_get_widget( sxsld->gxml, OBSOLETE_PG );
gnome_druid_set_page( sxsld->sincelast_druid, GNOME_DRUID_PAGE(w) );
return;
}
}
static void
obsolete_prep( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
GtkWidget *w;
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
if ( g_list_length( sxsld->toRemoveList ) == 0 ) {
w = glade_xml_get_widget( sxsld->gxml, FINISH_PG );
gnome_druid_set_page( sxsld->sincelast_druid, GNOME_DRUID_PAGE(w) );
return;
}
processRemoveList( sxsld->toRemoveList, sxsld );
}
static gboolean
obsolete_next( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
GList *sxList, *toDelPtr;
GtkCList *cl;
gint row;
toDeleteTuple *tdt;
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
DEBUG( "obsolete_next" );
sxList = gnc_book_get_schedxactions( gnc_get_current_book() );
cl = GTK_CLIST( glade_xml_get_widget( sxsld->gxml,
SX_OBSOLETE_CLIST ) );
/* toDelPtr = sxsld->toRemoveList; */
for ( toDelPtr = cl->selection;
toDelPtr;
toDelPtr = toDelPtr->next ) {
row = (gint)toDelPtr->data;
tdt = (toDeleteTuple*)gtk_clist_get_row_data( cl, row );
sxList = g_list_remove( sxList, tdt->sx );
sxsld->toRemoveList =
g_list_remove( sxsld->toRemoveList, tdt->sx );
xaccSchedXactionFree( tdt->sx );
tdt->sx = NULL;
g_free( tdt );
}
gnc_book_set_schedxactions( gnc_get_current_book(), sxList );
gtk_clist_freeze( cl );
gtk_clist_clear( cl );
gtk_clist_thaw( cl );
return FALSE;
}
static gboolean
to_create_next( GnomeDruidPage *druid_page,
gpointer arg1, gpointer ud )
{
sxSinceLastData *sxsld;
GtkCList *cl;
GList *tcList;
gboolean allVarsBound;
toCreateTuple *tct;
sxsld = (sxSinceLastData*)ud;
cl = GTK_CLIST(glade_xml_get_widget( sxsld->gxml, TO_CREATE_CLIST ));
/* First: check to make sure all TCTs are 'ready' [and return if not].
* Second: create the entries based on the variable bindings. */
tcList = sxsld->toCreateData;
if ( tcList == NULL ) {
DEBUG( "No transactions to create..." );
/* FIXME: sxsincelast_close_handler( sxsld ); */
return FALSE;
}
do {
tct = (toCreateTuple*)tcList->data;
allVarsBound = TRUE;
g_hash_table_foreach( tct->varBindings,
andequal_numerics_set,
&allVarsBound );
if ( !allVarsBound ) {
char tmpBuf[GNC_D_BUF_WIDTH];
g_date_strftime( tmpBuf, GNC_D_WIDTH, GNC_D_FMT, tct->date );
/* FIXME: this should be better-presented to the user. */
DEBUG( "SX %s on date %s still has unbound variables.",
xaccSchedXactionGetName(tct->sx), tmpBuf );
gtk_clist_select_row( cl, tct->clistRow, 0 );
return TRUE;
}
} while ( (tcList = tcList->next) );
tcList = sxsld->toCreateData;
/* At this point we can assume there are to-create transactions and
all variables are bound. */
g_return_val_if_fail( tcList, TRUE );
do {
tct = (toCreateTuple*)tcList->data;
create_transactions_on( tct->sx, tct->date, tct, NULL );
} while ( (tcList = tcList->next) );
/* FXME: place in GL for review */
return FALSE;
}
static void
sxsincelast_init( sxSinceLastData *sxsld )
{
GtkWidget *w;
int i;
static widgetSignalHandlerTuple widgets[] = {
{ SINCELAST_DRUID, "cancel", sxsincelast_druid_cancelled },
{ REMINDER_CLIST, "select-row", sxsld_remind_row_toggle },
{ REMINDER_CLIST, "unselect-row", sxsld_remind_row_toggle },
{ TO_CREATE_CLIST, "select-row", sxsincelast_tc_row_sel },
{ TO_CREATE_CLIST, "unselect-row", sxsincelast_tc_row_unsel },
{ SELECT_ALL_BUTTON, "clicked",
sx_obsolete_select_all_clicked },
{ UNSELECT_ALL_BUTTON, "clicked",
sx_obsolete_unselect_all_clicked },
{ NULL, NULL, NULL }
};
static druidSignalHandlerTuple pages[] = {
{ REMINDERS_PG,
reminders_prep, theres_no_turning_back_bang, reminders_next,
reminders_finish, reminders_cancel },
{ TO_CREATE_PG,
to_create_prep, theres_no_turning_back_bang, to_create_next,
NULL, NULL },
{ AUTO_CREATE_NOTIFY_PG,
auto_create_prep, theres_no_turning_back_bang, NULL,
NULL, NULL },
{ CREATED_PG,
created_prep, theres_no_turning_back_bang, NULL,
NULL, NULL },
{ OBSOLETE_PG,
obsolete_prep, theres_no_turning_back_bang, obsolete_next,
NULL, NULL},
{ NULL, NULL, NULL, NULL, NULL, NULL }
};
gnc_register_gui_component( DIALOG_SXSINCELAST_CM_CLASS,
NULL,
sxsincelast_close_handler,
sxsld->sincelast_window );
gtk_signal_connect( GTK_OBJECT(sxsld->sincelast_window), "destroy",
GTK_SIGNAL_FUNC( sxsincelast_destroy ), sxsld );
dialog_widgets_attach_handlers(sxsld->gxml, widgets, sxsld);
druid_pages_attach_handlers( sxsld->gxml, pages, sxsld );
/* set all to-create clist columns to auto-resize. */
w = glade_xml_get_widget( sxsld->gxml, TO_CREATE_CLIST );
clist_set_all_cols_autoresize(GTK_CLIST(w), TO_CREATE_CLIST_WIDTH);
w = glade_xml_get_widget( sxsld->gxml, REMINDER_CLIST );
clist_set_all_cols_autoresize(GTK_CLIST(w), REMINDER_CLIST_WIDTH);
w = glade_xml_get_widget( sxsld->gxml, SX_OBSOLETE_CLIST );
clist_set_all_cols_autoresize(GTK_CLIST(w), SX_OBSOLETE_CLIST_WIDTH);
rl_row = tcl_row = 0;
create_autoCreate_gen_ledger( sxsld );
/* FIXME: deal with nothing-to-do. */
if ( ! sxsincelast_populate( sxsld ) ) {
sxsincelast_close_handler( sxsld );
return;
}
gtk_widget_show_all( sxsld->sincelast_window );
}
static void
generate_instances( SchedXaction *sx,
GDate *end,
GDate *reminderEnd,
GList **instanceList,
GList **reminderList,
GList **deadList )
{
GDate gd, *gdToReturn;
reminderTuple *rt;
void *seqStateData;
char tmpBuf[GNC_D_BUF_WIDTH];
DEBUG( "Generating instances for \"%s\"",
xaccSchedXactionGetName( sx ) );
/* Process valid next instances. */
seqStateData = xaccSchedXactionCreateSequenceState( sx );
gd = xaccSchedXactionGetNextInstance( sx, seqStateData );
while ( g_date_valid(&gd)
&& g_date_compare( &gd, end ) <= 0 ) {
g_date_strftime( tmpBuf, GNC_D_WIDTH, GNC_D_FMT, &gd );
DEBUG( "Adding instance %s", tmpBuf );
gdToReturn = g_date_new();
*gdToReturn = gd;
*instanceList = g_list_append( *instanceList, gdToReturn );
xaccSchedXactionIncrSequenceState( sx, seqStateData );
gd = xaccSchedXactionGetInstanceAfter( sx, &gd, seqStateData );
}
/* Add to dead list, or process reminder instances. */
if ( !g_date_valid( &gd )
&& deadList ) {
toDeleteTuple *tdt;
PINFO( "Should be added to dead-list..." );
tdt = g_new0( toDeleteTuple, 1 );
tdt->sx = sx;
tdt->endDate = g_date_new();
*tdt->endDate = gd;
*deadList = g_list_append( *deadList, tdt );
} else {
while ( g_date_valid(&gd)
&& g_date_compare( &gd, reminderEnd ) <= 0 ) {
g_date_strftime( tmpBuf, GNC_D_WIDTH, GNC_D_FMT, &gd );
DEBUG( "Adding reminder instance %s", tmpBuf );
rt = g_new0( reminderTuple, 1 );
rt->sx = sx;
rt->endDate = g_date_new();
*rt->endDate = *end;
rt->occurDate = g_date_new();
*rt->occurDate = gd;
rt->isSelected = FALSE;
*reminderList = g_list_append( *reminderList, rt );
xaccSchedXactionIncrSequenceState( sx, seqStateData );
gd = xaccSchedXactionGetInstanceAfter( sx, &gd, seqStateData );
}
}
xaccSchedXactionDestroySequenceState( sx, seqStateData );
seqStateData = NULL;
}
static void
free_gdate_list_elts( gpointer data, gpointer user_data )
{
g_date_free( (GDate*)data );
}
static void
_free_reminderTuple_list_elts( gpointer data, gpointer user_data )
{
/* FIXME: endDate? */
g_date_free( ((reminderTuple*)data)->occurDate );
g_free( (reminderTuple*)data );
}
static void
_free_varBindings_hash_elts( gpointer key, gpointer value, gpointer data )
{
g_free( key );
g_free( value );
}
static void
_free_toCreate_list_elts( gpointer data, gpointer user_data )
{
toCreateTuple *tct = (toCreateTuple*)data;
tct->sx = NULL;
g_date_free( tct->date );
g_hash_table_foreach( tct->varBindings, _free_varBindings_hash_elts, NULL );
g_hash_table_destroy( tct->varBindings );
g_free( tct );
}
static void
processAutoCreateList( GList *autoCreateList, sxSinceLastData *sxsld, SchedXaction *sx )
{
Query *autoCreateQuery;
GList *createdGUIDs = NULL;
GList *thisGUID;
gboolean autoCreateState, notifyState;
char *rowText[2];
/* get the "automagically created and notification requested"
* register, and create the entries in it. */
autoCreateQuery = xaccMallocQuery();
xaccQuerySetGroup( autoCreateQuery,
gnc_book_get_group( gnc_get_current_book() ) );
while ( autoCreateList ) {
xaccSchedXactionGetAutoCreate( sx,
&autoCreateState,
&notifyState );
create_transactions_on( sx, (GDate*)autoCreateList->data,
NULL, &createdGUIDs );
sxsld->autoCreatedSomething |= TRUE;
if ( notifyState ) {
thisGUID = createdGUIDs;
for ( ; thisGUID ; (thisGUID = thisGUID->next) ) {
xaccQueryAddGUIDMatch( autoCreateQuery,
(GUID*)thisGUID->data,
QUERY_OR );
}
thisGUID = createdGUIDs = NULL;
}
autoCreateList = autoCreateList->next;
}
gnc_ledger_display_set_query( sxsld->ac_ledger, autoCreateQuery );
gnc_ledger_display_refresh( sxsld->ac_ledger );
xaccFreeQuery( autoCreateQuery );
g_list_free( createdGUIDs );
createdGUIDs = NULL;
}
static void
processToCreateList( GList *toCreateList, sxSinceLastData *sxsld, SchedXaction *sx )
{
toCreateTuple *tct;
GtkCList *clist;
char *rowText[3];
if ( toCreateList == NULL )
return;
clist = GTK_CLIST( glade_xml_get_widget( sxsld->gxml, TO_CREATE_CLIST ) );
do {
tct = g_new0( toCreateTuple, 1 );
tct->sx = sx;
tct->date = (GDate*)toCreateList->data;
tct->clistRow = tcl_row;
tct->varBindings = g_hash_table_new( g_str_hash, g_str_equal );
/* add to clist [ahem... register... ahem] */
rowText[0] = xaccSchedXactionGetName( sx );
rowText[1] = g_new0( char, GNC_D_WIDTH );
g_date_strftime( rowText[1], GNC_D_WIDTH, GNC_D_FMT, tct->date );
sxsl_get_sx_vars( sx, tct->varBindings );
if ( g_hash_table_size( tct->varBindings ) == 0 ) {
rowText[2] = "y";
} else {
rowText[2] = "n";
}
gtk_clist_insert( clist, tcl_row, rowText );
gtk_clist_set_row_data( clist, tcl_row, tct );
tcl_row++;
sxsld->toCreateData = g_list_append( sxsld->toCreateData, tct );
g_free( rowText[1] );
} while ( (toCreateList = toCreateList->next) );
if ( tcl_row > 0 ) {
gtk_clist_select_row( clist, 0, 0 );
}
}
static void
processReminderList( GList *reminderList, sxSinceLastData *sxsld )
{
GtkCList *clist;
char *rowText[3];
reminderTuple *rt;
if ( reminderList == NULL )
return;
clist = GTK_CLIST( glade_xml_get_widget( sxsld->gxml,
REMINDER_CLIST ) );
do {
rt = (reminderTuple*)reminderList->data;
rowText[0] = xaccSchedXactionGetName( rt->sx );
rowText[1] = g_new0( gchar, GNC_D_WIDTH );
g_date_strftime( rowText[1],
GNC_D_WIDTH, GNC_D_FMT, rt->occurDate );
rowText[2] = g_new0( gchar, 5 ); /* FIXME: appropriate size? */
sprintf( rowText[2], "%d",
(g_date_julian(rt->occurDate)
- g_date_julian(rt->endDate)) );
gtk_clist_insert( clist, rl_row, rowText );
gtk_clist_set_row_data( clist, rl_row, (gpointer)rt );
rl_row += 1;
g_free( rowText[1] );
g_free( rowText[2] );
} while ( (reminderList = reminderList->next) );
}
static void
processRemoveList(GList *removeList, sxSinceLastData *sxsld)
{
GtkCList *cl;
char *rowtext[3];
int row;
GString *tmp_str;
toDeleteTuple *tdt;
FreqSpec *fs;
GDate *gd;
rowtext[2] = g_new0(gchar, GNC_D_BUF_WIDTH );
cl = GTK_CLIST( glade_xml_get_widget( sxsld->gxml,
SX_OBSOLETE_CLIST ));
tmp_str = g_string_new(NULL);
gtk_clist_freeze( cl );
gtk_clist_clear( cl );
for(row = 0; removeList != NULL; row++, removeList = removeList->next) {
tdt = (toDeleteTuple*)removeList->data;
rowtext[0] = xaccSchedXactionGetName( tdt->sx );
fs = xaccSchedXactionGetFreqSpec( tdt->sx );
xaccFreqSpecGetFreqStr( fs, tmp_str );
rowtext[1] = tmp_str->str;
/* FIXME: This date is the first invalid one, as opposed to the
* last valid one. :( Or, even better, the reason for
* obsolesence. */
/*g_date_strftime( rowtext[2], GNC_D_WIDTH, GNC_D_FMT, tdt->endDate ); */
strcpy( rowtext[2], "obsolete" );
gtk_clist_insert( cl, row, rowtext );
gtk_clist_set_row_data(cl, row, removeList->data );
}
gtk_clist_thaw( cl );
g_string_free(tmp_str, TRUE);
g_free(rowtext[2]);
}
/**
* Moves the selected reminders to the appropriate [auto-create or to-create]
* sections of the since-last-run dialog.
**/
static void
processSelectedReminderList( GList *goodList, sxSinceLastData *sxsld )
{
reminderTuple *rt;
gboolean autoCreateOpt, notifyOpt;
GList *list = NULL;
for ( ; goodList ; goodList = goodList->next ) {
rt = (reminderTuple*)goodList->data;
DEBUG( "Processing selected reminder \"%s\"",
xaccSchedXactionGetName( rt->sx ) );
xaccSchedXactionGetAutoCreate( rt->sx,
&autoCreateOpt, &notifyOpt );
if ( autoCreateOpt ) {
list = g_list_append( list,
rt->occurDate );
processAutoCreateList( list, sxsld, rt->sx );
} else {
list = g_list_append( list,
rt->occurDate );
processToCreateList( list, sxsld, rt->sx );
}
g_list_free( list );
list = NULL;
}
}
/**
* Returns TRUE if there's some populated in the dialog to show to the user,
* FALSE if not.
**/
static gboolean
sxsincelast_populate( sxSinceLastData *sxsld )
{
GList *sxList, *instanceList;
GList *autoCreateList, *toCreateList;
SchedXaction *sx;
GDate end, endPlusReminders;
GDate *instDate;
gint daysInAdvance;
gboolean autocreateState, notifyState;
GtkWidget *w;
gint autoCreateRow, toCreateRow, remindersRow;
reminderTuple *rt;
gboolean showIt;
showIt = FALSE;
autoCreateList = toCreateList = NULL;
autoCreateRow = toCreateRow = remindersRow = 0;
instanceList = NULL;
sxList = gnc_book_get_schedxactions( gnc_get_current_book () );
if ( sxList == NULL ) {
DEBUG( "No scheduled transactions to populate." );
return FALSE;
}
do {
sx = (SchedXaction*)sxList->data;
g_date_set_time( &end, time(NULL) );
daysInAdvance = xaccSchedXactionGetAdvanceCreation( sx );
g_date_add_days( &end, daysInAdvance );
endPlusReminders = end;
daysInAdvance = xaccSchedXactionGetAdvanceReminder( sx );
g_date_add_days( &endPlusReminders, daysInAdvance );
generate_instances( sx, &end,
&endPlusReminders,
&instanceList,
&sxsld->reminderList,
&sxsld->toRemoveList );
if ( instanceList == NULL ) {
continue;
}
xaccSchedXactionGetAutoCreate( sx, &autocreateState,
&notifyState );
do {
instDate = (GDate*)instanceList->data;
if ( autocreateState ) {
autoCreateList =
g_list_append( autoCreateList,
instDate );
} else {
toCreateList =
g_list_append( toCreateList,
instDate );
}
} while ( (instanceList = instanceList->next) );
/* Report RE:showing the dialog iff there's stuff in it to
* show. */
showIt |= (g_list_length( autoCreateList ) > 0);
showIt |= (g_list_length( toCreateList ) > 0);
processAutoCreateList( autoCreateList, sxsld, sx );
processToCreateList ( toCreateList, sxsld, sx );
g_list_foreach( autoCreateList, free_gdate_list_elts, NULL );
g_list_free( autoCreateList );
autoCreateList = NULL;
/* We have moved the GDates over to the toCreateData list in
* sxsld, so we don't free them here. */
g_list_free( toCreateList );
toCreateList = NULL;
g_list_free( instanceList );
instanceList = NULL;
} while ( (sxList = sxList->next) );
processReminderList( sxsld->reminderList, sxsld );
showIt |= (g_list_length( sxsld->reminderList ) > 0);
/* Defer showing the obsolete list until the end of the reminder
* processing [as selection of reminders to create may affect the
* list length. */
showIt |= (g_list_length( sxsld->toRemoveList ) > 0);
return showIt;
}
static void
clean_sincelast_dlg( sxSinceLastData *sxsld )
{
GtkWidget *w;
/* . clean out to-create clist
* . free associated memories.
*
* FIXME: other dlg stuff to clean?
*/
clean_variable_table( sxsld );
w = glade_xml_get_widget( sxsld->gxml, TO_CREATE_CLIST );
gtk_clist_clear( GTK_CLIST(w) );
g_list_foreach( sxsld->toCreateData, _free_toCreate_list_elts, sxsld );
g_list_free( sxsld->toCreateData );
sxsld->toCreateData = NULL;
tcl_row = 0;
}
/*
* FIXME: This is workable w/two dialogs, but not with three!!
*/
/**
* Close policy:
* . If reminders-dialog-open
* . clean up since-last and hide [reminders may need to re-populate and
* re-display it.
* . If !reminders-dialog-open && since-last-dialog-open
* . close/destroy reminders dialog
* . close/destroy since-last dialog.
* . If !reminders-dialog-open && !since-last-dialog-open
* . close/destroy reminders dialog.
* . close/destroy since-last dialog.
*
* Fragile code alert... :(
**/
/* major problem... we don't know, here, whether we've been
* called because we've been asked to close, or because the
* remind_dialog close has called us. This is important,
* because it determines what we do at this point:
*
* 1. We were called...
* ...thus, we clean out and raise the reminder dlg, or
* create the obsolete dialog.
*
* 2. The remind-dialog-close was called, and then called us...
* ...thus, we wait around for our interaction to complete.
*/
static void
sxsincelast_close_handler( gpointer ud )
{
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
DEBUG( "sxsincelast_close_handler" );
gtk_widget_hide( sxsld->sincelast_window );
clean_sincelast_dlg( sxsld );
gtk_widget_destroy( sxsld->sincelast_window );
clean_sincelast_data( sxsld );
}
static void
free_elts( gpointer data, gpointer user_data )
{
g_free( data );
}
static void
sxsincelast_ok_clicked( GtkButton *b, gpointer ud )
{
sxSinceLastData *sxsld;
GtkWidget *dlg;
GtkWidget *o;
GtkCList *cl;
time_t gdeDate;
GList *sxList;
GNCBook *book;
SchedXaction *sx;
GDate gd, *endDate;
gint row;
char *rowText[2];
toCreateTuple *tct;
GList *tcList;
gboolean allVarsBound;
sxsld = (sxSinceLastData*)ud;
cl = GTK_CLIST(glade_xml_get_widget( sxsld->gxml, TO_CREATE_CLIST ));
/* First: check to make sure all TCTs are 'ready' [and return if not].
* Second: create the entries based on the variable bindings. */
tcList = sxsld->toCreateData;
if ( tcList == NULL ) {
DEBUG( "No transactions to create..." );
sxsincelast_close_handler( sxsld );
return;
}
do {
tct = (toCreateTuple*)tcList->data;
allVarsBound = TRUE;
g_hash_table_foreach( tct->varBindings,
andequal_numerics_set,
&allVarsBound );
if ( !allVarsBound ) {
char tmpBuf[GNC_D_BUF_WIDTH];
g_date_strftime( tmpBuf, GNC_D_WIDTH, GNC_D_FMT, tct->date );
/* FIXME: this should be better-presented to the user. */
DEBUG( "SX %s on date %s still has unbound variables.",
xaccSchedXactionGetName(tct->sx), tmpBuf );
gtk_clist_select_row( cl, tct->clistRow, 0 );
return;
}
} while ( (tcList = tcList->next) );
tcList = sxsld->toCreateData;
/* At this point we can assume there are to-create transactions and
all variables are bound. */
g_return_if_fail( tcList );
do {
tct = (toCreateTuple*)tcList->data;
create_transactions_on( tct->sx, tct->date, tct, NULL );
} while ( (tcList = tcList->next) );
/* FIXME: cleanup appropriately. */
sxsincelast_close_handler( sxsld );
}
static void
sxsincelast_cancel_clicked( GtkButton *o, gpointer ud )
{
sxsincelast_close_handler( ud );
}
static void
andequal_numerics_set( gpointer key, gpointer value, gpointer data )
{
gboolean *allVarsBound = data;
*allVarsBound &= (value != NULL);
}
static void
sxsincelast_entry_changed( GtkEditable *e, gpointer ud )
{
sxSinceLastData *sxsld;
gchar *varName;
toCreateTuple *tct;
gchar *entryText;
gnc_numeric *num;
GHashTable *dummyVarHash;
sxsld = (sxSinceLastData*)ud;
tct = (toCreateTuple*)gtk_object_get_data( GTK_OBJECT(e), "tct" );
varName = (gchar*)gtk_object_get_data( GTK_OBJECT(e), "varName" );
entryText = gtk_editable_get_chars( e, 0, -1 );
num = g_new0( gnc_numeric, 1 );
*num = gnc_numeric_create( 0, 1 );
dummyVarHash = g_hash_table_new( NULL, NULL );
/* FIXME: these debugs probably want to go into a staus bar... */
/* FIXME: Should be using xaccParseAmount instead of parser_parse_separate_vars? */
if ( !gnc_exp_parser_parse_separate_vars( entryText, num,
NULL, dummyVarHash ) ) {
DEBUG( "error parsing entry \"%s\"", entryText );
g_free( num );
num = NULL;
} else if ( g_hash_table_size( dummyVarHash ) != 0 ) {
DEBUG( "no new variables allowed in variable "
"bindings for expression \"%s\"", entryText );
g_free( num );
num = NULL;
} else if ( gnc_numeric_check( *num ) != GNC_ERROR_OK ) {
DEBUG( "entry \"%s\" is not "
"gnc_numeric-parseable", entryText );
g_free( num );
num = NULL;
} else if ( gnc_numeric_zero_p( *num ) ) {
DEBUG( "entry \"%s\" parses as '0'",
( entryText ? entryText : "(null)" ) );
g_free( num );
num = NULL;
}
g_hash_table_destroy( dummyVarHash );
{
gpointer maybeKey, maybeValue;
if ( g_hash_table_lookup_extended( tct->varBindings, varName,
&maybeKey, &maybeValue ) ) {
g_hash_table_remove( tct->varBindings, maybeKey );
g_free( maybeValue );
/* Does the maybeKey need to be freed? */
}
g_hash_table_insert( tct->varBindings, maybeKey, num );
}
{
GtkCList *clist;
gboolean allVarsBound = TRUE;
/* If there are no un-bound variables, then set the 'ready-to-go'
flag to 'y'. */
g_hash_table_foreach( tct->varBindings, andequal_numerics_set, &allVarsBound );
clist = GTK_CLIST(glade_xml_get_widget( sxsld->gxml, TO_CREATE_CLIST ));
gtk_clist_set_text( clist, tct->clistRow, 2, ( allVarsBound ? "y" : "n" ) );
}
}
static void
sxsincelast_destroy( GtkObject *o, gpointer ud )
{
sxSinceLastData *sxsld = (sxSinceLastData*)ud;
DEBUG( "sxsincelast_destroy called\n" );
/* appropriate place to destroy data structures */
clean_sincelast_data( sxsld );
gnc_ledger_display_close( sxsld->ac_ledger );
/* FIXME: need more freeing for ac_ledger? */
sxsld->ac_ledger = NULL;
g_free( sxsld );
}
static gboolean
create_each_transaction_helper( Transaction *t, void *d )
{
Transaction *newT;
GList *sList;
GList *osList;
Split *split;
kvp_frame *split_kvpf;
kvp_value *kvp_val;
gboolean errFlag;
createData *createUD;
toCreateTuple *tct;
gnc_commodity *commonCommodity = NULL;
errFlag = FALSE;
/* FIXME: In general, this should [correctly] deal with errors such
as not finding the approrpiate Accounts and not being able to
parse the formula|credit/debit strings. */
/* FIXME: when we copy the trans_onto_trans, we don't want to copy
the Split's kvp_frames... */
DEBUG( "I'm seeing Transaction \"%s\"", xaccTransGetDescription( t ) );
createUD = (createData*)d;
tct = createUD->tct;
newT = xaccMallocTransaction(gnc_get_current_session ());
xaccTransBeginEdit( newT );
/* the action and description/memo are in the template */
gnc_copy_trans_onto_trans( t, newT, FALSE, FALSE );
/* the date is new [by definition :)] */
xaccTransSetDate( newT,
g_date_day( tct->date ),
g_date_month( tct->date ),
g_date_year( tct->date ) );
/* the accounts and amounts are in the kvp_frames of the splits. */
osList = xaccTransGetSplitList( t );
sList = xaccTransGetSplitList( newT );
if ( (osList == NULL) || (sList == NULL) ) {
PERR( "\tseen transaction w/o splits. :(" );
return FALSE;
}
do {
split = (Split*)sList->data;
/* FIXME: Ick. This assumes that the split lists will be
ordered identically. :( I think it's fair to say they
will, but I'd rather not have to count on it. --jsled */
split_kvpf = xaccSplitGetSlots( (Split*)osList->data );
DEBUG( "\tProcessing Split \"%s\"", xaccSplitGetMemo( split ) );
DEBUG( "\tkvp_frame: %s\n", kvp_frame_to_string( split_kvpf ) );
/* from-transaction of splits */
{
GUID *acct_guid;
Account *acct;
/* contains the guid of the split's actual account. */
kvp_val = kvp_frame_get_slot_path( split_kvpf,
GNC_SX_ID,
GNC_SX_ACCOUNT,
NULL );
if ( kvp_val == NULL ) {
PERR( "Null kvp_val for account" );
}
acct_guid = kvp_value_get_guid( kvp_val );
acct = xaccAccountLookup( acct_guid,
gnc_get_current_session ());
DEBUG( "Got account with name \"%s\"",
xaccAccountGetName( acct ) );
if ( commonCommodity != NULL ) {
if ( commonCommodity != xaccAccountGetCommodity( acct ) ) {
PERR( "Common-commodity difference: old=%s, new=%s\n",
gnc_commodity_get_mnemonic( commonCommodity ),
gnc_commodity_get_mnemonic( xaccAccountGetCommodity( acct ) ) );
}
}
commonCommodity = xaccAccountGetCommodity( acct );
xaccAccountInsertSplit( acct, split );
}
/* credit/debit formulas */
{
char *str, *parseErrorLoc;
gnc_numeric credit_num, debit_num, final;
int gncn_error;
kvp_val = kvp_frame_get_slot_path( split_kvpf,
GNC_SX_ID,
GNC_SX_CREDIT_FORMULA,
NULL);
str = kvp_value_get_string( kvp_val );
credit_num = gnc_numeric_create( 0, 1 );
if ( str != NULL
&& strlen(str) != 0 ) {
if ( ! gnc_exp_parser_parse_separate_vars( str, &credit_num,
&parseErrorLoc, tct->varBindings ) ) {
PERR( "Error parsing credit formula \"%s\" at \"%s\": %s",
str, parseErrorLoc, gnc_exp_parser_error_string() );
errFlag = TRUE;
break;
}
DEBUG( "gnc_numeric::credit: \"%s\" -> %s [%s]",
str, gnc_numeric_to_string( credit_num ),
gnc_numeric_to_string( gnc_numeric_reduce( credit_num ) ) );
}
kvp_val = kvp_frame_get_slot_path( split_kvpf,
GNC_SX_ID,
GNC_SX_DEBIT_FORMULA,
NULL);
str = kvp_value_get_string( kvp_val );
debit_num = gnc_numeric_create( 0, 1 );
if ( str != NULL
&& strlen(str) != 0 ) {
if ( ! gnc_exp_parser_parse_separate_vars( str, &debit_num,
&parseErrorLoc, tct->varBindings ) ) {
PERR( "Error parsing debit_formula \"%s\" at \"%s\": %s",
str, parseErrorLoc, gnc_exp_parser_error_string() );
errFlag = TRUE;
break;
}
DEBUG( "gnc_numeric::debit: \"%s\" -> %s [%s]",
str, gnc_numeric_to_string( debit_num ),
gnc_numeric_to_string( gnc_numeric_reduce( debit_num ) ) );
}
final = gnc_numeric_sub_fixed( credit_num, debit_num );
gncn_error = gnc_numeric_check( final );
if ( gncn_error != GNC_ERROR_OK ) {
PERR( "Error %d in final gnc_numeric value", gncn_error );
errFlag = TRUE;
break;
}
DEBUG( "gnc_numeric::final: \"%s\"",
gnc_numeric_to_string( final ) );
xaccSplitSetValue( split, final );
}
#if 0
/* NOT [YET] USED */
kvp_val = kvp_frame_get_slot_path( split_kvpf,
GNC_SX_ID,
GNC_SX_SHARES,
NULL);
kvp_val = kvp_frame_get_slot_path( split_kvpf,
GNC_SX_ID,
GNC_SX_AMNT,
NULL);
#endif /* 0 */
/* FIXME:
* . We'd like to store the variable bindings, but this might be
* problematic [if the formulas change in the SX].
*/
} while ( (sList = sList->next) && (osList = osList->next) );
/* set the balancing currency. */
if ( commonCommodity == NULL ) {
PERR( "Unable to find common currency/commodity." );
} else {
xaccTransSetCurrency( newT, commonCommodity );
}
{
kvp_frame *txn_frame;
/* set a kvp-frame element in the transaction indicating and
pointing-to the SX this was created from. */
txn_frame = xaccTransGetSlots( newT );
if ( txn_frame == NULL ) {
txn_frame = kvp_frame_new();
xaccTransSetSlots_nc( newT, txn_frame );
}
kvp_val = kvp_value_new_guid( xaccSchedXactionGetGUID(tct->sx) );
kvp_frame_set_slot( txn_frame, "from-sched-xaction", kvp_val );
}
if ( errFlag ) {
PERR( "Some error in new transaction creation..." );
xaccTransRollbackEdit( newT );
xaccTransDestroy( newT );
xaccTransCommitEdit( newT );
return FALSE;
}
xaccTransCommitEdit( newT );
if ( createUD->createdGUIDs != NULL ) {
*createUD->createdGUIDs =
g_list_append( *(createUD->createdGUIDs),
(gpointer)xaccTransGetGUID(newT) );
}
return TRUE;
}
/**
* This should be called with the dates in increasing order, or the last call
* will set the last occur date incorrectly.
**/
static void
create_transactions_on( SchedXaction *sx, GDate *gd,
toCreateTuple *tct,
GList **createdGUIDs )
{
createData createUD;
AccountGroup *ag;
Account *acct;
char *id;
char tmpBuf[GNC_D_BUF_WIDTH];
gboolean createdTCT;
{
g_date_strftime( tmpBuf, GNC_D_WIDTH, GNC_D_FMT, gd );
DEBUG( "Creating transactions on %s for %s",
tmpBuf, xaccSchedXactionGetName( sx ) );
}
if ( tct != NULL
&& g_date_compare( gd, tct->date ) != 0 ) {
PERR( "GDate and TCT date aren't equal, "
"which isn't a Good Thing." );
return;
}
createdTCT = FALSE;
if ( tct == NULL ) {
/* Create a faux tct for the creation-helper. */
tct = g_new0( toCreateTuple, 1 );
tct->sx = sx;
tct->date = gd;
tct->clistRow = -1;
createdTCT = TRUE;
}
ag = gnc_book_get_template_group( gnc_get_current_book () );
id = guid_to_string( xaccSchedXactionGetGUID(sx) );
if(ag && id)
{
acct = xaccGetAccountFromName( ag, id );
if(acct)
{
createUD.tct = tct;
createUD.createdGUIDs = createdGUIDs;
xaccAccountForEachTransaction( acct,
create_each_transaction_helper,
/*tct*/ &createUD );
}
}
if (id)
{
g_free( id );
}
xaccSchedXactionSetLastOccurDate( sx, tct->date );
if ( createdTCT ) {
g_free( tct );
tct = NULL;
}
}
static void
_hashToList( gpointer key, gpointer value, gpointer user_data )
{
*(GList**)user_data = g_list_append( *(GList**)user_data, key );
}
static void
hash_to_sorted_list( GHashTable *hashTable, GList **gl )
{
g_hash_table_foreach( hashTable, _hashToList, gl );
*gl = g_list_sort( *gl, g_str_equal );
}
static void
_clear_variable_numerics( gpointer key, gpointer value, gpointer data )
{
g_free( (gnc_numeric*)value );
g_hash_table_insert( (GHashTable*)data, key, NULL );
}
static void
sxsl_get_sx_vars( SchedXaction *sx, GHashTable *varHash )
{
GList *splitList, *tmpL;
kvp_frame *kvpf;
kvp_value *kvp_val;
Split *s;
char *str;
{
AccountGroup *ag;
Account *acct;
char *id;
ag = gnc_book_get_template_group( gnc_get_current_book () );
id = guid_to_string( xaccSchedXactionGetGUID(sx) );
acct = xaccGetAccountFromName( ag, id );
g_free( id );
splitList = xaccAccountGetSplitList( acct );
}
if ( splitList == NULL ) {
PINFO( "SchedXaction %s has no splits", xaccSchedXactionGetName( sx ) );
return;
}
do {
s = (Split*)splitList->data;
kvpf = xaccSplitGetSlots(s);
kvp_val = kvp_frame_get_slot_path( kvpf,
GNC_SX_ID,
GNC_SX_CREDIT_FORMULA,
NULL);
if ( kvp_val != NULL ) {
str = kvp_value_get_string( kvp_val );
if ( str && strlen(str) != 0 ) {
parse_vars_from_formula( str, varHash );
}
}
kvp_val = kvp_frame_get_slot_path( kvpf,
GNC_SX_ID,
GNC_SX_DEBIT_FORMULA,
NULL);
if ( kvp_val != NULL ) {
str = kvp_value_get_string( kvp_val );
if ( str && strlen(str) != 0 ) {
parse_vars_from_formula( str, varHash );
}
}
} while ( (splitList = splitList->next) );
g_hash_table_foreach( varHash, _clear_variable_numerics, (gpointer)varHash );
}
static void
sxsincelast_tc_row_sel( GtkCList *clist,
gint row,
gint column,
GdkEventButton *event,
gpointer user_data)
{
static const int NUM_COLS = 2;
static GtkAttachOptions sopts = GTK_SHRINK;
static GtkAttachOptions lxopts = GTK_EXPAND | GTK_FILL;
GtkTable *varTable;
int tableIdx;
GtkWidget *label, *entry;
GList *varList;
toCreateTuple *tct;
sxSinceLastData *sxsld;
/* FIXME: this should more gracefully deal with multiple 'row-select'
signals from double/triple-clicks. */
sxsld = (sxSinceLastData*)user_data;
tct = (toCreateTuple*)gtk_clist_get_row_data( clist, row );
if ( tct == NULL ) {
PERR( "TCT for row %d is NULL", row );
return;
}
varList = NULL;
hash_to_sorted_list( tct->varBindings, &varList );
if ( g_hash_table_size( tct->varBindings ) == 0 ) {
PINFO( "No variables to deal with" );
return;
}
varTable = GTK_TABLE( glade_xml_get_widget( sxsld->gxml,
VARIABLE_TABLE ) );
gtk_table_resize( varTable, 4, NUM_COLS );
tableIdx = 1;
do {
GString *gstr;
const gchar *numValueStr;
gnc_numeric *numValue;
gstr = g_string_sized_new(16);
g_string_sprintf( gstr, "%s: ", (gchar*)varList->data );
label = gtk_label_new( gstr->str );
gtk_label_set_justify( GTK_LABEL(label), GTK_JUSTIFY_RIGHT );
g_string_free( gstr, TRUE );
entry = gtk_entry_new();
gtk_object_set_data( GTK_OBJECT(entry), "varName", varList->data );
gtk_object_set_data( GTK_OBJECT(entry), "tct", tct );
gtk_widget_set_usize( entry, 64, 0 );
numValue = (gnc_numeric*)g_hash_table_lookup( tct->varBindings,
varList->data );
if ( numValue != NULL ) {
numValueStr =
xaccPrintAmount( *numValue,
gnc_default_print_info( FALSE ) );
gtk_entry_set_text( GTK_ENTRY(entry), numValueStr );
}
gtk_signal_connect( GTK_OBJECT(entry), "changed",
GTK_SIGNAL_FUNC( sxsincelast_entry_changed ),
sxsld );
gtk_table_attach( varTable, label,
0, 1, tableIdx, tableIdx + 1,
lxopts, sopts, 0, 0 );
gtk_table_attach( varTable, entry,
1, 2, tableIdx, tableIdx + 1,
sopts, sopts, 0, 0 );
tableIdx += 1;
} while ( (varList = varList->next) );
gtk_widget_show_all( GTK_WIDGET(varTable) );
}
static void
clean_variable_table( sxSinceLastData *sxsld )
{
GtkTable *table;
GList *children, *toFree;
GtkTableChild *child;
table = GTK_TABLE( glade_xml_get_widget( sxsld->gxml,
VARIABLE_TABLE ) );
children = table->children;
toFree = NULL;
if ( children == NULL ) {
PERR( "The variable-binding table should always have at "
"least 2 children... something's amiss." );
return;
}
do {
/* Destroy all children after the first [label-continaing]
row... ie, leave the labels in place. */
child = (GtkTableChild*)children->data;
if ( child->top_attach > 0 ) {
toFree = g_list_append( toFree, child->widget );
}
} while ( (children = children->next) );
gtk_table_resize( table, 1, 2 );
while ( toFree != NULL ) {
gtk_widget_destroy( (GtkWidget*)toFree->data );
toFree = toFree->next;
}
g_list_free( toFree );
}
static void
sxsincelast_tc_row_unsel( GtkCList *clist,
gint row, gint column,
GdkEventButton *event,
gpointer user_data )
{
sxSinceLastData *sxsld;
sxsld = (sxSinceLastData*)user_data;
clean_variable_table( sxsld );
}
static void
print_vars_helper( gpointer key, gpointer value, gpointer user_data )
{
DEBUG( "\"%s\" -> %.8x [%s]",
(gchar*)key, (unsigned int)value,
gnc_numeric_to_string( *(gnc_numeric*)value ) );
}
int
parse_vars_from_formula( const char *formula, GHashTable *varHash )
{
gnc_numeric numeric;
char *foo;
if ( ! gnc_exp_parser_parse_separate_vars( formula, &numeric, &foo, varHash ) ) {
PERR( "Error parsing at \"%s\": %s",
foo, gnc_exp_parser_error_string() );
return -1;
}
#if 0
/* NOT USED... still keep? */
if ( g_hash_table_size( varHash ) == 0 ) {
DEBUG( "No variables in expression \"%s\"", formula );
} else {
g_hash_table_foreach( varHash, print_vars_helper, NULL );
}
#endif /* 0 */
return 0;
}
/**
* The following makes me [jsled] somewhat sad, but it works... :I
*
* Basic problem: You can't create a SX instance if any after it have already
* been created [e.g., if an SX has instances on d_0, d_1 and d_2, and you
* create d_0 and d_2, then d_1 will never get created ... only d_3, d_4,
* &c.]
*
* This code, then, makes sure that the user hasn't skipped a date...
*
* Code flow...
* . If non-consecutive Reminders chosen, disallow.
* . Else, for each selected reminder, add to to-create list.
* . Dismiss dialog.
*
* Returns TRUE if there are processed, valid reminders... FALSE otherwise.
**/
static gboolean
processed_valid_reminders_listP( sxSinceLastData *sxsld )
{
reminderTuple *rt, *prevRT;
char *rtName;
gboolean overallOkFlag, okFlag, prevState;
GList *reminderListPtr;
GList *badList;
GList *badRecentRun;
GList *goodList;
rtName = NULL;
goodList = NULL;
overallOkFlag = TRUE;
okFlag = prevState = TRUE;
badList = badRecentRun = NULL;
rt = prevRT = NULL;
reminderListPtr = sxsld->reminderList;
while ( reminderListPtr != NULL ) {
prevRT = rt;
rt = (reminderTuple*)reminderListPtr->data;
if ( xaccSchedXactionGetName( rt->sx ) != rtName ) {
if ( rtName != NULL ) {
/* Deal with previous sequence. */
overallOkFlag &=
inform_or_add( reminderListPtr,
prevRT, okFlag,
badList, &goodList );
}
/* Reset loop state vars */
okFlag = prevState = TRUE;
rtName = xaccSchedXactionGetName( rt->sx );
/* Cleanup */
if ( badList != NULL ) {
g_list_free( badList );
}
if ( badRecentRun != NULL ) {
g_list_free( badRecentRun );
}
badList = badRecentRun = NULL;
}
/* If we haven't seen an inconsistency, then we're still ok.
* "inconsistency": we can go from [isSelected ==] TRUE to
* FALSE, but not back again [w/in a SX sequence, which we
* are [by the outer loop]]. */
if ( prevState ) {
prevState = rt->isSelected;
if ( ! rt->isSelected ) {
badRecentRun =
g_list_append( badRecentRun, rt );
}
} else {
if ( rt->isSelected ) {
okFlag = FALSE;
if ( g_list_length( badRecentRun ) > 0 ) {
badList =
g_list_concat( badList,
badRecentRun );
badRecentRun = NULL;
}
} else {
badRecentRun =
g_list_append( badRecentRun, rt );
}
}
reminderListPtr = reminderListPtr->next;
}
/* Deal with final sequence. */
if ( rtName != NULL ) {
overallOkFlag &= inform_or_add( sxsld->reminderList,
rt, okFlag,
badList, &goodList );
}
/* cleanup */
g_list_free( badList );
g_list_free( badRecentRun );
/* Handle implications of above logic. */
if ( !overallOkFlag ) {
g_list_free( goodList );
return FALSE;
}
/* Go through to-create list and do so...
* . if auto-create, add to auto-create list.
* . if to-create, add to to-create list.
* . if need to bring up since-last dialog, do so.
* . dismiss reminder dialog
* . if since-last dialog is empty, close.
*/
if ( g_list_length( goodList ) > 0 ) {
processSelectedReminderList( goodList, sxsld );
g_list_free( goodList );
}
return TRUE;
}
static void
sxsld_remind_row_toggle( GtkCList *clist,
gint row, gint column,
GdkEventButton *event,
gpointer user_data)
{
reminderTuple *rt;
rt = (reminderTuple*)gtk_clist_get_row_data( clist, row );
rt->isSelected = !rt->isSelected;
}
static void
create_bad_reminders_msg( gpointer data, gpointer ud )
{
GString *msg;
reminderTuple *rt;
static char tmpBuf[GNC_D_BUF_WIDTH];
rt = (reminderTuple*)data;
msg = (GString*)ud;
g_date_strftime( tmpBuf, GNC_D_WIDTH, GNC_D_FMT, rt->occurDate );
g_string_sprintfa( msg, tmpBuf );
g_string_sprintfa( msg, "\n" );
}
static gboolean
inform_or_add( GList *reminders,
reminderTuple *rt, gboolean okFlag,
GList *badList, GList **goodList )
{
reminderTuple *curRt;
GList *rtPtr;
GString *userMsg;
userMsg = NULL;
if ( okFlag ) {
/* Add selected instances of this rt to
okay-to-add-to-toCreateList list. */
rtPtr = reminders;
if ( rtPtr == NULL ) {
PERR( "We should never see an empty reminder list." );
}
do {
curRt = (reminderTuple*)rtPtr->data;
/* this isn't really all that efficient. */
if ( (curRt->sx == rt->sx)
&& curRt->isSelected ) {
*goodList = g_list_append( *goodList, curRt );
}
} while( (rtPtr = rtPtr->next) );
} else {
/* [Add to list for later] dialog issuance to user. */
userMsg = g_string_sized_new( 128 );
g_string_sprintf( userMsg,
"You cannot skip instances of Scheduled Transactions.\n"
"The following instances of \"%s\"\n"
"must be selected as well:\n\n",
xaccSchedXactionGetName( rt->sx ) );
g_list_foreach( badList, create_bad_reminders_msg, userMsg );
gnc_error_dialog( userMsg->str );
}
return okFlag;
}
#if 0
static void
sx_obsolete_row_sel(GtkCList *clist,
gint row,
gint column,
GdkEventButton *event,
gpointer user_data)
{
sxSinceLastData *sxsld;
GList *row_sxlentry_data;
sxsld = (sxSinceLastData*) user_data;
row_sxlentry_data = (GList *)gtk_clist_get_row_data(clist, row);
if (g_list_position(sxsld->actual_to_remove, row_sxlentry_data) == -1)
{
sxsld->actual_to_remove = g_list_append(sxsld->actual_to_remove, row_sxlentry_data);
}
return;
}
static void
sx_obsolete_row_unsel(GtkCList *clist,
gint row,
gint column,
GdkEventButton *event,
gpointer user_data)
{
GList *position;
GList *row_sxlentry_data = gtk_clist_get_row_data(clist, row);
sxSinceLastData *sxsld = user_data;
if((position = g_list_find(sxsld->actual_to_remove, row_sxlentry_data)))
{
sxsld->actual_to_remove = g_list_remove_link(sxsld->actual_to_remove, position);
g_list_free_1(position);
}
return;
}
static void
sx_obsolete_ok_clicked(GtkButton *button, gpointer user_data)
{
sxSinceLastData *sxsld = user_data;
SchedXaction *sx;
GList *actual_sx_list, *actual_sx_listref, *removelist_ref;
GNCBook *book = gnc_get_current_book ();
actual_sx_list = gnc_book_get_schedxactions(book);
for(removelist_ref = sxsld->actual_to_remove;
removelist_ref; removelist_ref = removelist_ref->next)
{
actual_sx_listref = removelist_ref->data;
xaccSchedXactionFree( (SchedXaction *) (actual_sx_listref->data) );
actual_sx_list = g_list_remove_link(actual_sx_list,
actual_sx_listref);
g_list_free_1(actual_sx_listref);
}
g_list_free(sxsld->actual_to_remove);
sxsld->actual_to_remove = NULL;
gnc_book_set_schedxactions(book, actual_sx_list);
printf( "c\n" );
gnome_dialog_close(GNOME_DIALOG(sxsld->dlg_obsolete));
}
static void
sx_obsolete_close_handler(gpointer user_data)
{
sxSinceLastData *sxsld = user_data;
if ( sxsld->toRemoveList ) {
/* FIXME: g_list_foreach( tdt_destroyer, ...->toRemoveList ) */
g_list_free(sxsld->toRemoveList);
sxsld->toRemoveList = NULL;
}
if ( sxsld->actual_to_remove ) {
g_list_free(sxsld->actual_to_remove);
sxsld->actual_to_remove = NULL;
}
sxsld->n_obsolete = 0;
if ( sxsld->dlg_obsolete ) {
printf( "d\n" );
gnome_dialog_close(GNOME_DIALOG(sxsld->dlg_obsolete));
sxsld->dlg_obsolete = NULL;
sxsld->obsolete_displayed = FALSE;
}
sxsincelast_close_handler( sxsld );
}
static void
sx_obsolete_cancel_clicked(GtkButton *button, gpointer user_data)
{
sx_obsolete_close_handler(user_data);
}
#endif /* 0 */
static void
sx_obsolete_select_all_clicked(GtkButton *button, gpointer user_data)
{
sxSinceLastData* sxsld = user_data;
GtkCList *ob_clist = GTK_CLIST(glade_xml_get_widget(sxsld->gxml,
SX_OBSOLETE_CLIST));
gtk_clist_select_all( ob_clist );
}
static void
sx_obsolete_unselect_all_clicked(GtkButton *button, gpointer user_data)
{
sxSinceLastData* sxsld = user_data;
GtkCList *ob_clist = GTK_CLIST(glade_xml_get_widget(sxsld->gxml,
SX_OBSOLETE_CLIST));
gtk_clist_unselect_all( ob_clist );
}
static void
create_autoCreate_gen_ledger( sxSinceLastData *sxsld )
{
SplitRegister *splitreg;
GtkWidget *vbox;
GtkWidget *toolbar;
GtkWidget *regWidget;
GtkWidget *popup;
sxsld->ac_ledger = gnc_ledger_display_gl();
gnc_ledger_display_set_handlers( sxsld->ac_ledger,
NULL,
sxsld_ledger_get_parent );
gnc_ledger_display_set_user_data( sxsld->ac_ledger, (gpointer)sxsld );
splitreg = gnc_ledger_display_get_split_register( sxsld->ac_ledger );
/* FIXME: make configurable? */
gnucash_register_set_initial_rows( 4 );
regWidget = gnucash_register_new( splitreg->table );
gnc_table_init_gui( regWidget, splitreg );
sxsld->reg = GNUCASH_REGISTER(regWidget);
GNUCASH_SHEET(sxsld->reg->sheet)->window =
GTK_WIDGET( sxsld->sincelast_druid );
vbox = glade_xml_get_widget( sxsld->gxml, AUTO_CREATE_VBOX );
toolbar = gtk_label_new( "foo ... toolbar...uh... fooo" );
gtk_box_pack_start( GTK_BOX(vbox), toolbar, FALSE, FALSE, 2 );
gtk_widget_show_all( toolbar );
/* FIXME: Too bad this just increases it's size w/o bound. */
gtk_box_pack_end( GTK_BOX(vbox), regWidget, TRUE, TRUE, 2 );
gtk_widget_show_all( regWidget );
#if 0
gtk_signal_connect( GTK_OBJECT(sxed->dialog), "activate_cursor",
GTK_SIGNAL_FUNC(sxe_register_record_cb), sxed );
gtk_signal_connect( GTK_OBJECT(sxed->dialog), "redraw_all",
GTK_SIGNAL_FUNC(sxe_register_redraw_all_cb), sxed );
#endif /* 0 */
/* FIXME: we should do all the happy-fun register stuff... button bar
* controls ... popups ... */
#if 0
popup = schedXaction_editor_create_reg_popup( sxsld );
gnucash_register_attach_popup( sxsld->reg, popup, sxsld );
#endif /* 0 */
/* configure... */
/* don't use double-line */
gnc_split_register_config(splitreg,
splitreg->type, splitreg->style,
FALSE);
/* don't show present/future divider [by definition, not necessary] */
gnc_split_register_show_present_divider( splitreg, FALSE );
/* force a refresh */
gnc_ledger_display_refresh( sxsld->ac_ledger );
}
static
gncUIWidget
sxsld_ledger_get_parent( GNCLedgerDisplay *ld )
{
sxSinceLastData *sxsld;
sxsld = (sxSinceLastData*)gnc_ledger_display_get_user_data( ld );
return (gncUIWidget)sxsld->reg->sheet->window;
}
static void
clean_sincelast_data( sxSinceLastData *sxsld )
{
/* FIXME: much more to go, here. */
g_list_foreach( sxsld->toCreateData, _free_toCreate_list_elts, NULL );
if ( sxsld->toCreateData ) {
g_list_free( sxsld->toCreateData );
sxsld->toCreateData = NULL;
}
}