From 4017f68403693ac67f3a1f1f63341290f75f3730 Mon Sep 17 00:00:00 2001 From: Joshua Sled Date: Mon, 30 Jul 2001 08:32:02 +0000 Subject: [PATCH] 2001-07-30 Joshua Sled * src/gnome/glade/sched-xact.glade: Added seperate reminders dialog from since-last-run dialog. Removed reminders frame/section from since-last-run dialog. Added alternative SX Editor UI [not presently used]. * src/gnome/dialog-sxsincelast.c: Changes for reminders-as-seperate-dialog; selected reminders then update since-last-run dialog [whether it's open or not]; the dialogs [mostly] deal with their closing conditions correctly. Support for ad-hoc variables in template-transaction-split formulas. * src/engine/gnc-schedxaction-xml-v2.c (gnc_schedXaction_end_handler): Added support for restoring in-development pre-template-account-stored-in-SX change. (tt_act_handler): Added support for restoring in-devel pre-CIT .gml2/XMLv2 files. * src/gnc-exp-parser.c (gnc_exp_parser_parse_seperate_vars): Added for Scheduled-Transaction-related since-last-run/split-formula parsing. * src/gnome/dialog-sxsincelast.c: Added support for variable entry before creation/formulas in template transactions. git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@5058 57a11ea4-9604-0410-9ed3-97b8803252fd --- ChangeLog | 26 + src/SplitLedger.c | 6 - src/doc/TODO-schedxactions | 41 +- src/engine/SchedXaction.c | 28 +- src/engine/SchedXaction.h | 4 +- src/engine/gnc-numeric.c | 3 - src/engine/gnc-numeric.h | 4 + src/engine/gnc-schedxaction-xml-v2.c | 89 +- src/engine/kvp_doc.txt | 11 +- src/gnc-exp-parser.c | 61 +- src/gnc-exp-parser.h | 23 +- src/gnome/dialog-scheduledxaction.c | 4 +- src/gnome/dialog-sxsincelast.c | 1322 ++++++++++++++++++++------ src/gnome/glade/sched-xact.glade | 988 +++++++++++++++++-- src/gnome/glade/sched-xact_strings.c | 28 + 15 files changed, 2160 insertions(+), 478 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0fd0f4b1c3..dd56c8ad19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2001-07-30 Joshua Sled + + * src/gnome/glade/sched-xact.glade: Added seperate reminders + dialog from since-last-run dialog. Removed reminders + frame/section from since-last-run dialog. Added alternative SX + Editor UI [not presently used]. + + * src/gnome/dialog-sxsincelast.c: Changes for + reminders-as-seperate-dialog; selected reminders then update + since-last-run dialog [whether it's open or not]; the dialogs + [mostly] deal with their closing conditions correctly. Support + for ad-hoc variables in template-transaction-split formulas. + + * src/engine/gnc-schedxaction-xml-v2.c + (gnc_schedXaction_end_handler): Added support for restoring + in-development pre-template-account-stored-in-SX change. + (tt_act_handler): Added support for restoring in-devel pre-CIT + .gml2/XMLv2 files. + + * src/gnc-exp-parser.c (gnc_exp_parser_parse_seperate_vars): Added + for Scheduled-Transaction-related since-last-run/split-formula + parsing. + + * src/gnome/dialog-sxsincelast.c: Added support for variable entry + before creation/formulas in template transactions. + 2001-07-28 Robert Graham Merkel * src/engine/SchedXaction.{ch}: Store template account in the diff --git a/src/SplitLedger.c b/src/SplitLedger.c index 1ca46c2239..bf05081dc2 100644 --- a/src/SplitLedger.c +++ b/src/SplitLedger.c @@ -3399,12 +3399,6 @@ xaccSRSaveChangedTemplateCells( SplitRegister *reg, gnc_numeric credit; gnc_numeric debit; - /* credit = xaccGetPriceCellValue(reg->creditCell); */ - /* debit = xaccGetPriceCellValue(reg->debitCell); */ - /* new_amount = gnc_numeric_sub_fixed (debit, credit); */ - - /* FIXME: the credit/debit cells are limited to - numeric values by definition [and code]. Blegh. */ DEBUG( "kvp_frame before: %s\n", kvp_frame_to_string( kvpf ) ); /* amountStr = gnc_numeric_to_string( new_amount ); */ diff --git a/src/doc/TODO-schedxactions b/src/doc/TODO-schedxactions index 7f35be255c..93453288d2 100644 --- a/src/doc/TODO-schedxactions +++ b/src/doc/TODO-schedxactions @@ -13,9 +13,12 @@ X XMLv2 I/O X need policy for start-dates in GNCFrequency X fix GetNextInstance and GetInstanceAfter, mostly for composite FreqSpecs X also needs to deal with >28-mday values WRT last-occur date + multipliers +X would like "don't even tell me you instantiated it, just fscking do + it!" option for non-manual-conf SXes ################################################## ### To-Do +### '.' = to-do, 'X' = done ################################################## . Engine ...Init() functions should be private. @@ -41,18 +44,29 @@ X create a template register . need "since-last-run" UI for instanatiation. X/O create transactions X numeric - . formula - . need variable fill-in UI... - . re-use gnome-sheet code for a variable-binding table? + X formula + X need variable fill-in UI... + X re-use gnome-sheet code for a variable-binding table? -- ended up + using GtkTable [for now?] . need way to expire/purge scheduled transactions which have no chance of being created [outside their end date; once-scheduled and past]. - . need 'reminders' of upcoming scheduled transactions during since-last-run - . Is it sufficient to look at the next instance of ea. transaction only? + . infrequent enough that a dialog should suffice. + X need 'reminders' of upcoming scheduled transactions during since-last-run + X Is it sufficient to look at the next instance of ea. transaction only? I think not... we probably want to look out until the transaction is outside the reminder range. . need some configuration for constantly-running GnuCash users of when to pop up the 'new scheduled transactions' dialog [option, time-of-day] - . Should deal with "nothing-to-do" better [not even show up]. + . Should deal with "nothing-to-do" better [not even show up in the worst + [read: no auto-notify/to-create/reminders] case]. + . Double-clicking on auto-create list should jump to transaction + . register of first split? + . double-clicking a reminder should [probably] move it up to the to-create + list. + X Easy UI way of moving through the SXes which need variable bindings. + [This presently is: click "Ok" and the next clist row which needs + bindings is selected.] + . Need some way to defer to-create SXes. . register mods . to see upcoming xactions, modifying future balance @@ -64,13 +78,10 @@ X create a template register not-yet-created transactions. . The ability to turn a non-recurring transaction into a recurring one... -X would like "don't even tell me you instantiated it, just fscking do - it!" option for non-manual-conf SXes - - . deal better with formulas in template transactions [real FormulaCell] . recognize purely numeric template transactions and balance at template-creation time. + . balance transaction via symbolic math? . GNCFrequency . initial-settings synchronization [start date, optionmenus] @@ -82,7 +93,7 @@ X would like "don't even tell me you instantiated it, just fscking do . SX Creation... . Creation occurs due to a variety of circumstances: - . GnuCash starts and hasn't been run in some length of time; the + X GnuCash starts and hasn't been run in some length of time; the transactions which have "come due" since the last run are available for creation. . A user opens a register with future transactions in it; they manually @@ -111,7 +122,9 @@ X would like "don't even tell me you instantiated it, just fscking do . reminder . lead-time -. Changes + + +. [NOTES] Dealing with SX state-changes . Create-into-future vs. create-normal . into-future -> normal : no change; the last_occur date will be appropriate, and will catch up eventually [unless the have-been-created @@ -145,11 +158,13 @@ X would like "don't even tell me you instantiated it, just fscking do much whitespace on the two top panels] . Size/space issues. . Un-selecting "End Date" leaves calendar widget sensitive + . 'ESC' while editing template register causes window to go away [badly]. . Scheduled Transaction List . Since-last-run . always goes one date past the instantiation date. + . tab-order on variable-entry window isn't always correct. X credit and debit seem reversed at instantiation time... . this is fixed, but is the fix correct? @@ -166,7 +181,7 @@ X would like "don't even tell me you instantiated it, just fscking do staying around]. . There probably wants to be a multi-month "example" View which the GNCFrequency can work with. - . jalapeno[ben] has some thoughts on this... + . jalapeno/ben has some thoughts on this... . Evolution calendar integ. . Evolution To-Do list integ. [SX reminders] diff --git a/src/engine/SchedXaction.c b/src/engine/SchedXaction.c index 26d68222da..e14f285348 100644 --- a/src/engine/SchedXaction.c +++ b/src/engine/SchedXaction.c @@ -69,13 +69,14 @@ xaccSchedXactionInit( SchedXaction *sx, GNCBook *book ) sx->advanceCreateDays = 0; sx->advanceRemindDays = 0; -// sx->templateSplits = NULL; +/* sx->templateSplits = NULL; */ /* create a new template account for our splits */ sx->template_acct = xaccMallocAccount(); name = guid_to_string( &sx->guid ); xaccAccountSetName( sx->template_acct, name ); xaccAccountSetCommodity( sx->template_acct, - gnc_commodity_new( "template", "template", "template", "template", 1 ) ); + gnc_commodity_new( "template", "template", + "template", "template", 1 ) ); g_free( name ); xaccAccountSetType( sx->template_acct, BANK ); ag = gnc_book_get_template_group( book ); @@ -92,7 +93,6 @@ xaccSchedXactionMalloc( GNCBook *book ) return sx; } - void sxprivtransactionListMapDelete( gpointer data, gpointer user_data ) { @@ -196,14 +196,12 @@ xaccSchedXactionGetName( SchedXaction *sx ) void xaccSchedXactionSetName( SchedXaction *sx, const gchar *newName ) { - GString *gstr; g_return_if_fail( newName != NULL ); if ( sx->name != NULL ) { g_free( sx->name ); + sx->name = NULL; } - gstr = g_string_new( newName ); - sx->name = gstr->str; - g_string_free( gstr, FALSE ); + sx->name = g_strdup( newName ); } GDate* @@ -391,17 +389,17 @@ xaccSchedXactionGetNextInstance( SchedXaction *sx ) } else { g_date_set_time( &last_occur, time(NULL) ); } -#endif // 0 +#endif /* 0 */ if ( g_date_valid( &sx->start_date ) && ! g_date_valid( &sx->last_date ) ) { - // Think about this for a second, and you realize - // that if the start date is _today_, we need a - // last-occur date such that the 'next instance' is - // after that date... one day should be good. - // - // This only holds for the first instance [read: if the - // last[-occur]_date is invalid. + /* Think about this for a second, and you realize + * that if the start date is _today_, we need a + * last-occur date such that the 'next instance' is + * after that date... one day should be good. + * + * This only holds for the first instance [read: if the + * last[-occur]_date is invalid. */ g_date_subtract_days( &last_occur, 1 ); } diff --git a/src/engine/SchedXaction.h b/src/engine/SchedXaction.h index f6cee8a073..5cae033b2e 100644 --- a/src/engine/SchedXaction.h +++ b/src/engine/SchedXaction.h @@ -74,10 +74,10 @@ typedef struct gncp_SchedXaction { gboolean autoCreateOption; gboolean autoCreateNotify; gint advanceCreateDays; - gint advanceRemindDays; + gint advanceRemindDays; Account *template_acct; - GUID guid; + GUID guid; kvp_frame *kvp_data; diff --git a/src/engine/gnc-numeric.c b/src/engine/gnc-numeric.c index 082f3c3153..93227d65e1 100644 --- a/src/engine/gnc-numeric.c +++ b/src/engine/gnc-numeric.c @@ -1052,9 +1052,6 @@ gnc_numeric_to_string(gnc_numeric n) { const gchar * string_to_gnc_numeric(const gchar* str, gnc_numeric *n) { - /* Read a gnc_numeric from str, skipping any leading whitespace, and - returning a pointer to just past the last byte read. Return NULL - on error. */ int num_read; long long int tmpnum; long long int tmpdenom; diff --git a/src/engine/gnc-numeric.h b/src/engine/gnc-numeric.h index 3d1c50dc18..313a506aad 100644 --- a/src/engine/gnc-numeric.h +++ b/src/engine/gnc-numeric.h @@ -158,6 +158,10 @@ gnc_numeric double_to_gnc_numeric(double in, gint64 denom, double gnc_numeric_to_double(gnc_numeric in); gchar *gnc_numeric_to_string(gnc_numeric n); + +/* Read a gnc_numeric from str, skipping any leading whitespace, and + returning a pointer to just past the last byte read. Return NULL + on error. */ const gchar *string_to_gnc_numeric(const gchar* str, gnc_numeric *n); #endif diff --git a/src/engine/gnc-schedxaction-xml-v2.c b/src/engine/gnc-schedxaction-xml-v2.c index 9a0f878796..b234b891d4 100644 --- a/src/engine/gnc-schedxaction-xml-v2.c +++ b/src/engine/gnc-schedxaction-xml-v2.c @@ -44,6 +44,8 @@ #include "sixtp-dom-parsers.h" #include "SchedXaction.h" +static short module = MOD_SX; + /** * The XML output should look something like: * XXX @@ -143,9 +145,6 @@ gnc_schedXaction_dom_tree_create(SchedXaction *sx) xmlNewTextChild( ret, NULL, SX_NAME, xaccSchedXactionGetName(sx) ); - //xmlNewTextChild( ret, NULL, "sx:manual-conf", - //(xaccSchedXactionGetManual(sx) == 1 ? "t" : "f") ); - xmlNewTextChild( ret, NULL, SX_AUTOCREATE, ( sx->autoCreateOption ? "y" : "n" ) ); xmlNewTextChild( ret, NULL, SX_AUTOCREATE_NOTIFY, @@ -323,24 +322,6 @@ sx_freqspec_handler( xmlNodePtr node, gpointer sx ) return TRUE; } -#if 0 -static -gboolean -sx_manualConf_handler( xmlNodePtr node, gpointer sx ) -{ - gchar *tmp; - - tmp = dom_tree_to_text( node ); - g_return_val_if_fail( tmp, FALSE ); - - xaccSchedXactionSetManual( (SchedXaction*)sx, - safe_strcmp(tmp, "t") == 0 ); - - g_free(tmp); - return TRUE; -} -#endif //0 - static gboolean sx_numOccur_handler( xmlNodePtr node, gpointer sx ) @@ -408,7 +389,7 @@ struct dom_tree_handler sx_dom_handlers[] = { { SX_NUM_OCCUR, sx_numOccur_handler, 0, 0 }, { SX_REM_OCCUR, sx_remOccur_handler, 0, 0 }, { SX_END, sx_end_handler, 0, 0 }, - { SX_TEMPL_ACCT, sx_templ_acct_handler, 1, 0 }, + { SX_TEMPL_ACCT, sx_templ_acct_handler, 0, 0 }, { SX_FREQSPEC, sx_freqspec_handler, 1, 0 }, { SX_SLOTS, sx_slots_handler, 0, 0 }, }; @@ -437,6 +418,15 @@ gnc_schedXaction_end_handler(gpointer data_for_children, sx = xaccSchedXactionMalloc( NULL ); + /* FIXME: this should be removed somewhere near 1.8 release time. */ + { + /* This is the just-created template acct. It can safely be + removed, as we either will find or don't have a relevent + template_acct. */ + xaccAccountDestroy( sx->template_acct ); + sx->template_acct = NULL; + } + successful = dom_tree_generic_parse( tree, sx_dom_handlers, sx ); if ( successful ) { @@ -446,6 +436,40 @@ gnc_schedXaction_end_handler(gpointer data_for_children, xaccSchedXactionFree( sx ); } + /* FIXME: this should be removed somewhere near 1.8 release time. */ + if ( sx->template_acct == NULL ) + { + AccountGroup *ag; + char *id; + Account *acct; + + ag = acct = id = NULL; + + /* We're dealing with a pre-200107 rgmerk + change re: storing template accounts. */ + /* Fix: get account with name of our GUID from the template + accounts group. Make that our template_acct pointer. */ + id = guid_to_string( xaccSchedXactionGetGUID( sx ) ); + ag = gnc_book_get_template_group( ((sixtp_gdv2*)gdata->data)->book ); + if ( ag == NULL ) + { + PERR( "Error getting template account group from being-parsed Book." ); + xmlFreeNode( tree ); + return FALSE; + } + acct = xaccGetAccountFromName( ag, id ); + if ( acct == NULL ) + { + PERR( "Error getting template account with name \"%s\"", id ); + xmlFreeNode( tree ); + return FALSE; + } + DEBUG( "Got template account with name \"%s\" for SX with GUID \"%s\"", + xaccAccountGetName( acct ), id ); + /* FIXME: free existing template account. */ + sx->template_acct = acct; + } + xmlFreeNode( tree ); return successful; @@ -459,14 +483,33 @@ gnc_schedXaction_sixtp_parser_create(void) static gboolean -tt_act_handler( xmlNodePtr node, gpointer data) +tt_act_handler( xmlNodePtr node, gpointer data ) { gnc_template_xaction_data *txd = data; Account *acc; + gnc_commodity *com; + acc = dom_tree_to_account( node ); + if ( acc == NULL ) { return FALSE; } else { + /* Check for the lack of a commodity [signifying that the + pre-7/11/2001-CIT-change SX template Account was parsed [but + incorrectly]. */ + if ( xaccAccountGetCommodity( acc ) == NULL ) { + /* FIXME: This should first look in the table of the + book, maybe? The right thing happens [WRT file + load/save] if we just _new all the time, but it + doesn't seem right. This whole block should go + away at some point, but the same concern still + applies for + SchedXaction.c:xaccSchedXactionInit... */ + com = gnc_commodity_new( "template", "template", + "template", "template", 1 ); + xaccAccountSetCommodity( acc, com ); + } + txd->accts = g_list_append( txd->accts, acc ); } return TRUE; diff --git a/src/engine/kvp_doc.txt b/src/engine/kvp_doc.txt index 57a71363a2..359ddc58cf 100644 --- a/src/engine/kvp_doc.txt +++ b/src/engine/kvp_doc.txt @@ -153,19 +153,24 @@ Use: Storage for the various fields of a scheduled transaction's Name: sched-xaction/xfrm Type: GUID -Entities: Split +Entities: Split associated with a SchedXaction Use: The GUID of this Split's xfrm account. Name: sched-xaction/amnt Type: string -Entities: Split in a SchedXaction +Entities: Split associated with a SchedXaction Use: The amount field of a SchedXaction might be a formula, which we store here. +Name: from-sched-xaction +Type: GUID +Entities: Transaction +Use: Identifies that the Transaction was created from a ScheduledTransaction, + and stores the GUID of that SX. + Name: user-keys Type: frame Entities: All Use: This frame is used to store keys which are editable directly by the user. The program should not attach any semantics to keys under this frame. - diff --git a/src/gnc-exp-parser.c b/src/gnc-exp-parser.c index a624c18f35..4a2a5bac0c 100644 --- a/src/gnc-exp-parser.c +++ b/src/gnc-exp-parser.c @@ -22,6 +22,7 @@ #include #include +#include #include #include "finproto.h" @@ -253,7 +254,7 @@ gnc_exp_parser_set_value (const char * variable_name, gnc_numeric value) key = g_strdup (variable_name); - pnum = g_new(ParserNum, 1); + pnum = g_new0(ParserNum, 1); pnum->value = value; g_hash_table_insert (variable_bindings, key, pnum); @@ -269,7 +270,7 @@ make_predefined_vars_helper (gpointer key, gpointer value, gpointer data) var = g_new0 (var_store, 1); - pnum = g_new (ParserNum, 1); + pnum = g_new0 (ParserNum, 1); *pnum = *pnum_old; var->variable_name = g_strdup(key); @@ -279,6 +280,16 @@ make_predefined_vars_helper (gpointer key, gpointer value, gpointer data) *vars_p = var; } +static void +make_predefined_vars_from_external_helper( gpointer key, gpointer value, gpointer data ) +{ + ParserNum *pnum = g_new0( ParserNum, 1 ); + if ( value != NULL ) + pnum->value = *(gnc_numeric*)value; + + make_predefined_vars_helper( key, pnum, data ); +} + static var_store_ptr make_predefined_variables (void) { @@ -354,7 +365,7 @@ numeric_ops(char op_sym, if ((left == NULL) || (right == NULL)) return NULL; - result = (op_sym == ASN_OP) ? left : g_new(ParserNum, 1); + result = (op_sym == ASN_OP) ? left : g_new0(ParserNum, 1); switch (op_sym) { @@ -396,8 +407,16 @@ negate_numeric(void *value) } gboolean -gnc_exp_parser_parse (const char * expression, gnc_numeric *value_p, - char **error_loc_p) +gnc_exp_parser_parse( const char * expression, gnc_numeric *value_p, + char **error_loc_p ) +{ + return gnc_exp_parser_parse_seperate_vars( expression, value_p, + error_loc_p, NULL ); +} + +gboolean +gnc_exp_parser_parse_seperate_vars (const char * expression, gnc_numeric *value_p, + char **error_loc_p, GHashTable *varHash ) { parser_env_ptr pe; var_store_ptr vars; @@ -417,6 +436,11 @@ gnc_exp_parser_parse (const char * expression, gnc_numeric *value_p, result.next_var = NULL; vars = make_predefined_variables (); + + if ( varHash != NULL ) { + g_hash_table_foreach( varHash, make_predefined_vars_from_external_helper, &vars); + } + lc = gnc_localeconv (); pe = init_parser (vars, *lc->mon_decimal_point, *lc->mon_thousands_sep, @@ -460,8 +484,31 @@ gnc_exp_parser_parse (const char * expression, gnc_numeric *value_p, last_error = get_parse_error (pe); } - update_variables (vars); - update_variables (parser_get_vars (pe)); + if ( varHash != NULL ) { + var_store_ptr newVars; + gpointer maybeKey, maybeValue; + gnc_numeric *numericValue; + + newVars = parser_get_vars( pe ); + for ( ; newVars ; newVars = newVars->next_var ) { + pnum = newVars->value; + if ( g_hash_table_lookup_extended( varHash, newVars->variable_name, + &maybeKey, &maybeValue ) ) { + g_hash_table_remove( varHash, maybeKey ); + g_free( maybeKey ); + g_free( maybeValue ); + } + numericValue = g_new0( gnc_numeric, 1 ); + *numericValue = ((ParserNum*)newVars->value)->value; + numericValue = NULL; + g_hash_table_insert( varHash, + g_strdup(newVars->variable_name), + numericValue ); + } + } else { + update_variables (vars); + update_variables (parser_get_vars (pe)); + } free_predefined_variables (vars); diff --git a/src/gnc-exp-parser.h b/src/gnc-exp-parser.h index fdb5084243..1ff6e71535 100644 --- a/src/gnc-exp-parser.h +++ b/src/gnc-exp-parser.h @@ -26,13 +26,13 @@ #include "gnc-numeric.h" - /* Initialize the expression parser. If this function is not * called before one of the other parsing routines (other than - * gnc_exp_parser_shutdown), it will be called if needed. */ + * gnc_exp_parser_shutdown), it will be called if needed. + */ void gnc_exp_parser_init (void); -/* Shutdown the expression parser and free any associated memory. */ +/* Shutdown the expression parser and free any associated memory in the ParserState. */ void gnc_exp_parser_shutdown (void); /* Return a list of variable names which are currently defined @@ -54,7 +54,8 @@ gboolean gnc_exp_parser_get_value (const char * variable_name, /* Set the value of the variable to the given value. If the variable is * not already defined, it will be after the call. */ -void gnc_exp_parser_set_value (const char * variable_name, gnc_numeric value); +void gnc_exp_parser_set_value (const char * variable_name, + gnc_numeric value); /* Parse the given expression using the current variable definitions. * If the parse was successful, return TRUE and, if value_p is @@ -63,8 +64,18 @@ void gnc_exp_parser_set_value (const char * variable_name, gnc_numeric value); * returned and error_loc_p is non-NULL, *error_loc_p is set to the * character in expression where parsing aborted. If TRUE is returned * and error_loc_p is non-NULL, *error_loc_p is set to NULL. */ -gboolean gnc_exp_parser_parse (const char * expression, gnc_numeric *value_p, - char **error_loc_p); +gboolean gnc_exp_parser_parse (const char * expression, + gnc_numeric *value_p, + char **error_loc_p ); + +/* Parses as per gnc_exp_parser_parse, but with an optional last argument + * dealing with a non-shared variable list and state, local to the expression + * being parsed. This is a hashTable of variable names mapping to + * gnc_numeric pointers. */ +gboolean gnc_exp_parser_parse_seperate_vars (const char * expression, + gnc_numeric *value_p, + char **error_loc_p, + GHashTable *varHash ); /* If the last parse returned FALSE, return an error string describing * the problem. Otherwise, return NULL. */ diff --git a/src/gnome/dialog-scheduledxaction.c b/src/gnome/dialog-scheduledxaction.c index e445c43e42..f69cf9e3c3 100644 --- a/src/gnome/dialog-scheduledxaction.c +++ b/src/gnome/dialog-scheduledxaction.c @@ -351,8 +351,6 @@ scheduledxaction_editor_dialog_destroy(GtkObject *object, gpointer data) gtk_widget_destroy( sxed->dialog ); sxed->sx = NULL; - /* xaccAccountDestroy( sxed->tmpAcct ); */ - g_free(sxed); } @@ -485,7 +483,7 @@ gnc_ui_scheduled_xaction_editor_dialog_create( SchedXactionDialog *sxd, } widgets[] = { { "ok_button", "clicked", editor_ok_button_clicked, NULL }, { "cancel_button", "clicked", editor_close_button_clicked, NULL }, - { "help_button", "clicked", editor_help_button_clicked, NULL}, + { "help_button", "clicked", editor_help_button_clicked, NULL}, { "rb_enddate", "toggled", endgroup_rb_toggled, (gpointer)END_OPTION }, { "rb_num_occur", "toggled", endgroup_rb_toggled, (gpointer)NUM_OCCUR_OPTION }, diff --git a/src/gnome/dialog-sxsincelast.c b/src/gnome/dialog-sxsincelast.c index 1a5334504d..66aff73c45 100644 --- a/src/gnome/dialog-sxsincelast.c +++ b/src/gnome/dialog-sxsincelast.c @@ -25,6 +25,8 @@ #include #include +#include "gnc-ui.h" +#include "finvar.h" #include "gnc-engine-util.h" #include "FileDialog.h" #include "gnc-book.h" @@ -41,42 +43,108 @@ #include "dialog-sxsincelast.h" #define DIALOG_SXSINCELAST_CM_CLASS "dialog-sxsincelast" +#define DIALOG_SXSINCELAST_REMIND_CM_CLASS "dialog-sxsincelast-remind" static short module = MOD_SX; -typedef struct _toCreateTransaction { - SchedXaction *sx; - GDate *date; - - gint clistRow; -} toCreateTransaction; - +/** + * There are two dialogs controled in this file: the since-last-run dialog, + * and the reminder-dialog. They cooperate and inter-relate, so they're both + * handled here. + **/ typedef struct _sxSinceLastData { GtkWidget *sxsincelastDlg; GladeXML *gxml; - GList /* */ *transList; + + GtkWidget *dlg_remind; + GladeXML *gxml_remind; + + gboolean sincelast_displayed; + gboolean remind_displayed; + + GList /* */ *toCreateData; + GList /* */ *reminderData; } 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; +/* 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; +/* Next auto-create clist row index to create. */ +static int acl_row = 0; + static void sxsincelast_init( sxSinceLastData *sxsld ); -static void sxsincelast_populate( sxSinceLastData *sxsld ); + +static gboolean sxsincelast_populate( sxSinceLastData *sxsld ); static void sxsincelast_close_handler( gpointer ud ); +static void sxsld_remind_close_handler( gpointer ud ); static void sxsincelast_ok_clicked( GtkButton *b, gpointer ud ); +static void sxsld_remind_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 ); +static void _create_transactions_on( SchedXaction *sx, + GDate *gd, + toCreateTuple *tct ); +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_variable_table( sxSinceLastData *sxsld ); -static void processAutoCreateList( GList *, sxSinceLastData *sxsld, SchedXaction * ); -static void processToCreateList( GList *, sxSinceLastData *sxsld, SchedXaction * ); -static void processReminderList( GList *, sxSinceLastData *sxsld, SchedXaction * ); +static void processAutoCreateList( GList *, + sxSinceLastData *sxsld, + SchedXaction * ); +static void processToCreateList( GList *, + sxSinceLastData *sxsld, + SchedXaction * ); +static void processReminderList( GList *, + sxSinceLastData *sxsld, + SchedXaction * ); +static void processSelectedReminderList( GList *, sxSinceLastData * ); -/* int parse_vars_from_formula( const char *formula, GHashTable *varHash ); */ +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 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 ); void gnc_ui_sxsincelast_guile_wrapper( char *foo ) @@ -88,8 +156,21 @@ void gnc_ui_sxsincelast_dialog_create(void) { sxSinceLastData *sxsld = g_new0( sxSinceLastData, 1 ); - sxsld->gxml = gnc_glade_xml_new( "sched-xact.glade", "Real Since-Last-Run Dialog" ); - sxsld->sxsincelastDlg = glade_xml_get_widget( sxsld->gxml, "Real Since-Last-Run Dialog" ); + + sxsld->toCreateData = sxsld->reminderData = NULL; + sxsld->remind_displayed = sxsld->sincelast_displayed = FALSE; + + sxsld->gxml = gnc_glade_xml_new( "sched-xact.glade", + "Real Since-Last-Run Dialog" ); + sxsld->sxsincelastDlg = + glade_xml_get_widget( sxsld->gxml, + "Real Since-Last-Run Dialog" ); + + + sxsld->gxml_remind = gnc_glade_xml_new( "sched-xact.glade", + "Since-Last Reminders" ); + sxsld->dlg_remind = glade_xml_get_widget( sxsld->gxml_remind, + "Since-Last Reminders" ); sxsincelast_init( sxsld ); } @@ -97,37 +178,75 @@ gnc_ui_sxsincelast_dialog_create(void) static void sxsincelast_init( sxSinceLastData *sxsld ) { - GtkWidget *o; - int i; + GtkWidget *w; + int i; struct widgetSignalHandlerTuple { char *name; char *signal; void (*handlerFn)(); } widgets[] = { - { "ok_button", "clicked", sxsincelast_ok_clicked }, - { "cancel_button", "clicked", sxsincelast_cancel_clicked }, - { NULL, NULL, NULL } + { "ok_button", "clicked", sxsincelast_ok_clicked }, + { "cancel_button", "clicked", sxsincelast_cancel_clicked }, + { "to_create_clist", "select-row", sxsincelast_tc_row_sel }, + { "to_create_clist", "unselect-row", sxsincelast_tc_row_unsel }, + { NULL, NULL, NULL } + }, widgets_remind[] = { + { "ok_button", "clicked", sxsld_remind_ok_clicked }, + { "reminder_clist", "select-row", sxsld_remind_row_toggle }, + { "reminder_clist", "unselect-row", sxsld_remind_row_toggle }, + { NULL, NULL, NULL } }; gnc_register_gui_component( DIALOG_SXSINCELAST_CM_CLASS, NULL, sxsincelast_close_handler, sxsld->sxsincelastDlg ); + + gnc_register_gui_component( DIALOG_SXSINCELAST_REMIND_CM_CLASS, + NULL, + sxsld_remind_close_handler, + sxsld->dlg_remind ); gtk_signal_connect( GTK_OBJECT(sxsld->sxsincelastDlg), "destroy", GTK_SIGNAL_FUNC( sxsincelast_destroy ), sxsld ); + /* Note: we don't add a 'destroy' signal handler because the + appropriate work [freeing since-last-run-related data structures] + is done in the sxsincelast_destroy function. */ for ( i=0; widgets[i].name != NULL ; i++ ) { - o = glade_xml_get_widget( sxsld->gxml, widgets[i].name ); - gtk_signal_connect( GTK_OBJECT(o), widgets[i].signal, + w = glade_xml_get_widget( sxsld->gxml, widgets[i].name ); + gtk_signal_connect( GTK_OBJECT(w), widgets[i].signal, GTK_SIGNAL_FUNC(widgets[i].handlerFn), sxsld ); } - sxsincelast_populate( sxsld ); + for ( i=0; widgets_remind[i].name != NULL ; i++ ) { + w = glade_xml_get_widget( sxsld->gxml_remind, + widgets_remind[i].name ); + gtk_signal_connect( GTK_OBJECT(w), widgets_remind[i].signal, + GTK_SIGNAL_FUNC( widgets_remind[i].handlerFn ), + sxsld ); + } - gtk_widget_show_all( sxsld->sxsincelastDlg ); + /* set all to-create clist columns to auto-resize. */ + w = glade_xml_get_widget( sxsld->gxml, "to_create_clist" ); + for ( i=0; i<3; i++ ) { + gtk_clist_set_column_auto_resize( GTK_CLIST(w), i, TRUE ); + } + w = glade_xml_get_widget( sxsld->gxml_remind, "reminder_clist" ); + for ( i=0; i<3; i++ ) { + gtk_clist_set_column_auto_resize( GTK_CLIST(w), i, TRUE ); + } + + rl_row = tcl_row = acl_row = 0; + + /* FIXME: deal with neither dialog being displayed [read: + nothing-to-do.] */ + if ( sxsincelast_populate( sxsld ) ) { + gtk_widget_show_all( sxsld->sxsincelastDlg ); + sxsld->sincelast_displayed = TRUE; + } } static void @@ -165,41 +284,51 @@ _free_gdate_list_elts( gpointer data, gpointer user_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 ) { - char tmpBuf[26]; - int row; GtkCList *cl; gboolean autoCreateState, notifyState; char *rowText[2]; - row = 0; - - /* - get the "automagically created and notification requested" - register, and create the entires. - For now, this is a clist... - */ + /* get the "automagically created and notification requested" + * register, and create the entries in it. For now, this is a clist, + * but it _should_ be a GL... */ cl = GTK_CLIST( glade_xml_get_widget( sxsld->gxml, "auto_create_clist" ) ); gtk_clist_freeze( cl ); while ( autoCreateList ) { - g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", - (GDate*)autoCreateList->data ); xaccSchedXactionGetAutoCreate( sx, &autoCreateState, ¬ifyState ); - - _create_transactions_on( sx, (GDate*)autoCreateList->data ); - + _create_transactions_on( sx, (GDate*)autoCreateList->data, NULL ); if ( notifyState ) { rowText[0] = xaccSchedXactionGetName( sx ); - rowText[1] = malloc( sizeof(gchar) * 26 ); /* FIXME */ + rowText[1] = g_new0( gchar, 26 ); g_date_strftime( rowText[1], 25, "%a, %b %e, %Y", (GDate*)autoCreateList->data ); - gtk_clist_insert( cl, row++, rowText ); + gtk_clist_insert( cl, acl_row++, rowText ); + g_free( rowText[1] ); } autoCreateList = autoCreateList->next; @@ -211,61 +340,128 @@ processAutoCreateList( GList *autoCreateList, sxSinceLastData *sxsld, SchedXacti static void processToCreateList( GList *toCreateList, sxSinceLastData *sxsld, SchedXaction *sx ) { + toCreateTuple *tct; GtkCList *clist; - int row; - char *rowText[2]; + char *rowText[3]; - row = 0; 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] = malloc( sizeof(char) * 25 ); /* FIXME */ - g_date_strftime( rowText[1], 25, "%a, %b %e, %Y", (GDate*)toCreateList->data ); - gtk_clist_insert( clist, row++, rowText ); + rowText[1] = g_new0( char, 25 ); + g_date_strftime( rowText[1], 25, "%a, %b %e, %Y", 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, SchedXaction *sx ) { GtkCList *clist; - int row; char *rowText[3]; reminderTuple *rt; + gboolean showIt; - /* . need - . orig end date - . this instance date - . sx - */ - - row = 0; + showIt = (g_list_length( reminderList ) > 0); if ( reminderList == NULL ) return; - clist = GTK_CLIST( glade_xml_get_widget( sxsld->gxml, "reminders_clist" ) ); + clist = GTK_CLIST( glade_xml_get_widget( sxsld->gxml_remind, "reminder_clist" ) ); do { rt = (reminderTuple*)reminderList->data; /* add to clist [ahem... register... ahem] */ rowText[0] = xaccSchedXactionGetName( sx ); - rowText[1] = malloc( sizeof(gchar) * 25 ); /* FIXME */ + rowText[1] = g_new0( gchar, 25 ); /* FIXME: appropriate size */ g_date_strftime( rowText[1], 25, "%a, %b %e, %Y", rt->occurDate ); - rowText[2] = malloc( sizeof(gchar) * 5 ); /* FIXME */ + 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, row++, rowText ); + 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] ); + + sxsld->reminderData = g_list_append( sxsld->reminderData, rt ); + } while ( (reminderList = reminderList->next) ); + + if ( showIt ) { + gtk_widget_show_all( sxsld->dlg_remind ); + sxsld->remind_displayed = TRUE; + } } - +/** + * 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; + + while ( goodList ) { + rt = (reminderTuple*)goodList->data; + + list = NULL; + + xaccSchedXactionGetAutoCreate( rt->sx, + &autoCreateOpt, ¬ifyOpt ); + 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 ); + } + /* FIXME: Cleanup appropriately? */ + g_list_free( list ); + list = NULL; + + goodList = goodList->next; + } +} + +/** + * Returns TRUE if there's some populated in the dialog to show to the user, + * FALSE if not. + **/ +static gboolean sxsincelast_populate( sxSinceLastData *sxsld ) { @@ -276,10 +472,12 @@ sxsincelast_populate( sxSinceLastData *sxsld ) GDate *instDate; gint daysInAdvance; gboolean autocreateState, notifyState; - gchar *rowText[3]; GtkWidget *w; gint autoCreateRow, toCreateRow, remindersRow; reminderTuple *rt; + gboolean showIt; + + showIt = FALSE; autoCreateList = toCreateList = reminderList = NULL; autoCreateRow = toCreateRow = remindersRow = 0; @@ -288,7 +486,7 @@ sxsincelast_populate( sxSinceLastData *sxsld ) if ( sxList == NULL ) { DEBUG( "No scheduled transactions to populate." ); - return; + return FALSE; } do { @@ -301,16 +499,19 @@ sxsincelast_populate( sxSinceLastData *sxsld ) daysInAdvance = xaccSchedXactionGetAdvanceReminder( sx ); g_date_add_days( &endPlusReminders, daysInAdvance ); - - if (0) { + +#if 0 +/* NOT USED */ + { char tmpBuf[26]; g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", &end ); - DEBUG( "We'll generate the appropriate instances, now for " + DEBUG( "We'll generate the appropriate instances now for " "SX \"%s\" with end time %s...", xaccSchedXactionGetName(sx), tmpBuf ); } +#endif /* 0 */ instanceList = NULL; _generate_instances( sx, &endPlusReminders, &instanceList ); @@ -320,54 +521,145 @@ sxsincelast_populate( sxSinceLastData *sxsld ) xaccSchedXactionGetAutoCreate( sx, &autocreateState, ¬ifyState ); do { instDate = (GDate*)instanceList->data; - if ( 0 ) { +#if 0 +/* NOT USED */ + { char tmpBuf[26]; g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", instDate ); DEBUG( "SX \"%s\" instance on %s", xaccSchedXactionGetName(sx), tmpBuf ); } +#endif /* 0 */ if ( (g_date_compare( &end, &endPlusReminders ) != 0) && (g_date_compare( &end, instDate ) <= 0) ) { - /* DEBUG( "\tIs a reminder" ); */ rt = g_new0( reminderTuple, 1 ); - rt->endDate = &end; - rt->occurDate = instDate; + rt->sx = sx; + rt->endDate = &end; + rt->occurDate = instDate; + rt->isSelected = FALSE; reminderList = g_list_append( reminderList, rt ); } else { if ( autocreateState ) { - /* DEBUG( "\tauto-create" ); */ autoCreateList = g_list_append( autoCreateList, instDate ); } else { - /* DEBUG( "\tAdding to create list" ); */ toCreateList = g_list_append( toCreateList, instDate ); } } } while ( (instanceList = instanceList->next) ); - /* process the lists */ + /* 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 ); processReminderList ( reminderList, sxsld, sx ); + processToCreateList ( toCreateList, sxsld, sx ); g_list_foreach( autoCreateList, _free_gdate_list_elts, NULL ); g_list_free( autoCreateList ); + autoCreateList = NULL; - g_list_foreach( reminderList, _free_reminderTuple_list_elts, 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; + + /* We have moved the reminderTuples over to the reminderData + list in sxsld, so we don't free them here. */ g_list_free( reminderList ); - - /* FIXME: freeup the toCreate list at a more appropriate - time. */ + reminderList = NULL; } while ( (sxList = sxList->next) ); + + return showIt; } +static void +_clean_sincelast_dlg( sxSinceLastData *sxsld ) +{ + GtkWidget *w; + + /* . clean out to-create clist + * . free associated memories. + */ + _clean_variable_table( sxsld ); + + w = glade_xml_get_widget( sxsld->gxml, "auto_create_clist" ); + gtk_clist_clear( GTK_CLIST(w) ); + acl_row = 0; + + 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; + +} + +/** + * 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. + **/ static void sxsincelast_close_handler( gpointer ud ) { - gnome_dialog_close( GNOME_DIALOG( ((sxSinceLastData*)ud)->sxsincelastDlg ) ); + sxSinceLastData *sxsld = (sxSinceLastData*)ud; + g_list_foreach( sxsld->toCreateData, _free_toCreate_list_elts, NULL ); + g_list_free( sxsld->toCreateData ); + sxsld->toCreateData = NULL; + + if ( sxsld->sincelast_displayed ) { + DEBUG( "since-last dialog displayed" ); + } + + if ( sxsld->remind_displayed ) { + DEBUG( "reminder dialog displayed, just hiding the since-last dialog." ); + /* FIXME: need to clean out old ui shit. */ + _clean_sincelast_dlg( sxsld ); + gtk_widget_hide( sxsld->sxsincelastDlg ); + sxsld->sincelast_displayed = FALSE; + } else { + DEBUG( "reminder dialog !displayed, closing both." ); + if ( sxsld->dlg_remind != NULL ) { + PERR( "How did the reminder dialog get !displayed, but !destroyed?" ); + sxsld_remind_close_handler( sxsld ); + } + + gnome_dialog_close( GNOME_DIALOG( sxsld->sxsincelastDlg ) ); + sxsld->sxsincelastDlg = NULL; + sxsld->sincelast_displayed = FALSE; + } + + /* FIXME -- cleanup: + * . reminder data? + * . memory allocated for UI elts [labels and such]? + * do this in the _destroy... + */ +} + +static void +sxsld_remind_close_handler( gpointer ud ) +{ + sxSinceLastData *sxsld; + + sxsld = (sxSinceLastData*)ud; + + gnome_dialog_close( GNOME_DIALOG( sxsld->dlg_remind ) ); + sxsld->dlg_remind = NULL; + sxsld->remind_displayed = FALSE; } static void @@ -391,73 +683,53 @@ sxsincelast_ok_clicked( GtkButton *b, gpointer ud ) gchar buf[1024]; gint row; char *rowText[2]; - toCreateTransaction *tct; + toCreateTuple *tct; + GList *tcList; + gboolean allVarsBound; sxsld = (sxSinceLastData*)ud; - sxsincelast_close_handler( sxsld ); - /* FIXME: the SX/date list already has been generated; we should use that. */ -#if 0 - o = glade_xml_get_widget( sxsld->gxml, "next_date" ); - gdeDate = gnome_date_edit_get_date( GNOME_DATE_EDIT(o) ); + cl = GTK_CLIST(glade_xml_get_widget( sxsld->gxml, "to_create_clist" )); - DEBUG( "Okay... I should run with a date of: %s", ctime(&gdeDate) ); + /* First: check to make sure all TCTs are 'ready' [and return if not]. + * Second: create the entries based on the variable bindings. */ - /* destroy the previous transactions - destroy all the toCreateTransactions */ - if ( sxsld->transList != NULL ) { - g_list_foreach( sxsld->transList, free_elts, NULL ); - g_list_free( sxsld->transList ); - sxsld->transList = NULL; - } - - book = gncGetCurrentBook(); - sxList = gnc_book_get_schedxactions( book ); - if ( sxList == NULL ) { - PERR( "No scheduled transactions to play with\n" ); + tcList = sxsld->toCreateData; + if ( tcList == NULL ) { + DEBUG( "No transactions to create..." ); + sxsincelast_close_handler( sxsld ); return; } - - endDate = g_date_new(); - g_date_set_time( endDate, gdeDate ); - - o = glade_xml_get_widget( sxsld->gxml, "replace_with_register" ); - cl = GTK_CLIST(o); - gtk_clist_clear( cl ); - - g_date_clear( &gd, 1 ); - row = 0; do { - sx = (SchedXaction*)sxList->data; - // this is really "last-run-date", right? - g_date_set_time( &gd, time(NULL) ); - - gd = xaccSchedXactionGetInstanceAfter( sx, &gd ); - while ( g_date_compare( &gd, endDate ) <= 0 - && g_date_valid( &gd ) ) { - - g_date_strftime( buf, 1023, "%a, %b %e, %Y", &gd ); - /* add to clist [ahem... register... ahem] */ - rowText[0] = xaccSchedXactionGetName( sx ); - rowText[1] = malloc( sizeof(char) * 256 ); /* FIXME */ - g_date_strftime( rowText[1], 255, "%a, %b %e, %Y", &gd ); - - tct = g_new0( toCreateTransaction, 1 ); - tct->sx = sx; - tct->date = g_date_new(); - *tct->date = gd; - tct->clistRow = row; - sxsld->transList = g_list_append( sxsld->transList, tct ); - - gtk_clist_insert( cl, row, rowText ); - row += 1; - gd = xaccSchedXactionGetInstanceAfter( sx, &gd ); + tct = (toCreateTuple*)tcList->data; + allVarsBound = TRUE; + g_hash_table_foreach( tct->varBindings, + _andequal_numerics_set, + &allVarsBound ); + if ( !allVarsBound ) { + char tmpBuf[26]; + g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", 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 ( (sxList = sxList->next) ); + } 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 ); + } while ( (tcList = tcList->next) ); + + /* FIXME: cleanup appropriately. */ + sxsincelast_close_handler( sxsld ); - g_date_free( endDate ); -#endif } static void @@ -466,41 +738,120 @@ 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_seperate_vars? */ + if ( !gnc_exp_parser_parse_seperate_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 ); + } + 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 ) { + /* appropriate place to destroy data structures */ + _clean_sincelast_dlg( (sxSinceLastData*)ud ); + /* FIXME: remains: reminder data. */ DEBUG( "nuttin' doin...\n" ); } static gboolean -_create_each_transaction( Transaction *t, void *d ) +_create_each_transaction_helper( Transaction *t, void *d ) { - Transaction *newT; - GDate *gd; - GList *sList; - GList *osList; - Split *split; - kvp_frame *split_kvpf; - kvp_value *kvp_val; - gboolean errFlag; + Transaction *newT; + GList *sList; + GList *osList; + Split *split; + kvp_frame *split_kvpf; + kvp_value *kvp_val; + gboolean errFlag; + toCreateTuple *tct; + gnc_commodity *commonCommodity = NULL; errFlag = FALSE; - DEBUG( "I'm seeing Transaction \"%s\"", - xaccTransGetDescription( t ) ); + /* 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. */ - gd = (GDate*)d; + /* 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 ) ); + + tct = (toCreateTuple*)d; newT = xaccMallocTransaction(); xaccTransBeginEdit( newT ); /* the action and description/memo are in the template */ gnc_copy_trans_onto_trans( t, newT, FALSE, FALSE ); - /* the date is new [gd] */ + /* the date is new [by definition :)] */ xaccTransSetDate( newT, - g_date_day( gd ), - g_date_month( gd ), - g_date_year( gd ) ); + 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 ); @@ -516,12 +867,8 @@ _create_each_transaction( Transaction *t, void *d ) 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 ) ); - + DEBUG( "\tProcessing Split \"%s\"", xaccSplitGetMemo( split ) ); + DEBUG( "\tkvp_frame: %s\n", kvp_frame_to_string( split_kvpf ) ); /* from-transaction of splits */ { @@ -536,40 +883,57 @@ _create_each_transaction( Transaction *t, void *d ) acct = xaccAccountLookup( acct_guid ); DEBUG( "Got account with name \"%s\"", xaccAccountGetName( acct ) ); - /* xaccSplitSetAccount( split, 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; - gnc_numeric credit_num; - gnc_numeric debit_num; - gnc_numeric final; - int gncn_error; + char *str, *parseErrorLoc; + gnc_numeric credit_num, debit_num, final; + int gncn_error; kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/credit_formula" ); str = kvp_value_get_string( kvp_val ); credit_num = gnc_numeric_create( 0, 1 ); - if ( str != NULL ) { - /* parse_vars_from_formula( str, NULL ); */ - - xaccParseAmount( str, TRUE, &credit_num, NULL ); - /* string_to_gnc_numeric( str, &credit_num ); */ - DEBUG( "gnc_numeric::credit: \"%s\" -> \"%s\"", - str, gnc_numeric_to_string( credit_num ) ); + if ( str != NULL + && strlen(str) != 0 ) { + if ( ! gnc_exp_parser_parse_seperate_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( split_kvpf, "sched-xaction/debit_formula" ); str = kvp_value_get_string( kvp_val ); debit_num = gnc_numeric_create( 0, 1 ); - if ( str != NULL ) { - /* parse_vars_from_formula( str, NULL ); */ + if ( str != NULL + && strlen(str) != 0 ) { + if ( ! gnc_exp_parser_parse_seperate_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; + } - xaccParseAmount( str, TRUE, &debit_num, NULL ); - /* string_to_gnc_numeric( str, &debit_num ); */ - DEBUG( "gnc_numeric::debit: \"%s\" -> \"%s\"", - str, gnc_numeric_to_string( debit_num ) ); + 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 ); @@ -585,11 +949,40 @@ _create_each_transaction( Transaction *t, void *d ) xaccSplitSetValue( split, final ); } #if 0 +/* NOT [YET] USED */ kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/shares" ); kvp_val = kvp_frame_get_slot( split_kvpf, "sched-xaction/amnt" ); #endif /* 0 */ + + /* FIXME: + * . Want to save the scheduled transaction GUID in the + * newly-created transactions/splits. + * . Want 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 ); @@ -598,11 +991,14 @@ _create_each_transaction( Transaction *t, void *d ) } 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 ) +_create_transactions_on( SchedXaction *sx, GDate *gd, toCreateTuple *tct ) { AccountGroup *ag; Account *acct; @@ -615,153 +1011,457 @@ _create_transactions_on( SchedXaction *sx, GDate *gd ) 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; + } + + 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; + } + ag = gnc_book_get_template_group( gncGetCurrentBook() ); id = guid_to_string( xaccSchedXactionGetGUID(sx) ); acct = xaccGetAccountFromName( ag, id ); - DEBUG( "Got account \"%s\"\n", - xaccAccountGetName( acct ) ); + /* FIXME: handle not being able to get any of the above. */ g_free( id ); xaccAccountForEachTransaction( acct, - _create_each_transaction, - gd ); - xaccSchedXactionSetLastOccurDate( sx, gd ); - + _create_each_transaction_helper, + tct ); + xaccSchedXactionSetLastOccurDate( sx, tct->date ); } -#if 0 +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( gncGetCurrentBook() ); + 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( kvpf, "sched-xaction/credit_formula" ); + 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( kvpf, "sched-xaction/debit_formula" ); + 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, *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 = g_string_sized_new( 32 ); + /* FIXME: Should use xaccPrintAmount(...) */ + g_string_sprintf( numValueStr, "%.2f", gnc_numeric_to_double( *numValue ) ); + gtk_entry_set_text( GTK_ENTRY(entry), numValueStr->str ); + g_string_free( numValueStr, TRUE ); + } + + 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..." ); + 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 ) ); +} -/** - * Parses in-fix mathematical formulas using the standard operators - * [+-/%*], and '(', ')' grouping. - * - * Any strings are placed in the GHashTable as variables. The value of - * each key in the hash-table is a struct of the form: - * { const char *varStr; - * gint idx; - * gint len; }; - * - **/ int parse_vars_from_formula( const char *formula, GHashTable *varHash ) { - gnc_numeric num_foo; - char *foo; - GList *list; - - gnc_exp_parser_init(); - if ( ! gnc_exp_parser_parse( formula, &num_foo, &foo ) ) { - PERR( "Error parsing at \"%s\": %s\n", - foo, gnc_exp_parser_error_string() ); - } - PINFO( "Successful parse...\n" ); - list = gnc_exp_parser_get_variable_names(); - if ( list == NULL ) { - PINFO( "NULL variable list\n" ); - } else { - do { - PINFO( "Variable \"%s\"\n", - list->data ); - } while ( (list = list->next) ); - } + gnc_numeric numeric; + char *foo; - gnc_exp_parser_shutdown(); -} -#endif /* 0 */ + if ( ! gnc_exp_parser_parse_seperate_vars( formula, &numeric, &foo, varHash ) ) { + PERR( "Error parsing at \"%s\": %s", + foo, gnc_exp_parser_error_string() ); + return -1; + } #if 0 - GScanner *varScanner; - GTokenType tok; - - varScanner = g_scanner_new( NULL ); - g_scanner_set_scope( varScanner, 0 ); - g_scanner_freeze_symbol_table( varScanner ); - g_scanner_scope_add_symbol( varScanner, 0, "(", (gpointer)"left-paren" ); - g_scanner_scope_add_symbol( varScanner, 0, ")", (gpointer)"right-paren" ); - g_scanner_scope_add_symbol( varScanner, 0, "+", (gpointer)"plus" ); - g_scanner_scope_add_symbol( varScanner, 0, "-", (gpointer)"minus" ); - g_scanner_scope_add_symbol( varScanner, 0, "/", (gpointer)"div" ); - g_scanner_scope_add_symbol( varScanner, 0, "*", (gpointer)"mult" ); - g_scanner_thaw_symbol_table( varScanner ); - - g_scanner_input_text( varScanner, formula, strlen( formula ) ); - - do { - tok = g_scanner_get_next_token( varScanner ); - printf( "tok: " ); - switch ( varScanner->token ) { - case G_TOKEN_EOF: - printf( "EOF" ); break; - case G_TOKEN_LEFT_PAREN: - printf( "(" ); break; - case G_TOKEN_RIGHT_PAREN: - printf( ")" ); break; - case G_TOKEN_LEFT_CURLY: - printf( "{" ); break; - case G_TOKEN_RIGHT_CURLY: - printf( "}" ); break; - case G_TOKEN_LEFT_BRACE: - printf( "[" ); break; - case G_TOKEN_RIGHT_BRACE: - printf( "]" ); break; - case G_TOKEN_EQUAL_SIGN: - printf( "=" ); break; - case G_TOKEN_COMMA: - printf( "," ); break; - case G_TOKEN_NONE: - printf( "NONE" ); break; - case G_TOKEN_ERROR: - printf( "ERROR(%d)", varScanner->value.v_error ); break; - case G_TOKEN_CHAR: - printf( "CHAR(%c)", varScanner->value.v_char ); break; - case G_TOKEN_BINARY: - printf( "BINARY" ); break; - case G_TOKEN_OCTAL: - printf( "OCTAL(%ul)", varScanner->value.v_octal ); break; - case G_TOKEN_INT: - printf( "INT(%ul)", varScanner->value.v_int ); break; - case G_TOKEN_HEX: - printf( "HEX(%ul)", varScanner->value.v_hex ); break; - case G_TOKEN_FLOAT: - printf( "FLOAT(%f)", varScanner->value.v_float ); break; - case G_TOKEN_STRING: - printf( "STRING(%s)", varScanner->value.v_string ); break; - case G_TOKEN_SYMBOL: - printf( "SYMBOL(%s)", (gchar*)varScanner->value.v_symbol ); break; - case G_TOKEN_IDENTIFIER: - printf( "IDENT(%s)", varScanner->value.v_identifier ); break; - case G_TOKEN_IDENTIFIER_NULL: - printf( "NULL_IDENT" ); break; - case G_TOKEN_COMMENT_SINGLE: - case G_TOKEN_COMMENT_MULTI: - printf( "COMMENT(%s)", varScanner->value.v_comment ); break; - case G_TOKEN_LAST: - printf( "END" ); break; - default: - printf( "UNK" ); break; - }; - printf( "\n" ); - } while ( (varScanner->token != G_TOKEN_LAST) && - (varScanner->token != G_TOKEN_EOF) && - (varScanner->token != G_TOKEN_NONE) ); - return 1; - /* -"0.33 * ( base + ld ) + (0.25 * internet)": - -tok: FLOAT(0.330000) -tok: UNK -tok: ) -tok: IDENT(base) -tok: UNK -tok: IDENT(ld) -tok: ( -tok: UNK -tok: ) -tok: FLOAT(0.250000) -tok: UNK -tok: IDENT(internet) -tok: ( -tok: EOF - */ - +/* 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. + **/ +static void +sxsld_remind_ok_clicked( GtkButton *b, gpointer ud ) +{ + sxSinceLastData *sxsld; + GList *reminderList; + reminderTuple *rt, *prevRT; + char *rtName; + gboolean overallOkFlag, okFlag, prevState; + GList *badList; + GList *badRecentRun; + GList *goodList; + + rtName = NULL; + goodList = NULL; + overallOkFlag = TRUE; + + /* The following assignments are only to placate the compiler ["might + be used uninitialized"]. */ + okFlag = prevState = TRUE; + badList = badRecentRun = NULL; + rt = prevRT = NULL; + + sxsld = (sxSinceLastData*)ud; + reminderList = sxsld->reminderData; + + while ( reminderList != NULL ) { + prevRT = rt; + rt = (reminderTuple*)reminderList->data; + + if ( xaccSchedXactionGetName( rt->sx ) != rtName ) { + if ( rtName != NULL ) { + /* Deal with previous sequence. */ + overallOkFlag &= + _inform_or_add( sxsld->reminderData, + 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 ); + } + + } + + reminderList = reminderList->next; + } + + /* Deal with final sequence. */ + if ( rtName != NULL ) { + overallOkFlag &= _inform_or_add( sxsld->reminderData, + rt, okFlag, + badList, &goodList ); + } + + /* cleanup */ + g_list_free( badList ); + g_list_free( badRecentRun ); + + /* Handle implications of above logic. */ + if ( overallOkFlag ) { + /* 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 ) { + /* gtk_widget_hide( sxsld->sxsincelastDlg ); */ + processSelectedReminderList( goodList, sxsld ); + gtk_widget_show_all( sxsld->sxsincelastDlg ); + sxsld->sincelast_displayed = TRUE; + } + g_list_free( goodList ); + + sxsld_remind_close_handler( sxsld ); + + /* FIXME: sufficient? we probably want the close-handler... + gtk_widget_destroy( sxsld->dlg_remind ); + sxsld->dlg_remind = NULL; */ + } + /* else + { Just leave the dialog alone [and let the user deal with the + error dialog they just saw]. } + */ +} + +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[26]; + + rt = (reminderTuple*)data; + msg = (GString*)ud; + g_date_strftime( tmpBuf, 25, "%a, %b %e, %Y", 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; +} diff --git a/src/gnome/glade/sched-xact.glade b/src/gnome/glade/sched-xact.glade index 57c2dda024..b472f42aa8 100644 --- a/src/gnome/glade/sched-xact.glade +++ b/src/gnome/glade/sched-xact.glade @@ -258,7 +258,6 @@ GtkSpinButton advance_days - False True 1 0 @@ -336,7 +335,6 @@ GtkSpinButton remind_days - False True 1 0 @@ -613,7 +611,7 @@ 0 True - False + True @@ -2582,8 +2580,8 @@ December GtkScrolledWindow scrolledwindow1 - GTK_POLICY_ALWAYS - GTK_POLICY_ALWAYS + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC GTK_UPDATE_CONTINUOUS GTK_UPDATE_CONTINUOUS @@ -2977,12 +2975,13 @@ December GnomeDialog Real Since-Last-Run Dialog + False Since Last Run... GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER False - 550 - 500 + 640 + 480 True True True @@ -3006,7 +3005,7 @@ December GnomeDialog:action_area dialog-action_area20 GTK_BUTTONBOX_DEFAULT_STYLE - 8 + 3 85 27 7 @@ -3084,8 +3083,8 @@ December GtkScrolledWindow scrolledwindow7 - GTK_POLICY_ALWAYS - GTK_POLICY_ALWAYS + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC GTK_UPDATE_CONTINUOUS GTK_UPDATE_CONTINUOUS @@ -3147,58 +3146,246 @@ December - GtkScrolledWindow - scrolledwindow6 - GTK_POLICY_ALWAYS - GTK_POLICY_ALWAYS - GTK_UPDATE_CONTINUOUS - GTK_UPDATE_CONTINUOUS + GtkHPaned + hpaned1 + 10 + 10 + 400 - GtkCList - to_create_clist - True - 2 - 210,80 - GTK_SELECTION_SINGLE - True - GTK_SHADOW_IN + GtkScrolledWindow + scrolledwindow8 + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_UPDATE_CONTINUOUS + GTK_UPDATE_CONTINUOUS + + True + False + - GtkLabel - CList:title - label847802 - - GTK_JUSTIFY_CENTER - False - 0.5 - 0.5 - 0 - 0 + GtkCList + to_create_clist + True + 3 + 80,123,80 + GTK_SELECTION_SINGLE + True + GTK_SHADOW_IN + + + GtkLabel + CList:title + label847820 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkLabel + CList:title + label847821 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkLabel + CList:title + label847822 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + + GtkFrame + frame74 + 1 + + 0.1 + GTK_SHADOW_ETCHED_IN + + True + True + - GtkLabel - CList:title - label847803 - - GTK_JUSTIFY_CENTER - False - 0.5 - 0.5 - 0 - 0 + GtkScrolledWindow + scrolledwindow9 + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_UPDATE_CONTINUOUS + GTK_UPDATE_CONTINUOUS + + + GtkViewport + viewport1 + 2 + GTK_SHADOW_IN + + + GtkTable + variable_table + 2 + 2 + False + 2 + 2 + + + GtkLabel + label847819 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 1 + 2 + 0 + 1 + 0 + 0 + True + False + False + False + True + False + + + + + GtkLabel + label847818 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + 1 + 0 + 1 + 0 + 0 + True + False + False + False + True + False + + + + + + + + + + GnomeDialog + Scheduled Transaction Editor, Take 2 + Edit Scheduled Transaction + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + False + False + False + False + False + + + GtkVBox + GnomeDialog:vbox + dialog-vbox21 + False + 8 + + 4 + True + True + + + + GtkHButtonBox + GnomeDialog:action_area + dialog-action_area21 + GTK_BUTTONBOX_DEFAULT_STYLE + 8 + 85 + 27 + 7 + 0 + + 0 + False + True + GTK_PACK_END + - GtkFrame - frame69 - - 0.1 - GTK_SHADOW_ETCHED_IN + GtkButton + ok_button + True + True + GNOME_STOCK_BUTTON_OK + + + + GtkButton + cancel_button + True + True + GNOME_STOCK_BUTTON_CANCEL + + + + + GtkVBox + vbox136 + False + 5 + + 0 + True + True + + + + GtkHBox + hbox153 + False + 0 0 True @@ -3206,61 +3393,690 @@ December - GtkScrolledWindow - scrolledwindow5 - 3 - GTK_POLICY_ALWAYS - GTK_POLICY_ALWAYS - GTK_UPDATE_CONTINUOUS - GTK_UPDATE_CONTINUOUS + GtkLabel + label847827 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 5 + 0 + + 0 + False + False + + + + + GtkEntry + sxe_name + True + True + True + 0 + + + 0 + True + True + + + + + + GtkNotebook + notebook1 + True + True + True + GTK_POS_TOP + False + 2 + 2 + False + + 0 + True + True + + + + GtkVBox + vbox137 + 2 + False + 0 - GtkCList - reminders_clist + GtkCheckButton + autocreate_opt + Conditional on splits not having variables True - 3 - 147,198,80 - GTK_SELECTION_SINGLE - True - GTK_SHADOW_IN + + False + True + + 0 + False + False + + + + + GtkAlignment + alignment3 + 1 + 0.5 + 0.66 + 1 + + 0 + False + False + - GtkLabel - CList:title - label847795 - - GTK_JUSTIFY_CENTER - False - 0.5 - 0.5 - 0 - 0 + GtkCheckButton + notify_opt + False + True + + False + True + + + + + GtkCheckButton + advance_opt + True + False + True + + 0 + False + False + + + + GtkHBox + hbox154 + False + 0 + + + GtkLabel + label847828 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkSpinButton + advance_days + False + True + 1 + 0 + True + GTK_UPDATE_ALWAYS + True + False + 0 + 0 + 100 + 1 + 10 + 10 + + 0 + False + False + + + + + GtkLabel + label847829 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + + + GtkCheckButton + remind_opt + True + False + True + + 0 + False + False + + + + GtkHBox + hbox155 + False + 0 + + + GtkLabel + label847830 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkSpinButton + remind_days + False + True + 1 + 0 + True + GTK_UPDATE_ALWAYS + True + False + 0 + 0 + 100 + 1 + 10 + 10 + + 0 + False + False + + + + + GtkLabel + label847831 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + + + + GtkLabel + Notebook:tab + label847823 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkVBox + vbox138 + 2 + False + 0 + + + GtkRadioButton + rb_noend + True + + True + True + sx_end_opt + + 2 + False + False + + + + + GtkRadioButton + rb_enddate + True + False + True + sx_end_opt + + 2 + False + False + + + + GtkHBox + hbox156 + False + 0 + + + GtkLabel + label847832 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GnomeDateEdit + sxe_end_date + False + True + False + 7 + 19 + + 0 + False + False + + + + + + + GtkRadioButton + rb_num_occur + True + False + True + sx_end_opt + + 2 + False + False + + + + GtkVBox + vbox139 + False + 0 + + + GtkLabel + label847833 + + GTK_JUSTIFY_LEFT + False + 7.45058e-09 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GnomeNumberEntry + end_nentry + 10 + + 0 + False + False + + + + GtkEntry + GnomeEntry:entry + entry2 + True + True + True + 0 + + + + + + + + + GtkLabel + Notebook:tab + label847824 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkFrame + recur_frame + + 0.1 + GTK_SHADOW_ETCHED_IN + + + Placeholder + + + + + GtkLabel + Notebook:tab + label847825 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkVBox + vbox140 + False + 0 + + + GtkHBox + hbox157 + False + 0 + + 0 + False + False + + + + GtkButton + rec_button + True + True + + GTK_RELIEF_NORMAL + + 0 + False + False + GtkLabel - CList:title - label847796 - + label847834 + GTK_JUSTIFY_CENTER False 0.5 0.5 0 0 + + 0 + True + True + + + + + Placeholder + + + + + GtkLabel + Notebook:tab + label847826 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + + + GtkStatusbar + ledger_status + + 0 + False + False + + + + + + + GnomeDialog + Since-Last Reminders + False + Reminders + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER + False + 300 + True + True + True + False + False + + + GtkVBox + GnomeDialog:vbox + dialog-vbox22 + False + 8 + + 4 + True + True + + + + GtkHButtonBox + GnomeDialog:action_area + dialog-action_area22 + GTK_BUTTONBOX_DEFAULT_STYLE + 8 + 85 + 27 + 7 + 0 + + 0 + False + True + GTK_PACK_END + + + + GtkButton + ok_button + True + True + GNOME_STOCK_BUTTON_OK + + + + + GtkHBox + hbox158 + False + 0 + + 0 + True + True + + + + GtkVBox + vbox141 + False + 0 + + 0 + True + True + + + + GtkLabel + label847838 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 11 + + 0 + False + False + + + + + GtkFrame + frame75 + + 0.1 + GTK_SHADOW_ETCHED_IN + + 0 + True + True + + + + GtkScrolledWindow + scrolledwindow10 + 3 + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_UPDATE_CONTINUOUS + GTK_UPDATE_CONTINUOUS - GtkLabel - CList:title - label847797 - - GTK_JUSTIFY_CENTER - False - 0.5 - 0.5 - 0 - 0 + GtkCList + reminder_clist + True + 3 + 147,198,80 + GTK_SELECTION_EXTENDED + True + GTK_SHADOW_IN + + + GtkLabel + CList:title + label847835 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkLabel + CList:title + label847836 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkLabel + CList:title + label847837 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + diff --git a/src/gnome/glade/sched-xact_strings.c b/src/gnome/glade/sched-xact_strings.c index 8a4d885158..bb4080a261 100644 --- a/src/gnome/glade/sched-xact_strings.c +++ b/src/gnome/glade/sched-xact_strings.c @@ -360,7 +360,35 @@ gchar *s = N_("Date"); gchar *s = N_("To-create..."); gchar *s = N_("Name"); gchar *s = N_("Date"); +gchar *s = N_("Ready?"); +gchar *s = N_("Variables"); +gchar *s = N_("Value"); +gchar *s = N_("Variable"); +gchar *s = N_("Edit Scheduled Transaction"); +gchar *s = N_("Name:"); +gchar *s = N_("Conditional on splits not having variables"); +gchar *s = N_("Create Automatically"); +gchar *s = N_("Notify me when created"); +gchar *s = N_("Create "); +gchar *s = N_(" days in advance"); +gchar *s = N_("Remind me "); +gchar *s = N_(" days in advance"); +gchar *s = N_("Creation Options"); +gchar *s = N_("No End"); +gchar *s = N_("End Date: "); +gchar *s = N_("Number of Occurances:"); +gchar *s = N_("End"); +gchar *s = N_("Recurrence Frequency"); +gchar *s = N_("Recurrance Frequency"); +gchar *s = N_("Record"); +gchar *s = N_("REPLACEME with the Register control box"); +gchar *s = N_("Template Transactions"); gchar *s = N_("Reminders"); +gchar *s = N_("The listed Scheduled Transactions are to-be created soon. \n" + "Select any which you would\n" + "like to create now, and click\n" + "\"OK\" to create them."); +gchar *s = N_("Reminders..."); gchar *s = N_("Name"); gchar *s = N_("Next Date"); gchar *s = N_("Days");