GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
dialog-sx-editor2.c
1 /********************************************************************\
2  * dialog-sx-editor2.c : dialog for scheduled transaction editing *
3  * Copyright (C) 2001,2002,2006 Joshua Sled <[email protected]>*
4  * Copyright (C) 2011 Robert Fewell *
5  * *
6  * This program is free software; you can redistribute it and/or *
7  * modify it under the terms of version 2 and/or version 3 of the *
8  * GNU General Public License as published by the Free Software *
9  * Foundation. *
10  * *
11  * As a special exception, permission is granted to link the binary *
12  * module resultant from this code with the OpenSSL project's *
13  * "OpenSSL" library (or modified versions of it that use the same *
14  * license as the "OpenSSL" library), and distribute the linked *
15  * executable. You must obey the GNU General Public License in all *
16  * respects for all of the code used other than "OpenSSL". If you *
17  * modify this file, you may extend this exception to your version *
18  * of the file, but you are not obligated to do so. If you do not *
19  * wish to do so, delete this exception statement from your version *
20  * of this file. *
21  * *
22  * This program is distributed in the hope that it will be useful, *
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
25  * GNU General Public License for more details. *
26  * *
27  * You should have received a copy of the GNU General Public License*
28  * along with this program; if not, contact: *
29  * *
30  * Free Software Foundation Voice: +1-617-542-5942 *
31  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
32  * Boston, MA 02110-1301, USA [email protected] *
33 \********************************************************************/
34 
35 #include "config.h"
36 
37 #include <gtk/gtk.h>
38 #include <glib/gi18n.h>
39 #include <locale.h>
40 
41 #include <gnc-gdate-utils.h>
42 #include "qof.h"
43 #include "Account.h"
44 #include "SchedXaction.h"
45 #include "SX-book.h"
46 #include "dialog-preferences.h"
47 #include "dialog-sx-editor2.h"
48 #include "dialog-utils.h"
49 #include "gnc-component-manager.h"
50 #include "gnc-date.h"
51 #include "gnc-date-edit.h"
52 #include "gnc-dense-cal.h"
53 #include "gnc-dense-cal-store.h"
54 #include "gnc-embedded-window.h"
55 #include "gnc-engine.h"
56 #include "gnc-frequency.h"
57 #include "gnc-gui-query.h"
58 #include "gnc-hooks.h"
59 #include "gnc-ledger-display.h"
60 #include "gnc-plugin-page.h"
61 #include "gnc-plugin-page-register2.h"
62 #include "gnc-prefs.h"
63 #include "gnc-ui.h"
64 #include "gnc-ui-util.h"
65 #include "gnc-tree-model-split-reg.h"
66 #include "gnc-tree-control-split-reg.h"
67 
68 #include "gnc-sx-instance-model.h"
69 #include "dialog-sx-since-last-run.h"
70 
71 #undef G_LOG_DOMAIN
72 #define G_LOG_DOMAIN "gnc.gui.sx.editor"
73 
74 static QofLogModule log_module = GNC_MOD_GUI_SX;
75 
76 static gint _sx_engine_event_handler_id = -1;
77 
78 #define END_NEVER_OPTION 0
79 #define END_DATE_OPTION 1
80 #define NUM_OCCUR_OPTION 2
81 
82 #define NUM_LEDGER_LINES_DEFAULT 6
83 
84 #define EX_CAL_NUM_MONTHS 6
85 #define EX_CAL_MO_PER_COL 3
86 
87 #define GNC_D_WIDTH 25
88 #define GNC_D_BUF_WIDTH 26
89 
92 typedef enum _EndTypeEnum
93 {
94  END_NEVER,
95  END_DATE,
96  END_OCCUR,
97 } EndType;
98 
99 typedef enum { NO_END, DATE_END, COUNT_END } END_TYPE;
100 
102 {
103  GtkWidget *dialog;
104  GtkBuilder *builder;
105  GtkNotebook *notebook;
106  SchedXaction *sx;
107  /* If this is a new scheduled transaction or not. */
108  int newsxP;
109 
110  /* The various widgets in the dialog */
111  GNCLedgerDisplay2 *ledger;
112 
113  GncFrequency *gncfreq;
114  GncDenseCalStore *dense_cal_model;
115  GncDenseCal *example_cal;
116 
117  GtkEditable *nameEntry;
118 
119  GtkLabel *lastOccurLabel;
120 
121  GtkToggleButton *enabledOpt;
122  GtkToggleButton *autocreateOpt;
123  GtkToggleButton *notifyOpt;
124  GtkToggleButton *advanceOpt;
125  GtkSpinButton *advanceSpin;
126  GtkToggleButton *remindOpt;
127  GtkSpinButton *remindSpin;
128 
129  GtkToggleButton *optEndDate;
130  GtkToggleButton *optEndNone;
131  GtkToggleButton *optEndCount;
132  GtkEntry *endCountSpin;
133  GtkEntry *endRemainSpin;
134  GNCDateEdit *endDateEntry;
135 
136  char *sxGUIDstr;
137 
138  GncEmbeddedWindow *embed_window;
139  GncPluginPage *plugin_page;
140 };
141 
144 static void schedXact_editor_create_freq_sel (GncSxEditorDialog2 *sxed);
145 static void schedXact_editor_create_ledger (GncSxEditorDialog2 *sxed);
146 static void schedXact_editor_populate (GncSxEditorDialog2 *);
147 static void endgroup_rb_toggled_cb (GtkButton *b, gpointer d);
148 static void set_endgroup_toggle_states (GncSxEditorDialog2 *sxed, EndType t);
149 static void advance_toggled_cb (GtkButton *b, GncSxEditorDialog2 *sxed);
150 static void remind_toggled_cb (GtkButton *b, GncSxEditorDialog2 *sxed);
151 static gboolean gnc_sxed_check_consistent (GncSxEditorDialog2 *sxed);
152 static gboolean gnc_sxed_check_changed (GncSxEditorDialog2 *sxed);
153 static void gnc_sxed_save_sx (GncSxEditorDialog2 *sxed);
154 static void gnc_sxed_freq_changed (GncFrequency *gf, gpointer ud);
155 static void sxed_excal_update_adapt_cb (GtkObject *o, gpointer ud);
156 static void gnc_sxed_update_cal (GncSxEditorDialog2 *sxed);
157 static void on_sx_check_toggled_cb (GtkWidget *togglebutton, gpointer user_data);
158 //void on_sx_check_toggled_cb (GtkWidget *togglebutton, gpointer user_data);
159 static void gnc_sxed_reg_check_close (GncSxEditorDialog2 *sxed);
160 static gboolean sxed_delete_event (GtkWidget *widget, GdkEvent *event, gpointer ud);
161 static gboolean sxed_confirmed_cancel (GncSxEditorDialog2 *sxed);
162 static gboolean editor_component_sx_equality (gpointer find_data, gpointer user_data);
163 
164 static GtkActionEntry gnc_sxed_menu_entries [] =
165 {
166  { "EditAction", NULL, N_("_Edit"), NULL, NULL, NULL },
167  { "TransactionAction", NULL, N_("_Transaction"), NULL, NULL, NULL },
168  { "ViewAction", NULL, N_("_View"), NULL, NULL, NULL },
169  { "ActionsAction", NULL, N_("_Actions"), NULL, NULL, NULL },
170 };
171 static guint gnc_sxed_menu_n_entries = G_N_ELEMENTS (gnc_sxed_menu_entries);
172 
175 static void
176 sxed_close_handler(gpointer user_data)
177 {
178  GncSxEditorDialog2 *sxed = user_data;
179 
180  gnc_sxed_reg_check_close(sxed);
181  gnc_save_window_size( GNC_PREFS_GROUP_SXED, GTK_WINDOW(sxed->dialog) );
182  gtk_widget_destroy(sxed->dialog);
183  /* The data will be cleaned up in the destroy handler. */
184 }
185 
186 
191 static gboolean
192 sxed_confirmed_cancel (GncSxEditorDialog2 *sxed)
193 {
194  GncTreeViewSplitReg *view;
195 
196  view = gnc_ledger_display2_get_split_view_register (sxed->ledger);
197  /* check for changes */
198  if (gnc_sxed_check_changed (sxed))
199  {
200  const char *sx_changed_msg =
201  _( "This Scheduled Transaction has changed; are you "
202  "sure you want to cancel?" );
203  if (!gnc_verify_dialog (sxed->dialog, FALSE, "%s", sx_changed_msg))
204  {
205  return FALSE;
206  }
207  }
208  /* cancel ledger changes */
209  gnc_tree_view_split_reg_cancel_edit (view, TRUE);
210  return TRUE;
211 }
212 
213 
214 /**********************************
215  * Dialog Action Button functions *
216  *********************************/
217 static void
218 editor_cancel_button_clicked_cb (GtkButton *b, GncSxEditorDialog2 *sxed)
219 {
220  /* close */
221  if (!sxed_confirmed_cancel (sxed))
222  return;
223 
224  gnc_close_gui_component_by_data (DIALOG_SCHEDXACTION2_EDITOR_CM_CLASS,
225  sxed);
226 }
227 
228 
229 static void
230 editor_help_button_clicked_cb (GtkButton *b, GncSxEditorDialog2 *sxed)
231 {
232  gnc_gnome_help (HF_HELP, HL_SXEDITOR);
233 }
234 
235 
236 static void
237 editor_ok_button_clicked_cb (GtkButton *b, GncSxEditorDialog2 *sxed)
238 {
239  QofBook *book;
240  SchedXactions *sxes;
241 
242  if (!gnc_sxed_check_consistent (sxed))
243  return;
244 
245  gnc_sxed_save_sx (sxed);
246 
247  /* add to list */
248  // @@fixme -- forget 'new'-flag: check for existance of the SX [?]
249  if ( sxed->newsxP )
250  {
251  book = gnc_get_current_book ();
252  sxes = gnc_book_get_schedxactions (book);
253  gnc_sxes_add_sx (sxes, sxed->sx);
254  sxed->newsxP = FALSE;
255  }
256 
257  /* cleanup */
258  gnc_close_gui_component_by_data (DIALOG_SCHEDXACTION2_EDITOR_CM_CLASS,
259  sxed);
260 }
261 
262 
263 /*************************************************************************
264  * Checks to see if the SX has been modified from it's previously-saved
265  * state.
266  * @return TRUE if this is a 'new' SX, or if the SX has changed from it's
267  * previous configuration.
268  ************************************************************************/
269 static gboolean
270 gnc_sxed_check_changed (GncSxEditorDialog2 *sxed)
271 {
272  if (sxed->newsxP)
273  return TRUE;
274 
275  /* name */
276  {
277  char *name;
278 
279  name = gtk_editable_get_chars (GTK_EDITABLE (sxed->nameEntry), 0, -1);
280  if (strlen (name) == 0)
281  {
282  return TRUE;
283 
284  }
285  if ((xaccSchedXactionGetName (sxed->sx) == NULL)
286  || (strcmp( xaccSchedXactionGetName (sxed->sx),
287  name ) != 0))
288  {
289  return TRUE;
290  }
291  }
292 
293  /* end options */
294  {
295  /* dialog says... no end */
296  if (gtk_toggle_button_get_active (sxed->optEndNone))
297  {
298  if (xaccSchedXactionHasEndDate (sxed->sx)
299  || xaccSchedXactionHasOccurDef (sxed->sx))
300  {
301  return TRUE;
302  }
303  }
304 
305  /* dialog says... end date */
306  if (gtk_toggle_button_get_active (sxed->optEndDate))
307  {
308  GDate sxEndDate, dlgEndDate;
309 
310  if (!xaccSchedXactionHasEndDate (sxed->sx))
311  {
312  return TRUE;
313  }
314  sxEndDate = *xaccSchedXactionGetEndDate (sxed->sx);
315  gnc_gdate_set_time64 (&dlgEndDate,
316  gnc_date_edit_get_date( sxed->
317  endDateEntry));
318 
319  if (g_date_compare (&sxEndDate, &dlgEndDate) != 0)
320  {
321  return TRUE;
322  }
323  }
324 
325  /* dialog says... num occur */
326  if (gtk_toggle_button_get_active (sxed->optEndCount))
327  {
328  gint sxNumOccur, sxNumRem, dlgNumOccur, dlgNumRem;
329 
330  if (!xaccSchedXactionGetNumOccur (sxed->sx))
331  {
332  return TRUE;
333  }
334 
335  dlgNumOccur =
336  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
337 
338  dlgNumRem =
339  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
340 
341  sxNumOccur = xaccSchedXactionGetNumOccur (sxed->sx);
342  sxNumRem = xaccSchedXactionGetRemOccur (sxed->sx);
343 
344  if ((dlgNumOccur != sxNumOccur)
345  || (dlgNumRem != sxNumRem))
346  {
347  return TRUE;
348  }
349  }
350  }
351 
352  /* SX options [autocreate, notify, reminder, advance] */
353  {
354  gboolean dlgEnabled,
355  dlgAutoCreate,
356  dlgNotify,
357  sxEnabled,
358  sxAutoCreate,
359  sxNotify;
360  gint dlgAdvance, sxAdvance;
361  gint dlgRemind, sxRemind;
362 
363  dlgEnabled =
364  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->
365  enabledOpt));
366  dlgAutoCreate =
367  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->
368  autocreateOpt));
369  dlgNotify =
370  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->
371  notifyOpt));
372 
373  sxEnabled = xaccSchedXactionGetEnabled (sxed->sx);
374  if (!((dlgEnabled == sxEnabled)))
375  {
376  return TRUE;
377  }
378 
379  xaccSchedXactionGetAutoCreate (sxed->sx, &sxAutoCreate, &sxNotify);
380  if (!((dlgAutoCreate == sxAutoCreate)
381  && (dlgNotify == sxNotify)))
382  {
383  return TRUE;
384  }
385 
386  dlgAdvance = 0;
387  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->advanceOpt)))
388  {
389  dlgAdvance =
390  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->
391  advanceSpin));
392  }
393  sxAdvance = xaccSchedXactionGetAdvanceCreation (sxed->sx);
394  if (dlgAdvance != sxAdvance)
395  {
396  return TRUE;
397  }
398 
399  dlgRemind = 0;
400  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->remindOpt)))
401  {
402  dlgRemind =
403  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->remindSpin));
404  }
405  sxRemind = xaccSchedXactionGetAdvanceReminder (sxed->sx);
406  if (dlgRemind != sxRemind)
407  {
408  return TRUE;
409  }
410  }
411 
412  {
413  GList *dialog_schedule = NULL;
414  GDate dialog_start_date, sx_start_date;
415  gchar *dialog_schedule_str, *sx_schedule_str;
416  gboolean schedules_are_the_same, start_dates_are_the_same;
417 
418  g_date_clear (&dialog_start_date, 1);
419  gnc_frequency_save_to_recurrence (sxed->gncfreq, &dialog_schedule, &dialog_start_date);
420  dialog_schedule_str = recurrenceListToString (dialog_schedule);
421  recurrenceListFree (&dialog_schedule);
422 
423  sx_start_date = *xaccSchedXactionGetStartDate (sxed->sx);
424  sx_schedule_str = recurrenceListToString (gnc_sx_get_schedule (sxed->sx));
425 
426  g_debug ("dialog schedule [%s], sx schedule [%s]",
427  dialog_schedule_str, sx_schedule_str);
428 
429  schedules_are_the_same = (strcmp (dialog_schedule_str, sx_schedule_str) == 0);
430  g_free (dialog_schedule_str);
431  g_free (sx_schedule_str);
432 
433  start_dates_are_the_same = (g_date_compare (&dialog_start_date, &sx_start_date) == 0);
434 
435  if (!schedules_are_the_same || !start_dates_are_the_same)
436  return TRUE;
437  }
438 
439  /* template transactions */
440  {
441  GncTreeViewSplitReg *view = gnc_ledger_display2_get_split_view_register (sxed->ledger);
442  /* Make sure we have finished the edit */
443  gnc_tree_view_split_reg_finish_edit (view);
444  if (gnc_tree_view_split_reg_get_dirty_trans (view) != NULL)
445  return TRUE;
446  }
447  return FALSE;
448 }
449 
450 
451 /*****************************************************************************
452  * Holds the credit- and debit-sum for a given Transaction, as used in
453  * gnc_sxed_check_consistent.
454  ****************************************************************************/
455 typedef struct _txnCreditDebitSums
456 {
457  gnc_numeric creditSum;
458  gnc_numeric debitSum;
460 
461 
462 static
463 void
464 set_sums_to_zero (gpointer key,
465  gpointer val,
466  gpointer ud)
467 {
469  tcds->creditSum = gnc_numeric_zero();
470  tcds->debitSum = gnc_numeric_zero();
471 }
472 
473 
474 static void
475 check_credit_debit_balance (gpointer key,
476  gpointer val,
477  gpointer ud)
478 {
480  gboolean *unbalanced = (gboolean*)ud;
481  *unbalanced |= !(gnc_numeric_zero_p (
482  gnc_numeric_sub_fixed (tcds->debitSum,
483  tcds->creditSum)));
484 
485  if (qof_log_check (G_LOG_DOMAIN, QOF_LOG_DEBUG))
486  {
487  if (gnc_numeric_zero_p (gnc_numeric_sub_fixed (tcds->debitSum,
488  tcds->creditSum)))
489  {
490  g_debug ("%p | true [%s - %s = %s]",
491  key,
492  gnc_numeric_to_string (tcds->debitSum),
493  gnc_numeric_to_string (tcds->creditSum),
494  gnc_numeric_to_string (gnc_numeric_sub_fixed (tcds->debitSum,
495  tcds->creditSum)));
496  }
497  else
498  {
499  g_debug ("%p | false [%s - %s = %s]",
500  key,
501  gnc_numeric_to_string (tcds->debitSum),
502  gnc_numeric_to_string (tcds->creditSum),
503  gnc_numeric_to_string (gnc_numeric_sub_fixed (tcds->debitSum,
504  tcds->creditSum)));
505  }
506  }
507 }
508 
509 
510 /*******************************************************************************
511  * Checks to make sure that the SX is in a reasonable state to save.
512  * @return true if checks out okay, false otherwise.
513  ******************************************************************************/
514 static gboolean
515 gnc_sxed_check_consistent (GncSxEditorDialog2 *sxed)
516 {
517  gboolean multi_commodity = FALSE;
518  gnc_commodity *base_cmdty = NULL;
519  gint ttVarCount, splitCount;
520  GList *schedule = NULL;
521 
522  /* Do checks on validity and such, interrupting the user if
523  * things aren't right.
524  *
525  * Features...
526  * X support formulas [?!]
527  * X balancing the SX if contain numeric-only formula data.
528  * X agreement with create-automagically/notification controls
529  * X the 'will ever be valid' check should take num-occur vals into
530  * account.
531  * X SX name is unique
532  * X SX has a name
533  * X "weekly" FS has some days set.
534  * X "once" with reasonable start/end dates.
535  * X This doesn't work at the time the 'weekly' one was fixed with
536  * user-confirmation, below; the once SX is always valid.
537  * [X more generically, creating a "not scheduled" SX is probably not
538  * right... ]
539  */
540 
541  ttVarCount = 0;
542  splitCount = 0;
543  {
544  static const int NUM_ITERS_WITH_VARS = 5;
545  static const int NUM_ITERS_NO_VARS = 1;
546  int numIters, i;
547  GHashTable *vars, *txns;
548  GList *splitList = NULL;
549  char *credit_formula = NULL, *debit_formula = NULL;
550  Split *s;
551  Transaction *t;
552  gnc_numeric tmp;
553  gboolean unbalanceable;
554  gpointer unusedKey, unusedValue;
555 
556  unbalanceable = FALSE; /* innocent until proven guilty */
557  vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)gnc_sx_variable_free);
558  txns = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
559  numIters = NUM_ITERS_NO_VARS;
569  /* numeric-formulas-get-balanced determination */
570  gnc_sx_get_variables (sxed->sx, vars);
571 
572  ttVarCount = g_hash_table_size (vars);
573  if (ttVarCount != 0)
574  {
575  /* balance with random variable bindings some number
576  * of times in an attempt to ferret out
577  * un-balanceable transactions.
578  *
579  * NOTE: The Real Way to do this is with some
580  * symbolic math to eliminate the variables. This is
581  * hard, and we don't do it. This solution will
582  * suffice for now, and perhaps for the lifetime of
583  * the software. --jsled */
584  numIters = NUM_ITERS_WITH_VARS;
585  }
586 
587  for (i = 0; i < numIters && !unbalanceable; i++)
588  {
589  gnc_sx_randomize_variables (vars);
590  g_hash_table_foreach (txns, set_sums_to_zero, NULL);
591  tmp = gnc_numeric_zero();
592 
593  splitList = xaccSchedXactionGetSplits (sxed->sx);
594  splitCount += g_list_length (splitList);
595 
596  for (; splitList; splitList = splitList->next)
597  {
598  GncGUID *acct_guid = NULL;
599  Account *acct;
600  gnc_commodity *split_cmdty;
601  txnCreditDebitSums *tcds;
602 
603  s = (Split*)splitList->data;
604  t = xaccSplitGetParent (s);
605 
606  if (!(tcds =
607  (txnCreditDebitSums*)g_hash_table_lookup (txns,
608  (gpointer)t)))
609  {
610  tcds = g_new0 (txnCreditDebitSums, 1 );
611  tcds->creditSum = gnc_numeric_zero();
612  tcds->debitSum = gnc_numeric_zero();
613  g_hash_table_insert (txns, (gpointer)t, (gpointer)tcds);
614  }
615 
616  qof_instance_get (QOF_INSTANCE (s),
617  "sx-account", &acct_guid,
618  "sx-credit-formula", &credit_formula,
619  "sx-debit-formula", &debit_formula,
620  NULL);
621  acct = xaccAccountLookup( acct_guid, gnc_get_current_book ());
622  split_cmdty = xaccAccountGetCommodity(acct);
623  if (base_cmdty == NULL)
624  {
625  base_cmdty = split_cmdty;
626  }
627  multi_commodity |= !gnc_commodity_equal(split_cmdty, base_cmdty);
628 
629  if ( g_strcmp0 (credit_formula, "") != 0 &&
630  gnc_sx_parse_vars_from_formula(credit_formula, vars,
631  &tmp ) < 0 )
632  {
633  GString *errStr;
634 
635  errStr = g_string_sized_new( 32 );
636  g_string_printf( errStr,
637  _( "Couldn't parse credit formula for "
638  "split \"%s\"." ),
639  xaccSplitGetMemo( s ) );
640  gnc_error_dialog( GTK_WIDGET(sxed->dialog), "%s",
641  errStr->str );
642  g_string_free( errStr, TRUE );
643 
644  return FALSE;
645  }
646  tcds->creditSum =
647  gnc_numeric_add( tcds->creditSum, tmp, 100,
649  tmp = gnc_numeric_zero();
650  if ( g_strcmp0 (debit_formula, "") != 0 &&
651  gnc_sx_parse_vars_from_formula( debit_formula, vars,
652  &tmp ) < 0 )
653  {
654  GString *errStr;
655 
656  errStr = g_string_sized_new( 32 );
657  g_string_printf( errStr,
658  _( "Couldn't parse debit formula for "
659  "split \"%s\"." ),
660  xaccSplitGetMemo( s ) );
661  gnc_error_dialog( GTK_WIDGET(sxed->dialog), "%s",
662  (gchar*)errStr->str );
663  g_string_free( errStr, TRUE );
664 
665  return FALSE;
666  }
667  tcds->debitSum = gnc_numeric_add( tcds->debitSum, tmp, 100,
669  tmp = gnc_numeric_zero();
670  }
671 
672  g_hash_table_foreach (txns,
673  check_credit_debit_balance,
674  &unbalanceable);
675  }
676 
677  /* Subtract out pre-defined vars */
678  if (g_hash_table_lookup_extended (vars, "i",
679  &unusedKey,
680  &unusedValue))
681  {
682  ttVarCount -= 1;
683  }
684 
685  g_hash_table_destroy (vars);
686  g_hash_table_destroy (txns);
687 
688  if (unbalanceable
689  && !gnc_verify_dialog (sxed->dialog, FALSE,
690  "%s",
691  _("The Scheduled Transaction Editor "
692  "cannot automatically balance "
693  "this transaction. "
694  "Should it still be "
695  "entered?")))
696  {
697  return FALSE;
698  }
699  }
700 
701  /* read out data back into SchedXaction object. */
702  /* FIXME: this is getting too deep; split out. */
703  {
704  gchar *name, *nameKey;
705  gboolean nameExists, nameHasChanged;
706  GList *sxList;
707 
708  name = gtk_editable_get_chars (GTK_EDITABLE (sxed->nameEntry), 0, -1);
709  if (strlen (name) == 0 )
710  {
711  const char *sx_has_no_name_msg =
712  _("Please name the Scheduled Transaction.");
713  gnc_error_dialog (sxed->dialog, "%s", sx_has_no_name_msg);
714  g_free (name);
715  return FALSE;
716 
717  }
718 
719  nameExists = FALSE;
720  nameKey = g_utf8_collate_key (name, -1);
721  nameHasChanged =
722  (xaccSchedXactionGetName (sxed->sx) == NULL)
723  || (strcmp (xaccSchedXactionGetName (sxed->sx), name) != 0);
724  for ( sxList =
725  gnc_book_get_schedxactions (gnc_get_current_book())->sx_list;
726  nameHasChanged && !nameExists && sxList;
727  sxList = sxList->next )
728  {
729  char *existingName, *existingNameKey;
730  existingName =
731  xaccSchedXactionGetName ((SchedXaction*)sxList->data);
732  existingNameKey = g_utf8_collate_key (existingName, -1);
733  nameExists |= (strcmp (nameKey, existingNameKey) == 0);
734  g_free (existingNameKey);
735  }
736  if (nameHasChanged && nameExists)
737  {
738  const char *sx_has_existing_name_msg =
739  _("A Scheduled Transaction with the "
740  "name \"%s\" already exists. "
741  "Are you sure you want to name "
742  "this one the same?");
743  if (!gnc_verify_dialog (sxed->dialog, FALSE,
744  sx_has_existing_name_msg,
745  name))
746  {
747  g_free (nameKey);
748  g_free (name);
749  return FALSE;
750  }
751  }
752  g_free (nameKey);
753  g_free (name);
754  }
755 
756  // @@FIXME: similar to below, check the commodities involved, and disallow autocreation
757  {
758  gboolean autocreateState;
759 
760  autocreateState =
761  gtk_toggle_button_get_active (
762  GTK_TOGGLE_BUTTON (sxed->autocreateOpt));
763 
764  if (((ttVarCount > 0) || multi_commodity) && autocreateState)
765  {
766  gnc_warning_dialog (sxed->dialog, "%s",
767  _("Scheduled Transactions with variables "
768  "cannot be automatically created."));
769  return FALSE;
770  }
771 
772  /* Fix for part of Bug#121740 -- auto-create transactions are
773  * only valid if there's actually a transaction to create. */
774  if (autocreateState && splitCount == 0)
775  {
776  gnc_warning_dialog (sxed->dialog, "%s",
777  _("Scheduled Transactions without a template "
778  "transaction cannot be automatically created."));
779  return FALSE;
780  }
781  }
782 
783  /* deal with time. */
784  {
785  GDate startDate, endDate, nextDate;
786 
787  if (!gtk_toggle_button_get_active (sxed->optEndDate)
788  && !gtk_toggle_button_get_active (sxed->optEndCount)
789  && !gtk_toggle_button_get_active (sxed->optEndNone) )
790  {
791  const char *sx_end_spec_msg =
792  _("Please provide a valid end selection.");
793  gnc_error_dialog (sxed->dialog, "%s", sx_end_spec_msg);
794  return FALSE;
795  }
796 
797  if (gtk_toggle_button_get_active (sxed->optEndCount))
798  {
799  gint occur, rem;
800 
801  occur =
802  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
803 
804  rem =
805  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
806 
807  if (occur == 0)
808  {
809  const char *sx_occur_count_zero_msg =
810  _("There must be some number of occurrences.");
811  gnc_error_dialog (sxed->dialog, "%s",
812  sx_occur_count_zero_msg);
813  return FALSE;
814  }
815 
816  if (rem > occur)
817  {
818  const char *sx_occur_counts_wrong_msg =
819  _("The number of remaining occurrences "
820  "(%d) is greater than the number of "
821  "total occurrences (%d).");
822  gnc_error_dialog (sxed->dialog,
823  sx_occur_counts_wrong_msg,
824  rem, occur);
825  return FALSE;
826  }
827 
828  }
829 
830  g_date_clear (&endDate, 1);
831  if ( gtk_toggle_button_get_active(sxed->optEndDate))
832  {
833  gnc_gdate_set_time64 (&endDate,
834  gnc_date_edit_get_date (sxed->
835  endDateEntry));
836  }
837 
838  g_date_clear (&nextDate, 1);
839  gnc_frequency_save_to_recurrence (sxed->gncfreq, &schedule, &startDate);
840  if (g_list_length (schedule) > 0)
841  {
842  g_date_subtract_days (&startDate, 1);
843  recurrenceListNextInstance (schedule, &startDate, &nextDate);
844  }
845  recurrenceListFree (&schedule);
846 
847  if (!g_date_valid (&nextDate)
848  || (g_date_valid (&endDate) && (g_date_compare (&nextDate, &endDate) > 0)))
849  {
850  const char *invalid_sx_check_msg =
851  _("You have attempted to create a Scheduled "
852  "Transaction which will never run. Do you "
853  "really want to do this?");
854  if (!gnc_verify_dialog (sxed->dialog, FALSE,
855  "%s", invalid_sx_check_msg))
856  return FALSE;
857  }
858  }
859  return TRUE;
860 }
861 
862 
863 /******************************************************************************
864  * Saves the contents of the SX. This assumes that gnc_sxed_check_consistent
865  * has returned true.
866  *****************************************************************************/
867 static void
868 gnc_sxed_save_sx (GncSxEditorDialog2 *sxed )
869 {
870  gnc_sx_begin_edit (sxed->sx);
871 
872  /* name */
873  {
874  char *name;
875 
876  name = gtk_editable_get_chars (sxed->nameEntry, 0, -1);
877  xaccSchedXactionSetName (sxed->sx, name);
878  g_free (name);
879  }
880 
881  /* date */
882  {
883  GDate gdate;
884 
885  if (gtk_toggle_button_get_active (sxed->optEndDate))
886  {
887  /* get the end date data */
888  gnc_gdate_set_time64 (&gdate,
889  gnc_date_edit_get_date (
890  sxed->endDateEntry));
891  xaccSchedXactionSetEndDate (sxed->sx, &gdate);
892  /* set the num occurrences data */
893  xaccSchedXactionSetNumOccur (sxed->sx, 0);
894  }
895  else if (gtk_toggle_button_get_active (sxed->optEndCount))
896  {
897  gint num;
898 
899  /* get the occurrences data */
900  num =
901  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
902  xaccSchedXactionSetNumOccur (sxed->sx, num);
903 
904  num =
905  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
906  xaccSchedXactionSetRemOccur (sxed->sx, num );
907 
908  g_date_clear (&gdate, 1);
909  xaccSchedXactionSetEndDate (sxed->sx, &gdate);
910  }
911  else if (gtk_toggle_button_get_active (sxed->optEndNone))
912  {
913  xaccSchedXactionSetNumOccur (sxed->sx, 0);
914  g_date_clear (&gdate, 1);
915  xaccSchedXactionSetEndDate (sxed->sx, &gdate);
916  }
917  else
918  {
919  g_critical ("no valid end specified\n");
920  }
921  }
922 
923  /* Enabled states */
924  {
925  gboolean enabledState;
926 
927  enabledState = gtk_toggle_button_get_active (sxed->enabledOpt);
928  xaccSchedXactionSetEnabled (sxed->sx, enabledState);
929  }
930 
931  /* Auto-create/notification states */
932  {
933  gboolean autocreateState, notifyState;
934 
935  autocreateState = gtk_toggle_button_get_active (sxed->autocreateOpt);
936  notifyState = gtk_toggle_button_get_active (sxed->notifyOpt);
937  /* "Notify" only makes sense if AutoCreate is actived;
938  * enforce that here. */
939  xaccSchedXactionSetAutoCreate (sxed->sx,
940  autocreateState,
941  (autocreateState & notifyState));
942  }
943 
944  /* days in advance */
945  {
946  int daysInAdvance;
947 
948  daysInAdvance = 0;
949  if (gtk_toggle_button_get_active (sxed->advanceOpt))
950  {
951  daysInAdvance =
952  gtk_spin_button_get_value_as_int (sxed->advanceSpin);
953  }
954  xaccSchedXactionSetAdvanceCreation (sxed->sx, daysInAdvance);
955 
956  daysInAdvance = 0;
957  if (gtk_toggle_button_get_active (sxed->remindOpt))
958  {
959  daysInAdvance =
960  gtk_spin_button_get_value_as_int (sxed->remindSpin);
961  }
962  xaccSchedXactionSetAdvanceReminder (sxed->sx, daysInAdvance);
963  }
964 
965  /* start date and freq spec */
966  {
967  GDate gdate;
968  GList *schedule = NULL;
969 
970  gnc_frequency_save_to_recurrence (sxed->gncfreq, &schedule, &gdate);
971  gnc_sx_set_schedule (sxed->sx, schedule);
972  {
973  gchar *recurrence_str = recurrenceListToCompactString (schedule);
974  g_debug("recurrences parsed [%s]", recurrence_str);
975  g_free (recurrence_str);
976  }
977 
978  /* now that we have it, set the start date */
979  xaccSchedXactionSetStartDate (sxed->sx, &gdate);
980  }
981 
982  gnc_sx_commit_edit (sxed->sx);
983 }
984 
985 
986 static void
987 enabled_toggled_cb (GtkObject *o, GncSxEditorDialog2 *sxed)
988 {
989  return;
990 }
991 
992 
993 static void
994 autocreate_toggled_cb (GtkObject *o, GncSxEditorDialog2 *sxed)
995 {
996  if ( !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(o)))
997  {
998  gtk_toggle_button_set_active (sxed->notifyOpt, FALSE);
999  }
1000  gtk_widget_set_sensitive (GTK_WIDGET (sxed->notifyOpt),
1001  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(o)));
1002 }
1003 
1004 
1005 static void
1006 advance_toggled_cb (GtkButton *o, GncSxEditorDialog2 *sxed)
1007 {
1008 
1009  gtk_widget_set_sensitive(GTK_WIDGET (sxed->advanceSpin),
1010  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->advanceOpt)));
1011  gtk_editable_set_editable(GTK_EDITABLE (sxed->advanceSpin), TRUE);
1012 }
1013 
1014 
1015 static void
1016 remind_toggled_cb (GtkButton *o, GncSxEditorDialog2 *sxed)
1017 {
1018 
1019  gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindSpin),
1020  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->remindOpt)));
1021  gtk_editable_set_editable (GTK_EDITABLE (sxed->remindSpin), TRUE);
1022 }
1023 
1024 
1025 /* Local destruction of dialog */
1026 static void
1027 scheduledxaction_editor_dialog_destroy (GtkObject *object, gpointer data)
1028 {
1029  GncSxEditorDialog2 *sxed = data;
1030 
1031  if (sxed == NULL)
1032  return;
1033 
1034  gnc_unregister_gui_component_by_data
1035  (DIALOG_SCHEDXACTION2_EDITOR_CM_CLASS, sxed);
1036 
1037  gnc_embedded_window_close_page (sxed->embed_window, sxed->plugin_page);
1038  gtk_widget_destroy (GTK_WIDGET (sxed->embed_window));
1039  sxed->embed_window = NULL;
1040  sxed->plugin_page = NULL;
1041  sxed->ledger = NULL;
1042 
1043  g_free (sxed->sxGUIDstr);
1044  sxed->sxGUIDstr = NULL;
1045 
1046  if ( sxed->newsxP )
1047  {
1048  /* FIXME: WTF???
1049  *
1050  * "WTF" explaination: in the "new" click from the caller, we
1051  * set this flag. When "ok" is pressed on the dialog, we set
1052  * this flag to false, and thus leave the SX live. If
1053  * "Cancel" is clicked, the flag will still be true, and this
1054  * SX will be cleaned, here. -- jsled
1055  */
1056  gnc_sx_begin_edit (sxed->sx);
1057  xaccSchedXactionDestroy (sxed->sx);
1058  }
1059  sxed->sx = NULL;
1060 
1061  g_free (sxed);
1062 }
1063 
1064 
1065 static
1066 gboolean
1067 sxed_delete_event (GtkWidget *widget, GdkEvent *event, gpointer ud)
1068 {
1070 
1071  /* We've already processed the SX, likely because of "ok" being
1072  * clicked. */
1073  if (sxed->sx == NULL)
1074  {
1075  return FALSE;
1076  }
1077 
1078  if (!sxed_confirmed_cancel (sxed))
1079  {
1080  return TRUE;
1081  }
1082  return FALSE;
1083 }
1084 
1085 
1086 /*************************************
1087  * Create the Schedule Editor Dialog *
1088  ************************************/
1090 gnc_ui_scheduled_xaction_editor_dialog_create2 (SchedXaction *sx,
1091  gboolean newSX)
1092 {
1093  GncSxEditorDialog2 *sxed;
1094  GtkBuilder *builder;
1095  GtkWidget *button;
1096  int i;
1097  GList *dlgExists = NULL;
1098 
1099  static struct widgetSignalCallback
1100  {
1101  char *name;
1102  char *signal;
1103  void (*fn)();
1104  gpointer objectData;
1105  } widgets[] =
1106  {
1107  { "ok_button", "clicked", editor_ok_button_clicked_cb, NULL },
1108  { "cancel_button", "clicked", editor_cancel_button_clicked_cb, NULL },
1109  { "help_button", "clicked", editor_help_button_clicked_cb, NULL },
1110  { "rb_noend", "toggled", endgroup_rb_toggled_cb, GINT_TO_POINTER(END_NEVER_OPTION) },
1111  { "rb_enddate", "toggled", endgroup_rb_toggled_cb, GINT_TO_POINTER(END_DATE_OPTION) },
1112  { "rb_num_occur", "toggled", endgroup_rb_toggled_cb, GINT_TO_POINTER(NUM_OCCUR_OPTION) },
1113  { "remain_spin" , "value-changed", sxed_excal_update_adapt_cb, NULL },
1114  { "enabled_opt", "toggled", enabled_toggled_cb, NULL },
1115  { "autocreate_opt", "toggled", autocreate_toggled_cb, NULL },
1116  { "advance_opt", "toggled", advance_toggled_cb, NULL },
1117  { "remind_opt", "toggled", remind_toggled_cb, NULL },
1118  { NULL, NULL, NULL, NULL }
1119  };
1120 
1121  dlgExists = gnc_find_gui_components (DIALOG_SCHEDXACTION2_EDITOR_CM_CLASS,
1122  editor_component_sx_equality,
1123  sx);
1124  if (dlgExists != NULL)
1125  {
1126  g_debug ("dialog already exists; using that one.");
1127  sxed = (GncSxEditorDialog2*)dlgExists->data;
1128  gtk_window_present (GTK_WINDOW (sxed->dialog));
1129  g_list_free (dlgExists);
1130  return sxed;
1131  }
1132 
1133  sxed = g_new0 (GncSxEditorDialog2, 1);
1134 
1135  sxed->sx = sx;
1136  sxed->newsxP = newSX;
1137 
1138  /* Load up Glade file */
1139  builder = gtk_builder_new();
1140  gnc_builder_add_from_file (builder, "dialog-sx.glade", "advance_days_adj");
1141  gnc_builder_add_from_file (builder, "dialog-sx.glade", "remind_days_adj");
1142  gnc_builder_add_from_file (builder, "dialog-sx.glade", "end_spin_adj");
1143  gnc_builder_add_from_file (builder, "dialog-sx.glade", "remain_spin_adj");
1144  gnc_builder_add_from_file (builder, "dialog-sx.glade", "Scheduled Transaction Editor");
1145 
1146  sxed->builder = builder;
1147 
1148  /* Connect the Widgets */
1149  sxed->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "Scheduled Transaction Editor"));
1150  sxed->notebook = GTK_NOTEBOOK (gtk_builder_get_object (builder, "editor_notebook"));
1151  sxed->nameEntry = GTK_EDITABLE (gtk_builder_get_object (builder, "sxe_name"));
1152  sxed->enabledOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "enabled_opt"));
1153  sxed->autocreateOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "autocreate_opt"));
1154  sxed->notifyOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "notify_opt"));
1155  sxed->advanceOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "advance_opt"));
1156  sxed->advanceSpin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "advance_days"));
1157  sxed->remindOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "remind_opt"));
1158  sxed->remindSpin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "remind_days"));
1159  sxed->lastOccurLabel = GTK_LABEL (gtk_builder_get_object (builder, "last_occur_label"));
1160  sxed->optEndNone = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_noend"));
1161  sxed->optEndDate = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_enddate"));
1162  sxed->optEndCount = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_num_occur"));
1163  sxed->endCountSpin = GTK_ENTRY (gtk_builder_get_object (builder, "end_spin"));
1164  sxed->endRemainSpin = GTK_ENTRY (gtk_builder_get_object (builder, "remain_spin"));
1165 
1166  /* Setup the end-date GNC widget */
1167  {
1168  GtkWidget *endDateBox = GTK_WIDGET (gtk_builder_get_object (builder, "end_date_hbox"));
1169  sxed->endDateEntry = GNC_DATE_EDIT (gnc_date_edit_new (gnc_time (NULL),
1170  FALSE, FALSE));
1171  gtk_widget_show (GTK_WIDGET (sxed->endDateEntry));
1172  g_signal_connect (sxed->endDateEntry, "date-changed",
1173  G_CALLBACK (sxed_excal_update_adapt_cb), sxed);
1174  gtk_box_pack_start (GTK_BOX (endDateBox), GTK_WIDGET (sxed->endDateEntry),
1175  TRUE, TRUE, 0 );
1176  }
1177 
1178  gnc_register_gui_component (DIALOG_SCHEDXACTION2_EDITOR_CM_CLASS,
1179  NULL, /* no refresh handler */
1180  sxed_close_handler,
1181  sxed);
1182 
1183  g_signal_connect (sxed->dialog, "delete_event",
1184  G_CALLBACK (sxed_delete_event), sxed);
1185  g_signal_connect (sxed->dialog, "destroy",
1186  G_CALLBACK (scheduledxaction_editor_dialog_destroy),
1187  sxed );
1188 
1189  for (i = 0; widgets[i].name != NULL; i++)
1190  {
1191  button = GTK_WIDGET (gtk_builder_get_object (builder, widgets[i].name));
1192  if (widgets[i].objectData != NULL)
1193  {
1194  g_object_set_data (G_OBJECT (button), "whichOneAmI",
1195  widgets[i].objectData);
1196  }
1197  g_signal_connect (button, widgets[i].signal,
1198  G_CALLBACK (widgets[i].fn ), sxed);
1199  }
1200 
1201  /* Set sensitivity settings */
1202  gtk_widget_set_sensitive (GTK_WIDGET (sxed->notifyOpt), FALSE );
1203  gtk_widget_set_sensitive (GTK_WIDGET (sxed->advanceSpin), FALSE );
1204  gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindSpin), FALSE );
1205  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endCountSpin), FALSE );
1206  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endRemainSpin), FALSE );
1207  gtk_editable_set_editable (GTK_EDITABLE (sxed->advanceSpin), TRUE );
1208  gtk_editable_set_editable (GTK_EDITABLE (sxed->remindSpin), TRUE );
1209 
1210  /* Allow resize */
1211  gtk_window_set_resizable (GTK_WINDOW (sxed->dialog), TRUE);
1212  gnc_restore_window_size (GNC_PREFS_GROUP_SXED, GTK_WINDOW (sxed->dialog));
1213 
1214  /* create the frequency-selection widget and example [dense-]calendar. */
1215  schedXact_editor_create_freq_sel (sxed);
1216 
1217  /* create the template-transaction ledger window */
1218  schedXact_editor_create_ledger (sxed);
1219 
1220  /* populate */
1221  schedXact_editor_populate (sxed);
1222 
1223  /* Do not call show_all here. Screws up the gtkuimanager code */
1224  gtk_widget_show (sxed->dialog);
1225  gtk_notebook_set_current_page (GTK_NOTEBOOK (sxed->notebook), 0);
1226 
1227  /* Refresh the cal and the ledger */
1228  gtk_widget_queue_resize (GTK_WIDGET (sxed->example_cal));
1229 
1230  gnc_ledger_display2_refresh (sxed->ledger);
1231 
1232  /* Move keyboard focus to the name entry */
1233  gtk_widget_grab_focus (GTK_WIDGET (sxed->nameEntry));
1234 
1235  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, sxed);
1236  g_object_unref (G_OBJECT (builder));
1237 
1238  return sxed;
1239 }
1240 
1241 
1242 static
1243 void
1244 schedXact_editor_create_freq_sel (GncSxEditorDialog2 *sxed)
1245 {
1246  GtkBox *b;
1247 
1248  b = GTK_BOX (gtk_builder_get_object (sxed->builder, "gncfreq_hbox"));
1249 
1250  sxed->gncfreq =
1251  GNC_FREQUENCY (gnc_frequency_new_from_recurrence (gnc_sx_get_schedule (sxed->sx),
1252  xaccSchedXactionGetStartDate (sxed->sx)));
1253  g_assert (sxed->gncfreq);
1254  g_signal_connect (sxed->gncfreq, "changed",
1255  G_CALLBACK (gnc_sxed_freq_changed),
1256  sxed );
1257  gtk_container_add (GTK_CONTAINER (b), GTK_WIDGET (sxed->gncfreq) );
1258 
1259  b = GTK_BOX(gtk_builder_get_object (sxed->builder, "example_cal_hbox" ));
1260  sxed->dense_cal_model = gnc_dense_cal_store_new (EX_CAL_NUM_MONTHS * 31);
1261  sxed->example_cal = GNC_DENSE_CAL (gnc_dense_cal_new_with_model (GNC_DENSE_CAL_MODEL (sxed->dense_cal_model)));
1262  g_assert (sxed->example_cal);
1263  gnc_dense_cal_set_num_months (sxed->example_cal, EX_CAL_NUM_MONTHS);
1264  gnc_dense_cal_set_months_per_col( sxed->example_cal, EX_CAL_MO_PER_COL);
1265  gtk_container_add (GTK_CONTAINER (b), GTK_WIDGET (sxed->example_cal));
1266  gtk_widget_show (GTK_WIDGET (sxed->example_cal));
1267 }
1268 
1269 
1270 static
1271 void
1272 schedXact_editor_create_ledger (GncSxEditorDialog2 *sxed)
1273 {
1274  GncTreeModelSplitReg *model;
1275  GtkWidget *main_vbox;
1276  GtkWidget *label;
1277 
1278  /* Create the ledger */
1279  sxed->sxGUIDstr = guid_to_string (xaccSchedXactionGetGUID (sxed->sx));
1280  sxed->ledger = gnc_ledger_display2_template_gl (sxed->sxGUIDstr);
1281  model = gnc_ledger_display2_get_split_model_register (sxed->ledger);
1282 
1283  /* First the embedded window */
1284  main_vbox = GTK_WIDGET (gtk_builder_get_object (sxed->builder, "register_vbox" ));
1285  sxed->embed_window =
1286  gnc_embedded_window_new ("SXWindowActions",
1287  gnc_sxed_menu_entries,
1288  gnc_sxed_menu_n_entries,
1289  "gnc-sxed-window-ui.xml",
1290  sxed->dialog,
1291  FALSE, /* no accelerators */
1292  sxed);
1293 
1294  gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (sxed->embed_window), TRUE, TRUE, 0);
1295 
1296  label = gtk_label_new (_("Note: If you have already accepted changes to the Template, Cancel will not revoke them."));
1297  gtk_box_pack_end (GTK_BOX (main_vbox), GTK_WIDGET (label), FALSE, TRUE, 0);
1298  gtk_widget_show (label);
1299 
1300  /* Now create the register plugin page. */
1301  sxed->plugin_page = gnc_plugin_page_register2_new_ledger (sxed->ledger);
1302  gnc_plugin_page_set_ui_description (sxed->plugin_page,
1303  "gnc-sxed-window-ui-full.xml");
1304  gnc_plugin_page_register2_set_options (sxed->plugin_page,
1305  NUM_LEDGER_LINES_DEFAULT, FALSE );
1306 
1307  gnc_embedded_window_open_page (sxed->embed_window, sxed->plugin_page);
1308 
1309  /* configure... */
1310  /* use double-line, so scheduled transaction Notes can be edited */
1311  gnc_tree_model_split_reg_config (model, model->type, model->style, TRUE);
1312 }
1313 
1314 
1315 
1316 static
1317 void
1318 schedXact_editor_populate (GncSxEditorDialog2 *sxed)
1319 {
1320  char *name;
1321  time64 tmpDate;
1322  GncTreeModelSplitReg *model;
1323  struct tm *tmpTm;
1324  const GDate *gd;
1325  gint daysInAdvance;
1326  gboolean enabledState, autoCreateState, notifyState;
1327 
1328  name = xaccSchedXactionGetName (sxed->sx);
1329  if (name != NULL)
1330  {
1331  gtk_entry_set_text (GTK_ENTRY (sxed->nameEntry), name);
1332  }
1333  {
1334  gd = xaccSchedXactionGetLastOccurDate (sxed->sx);
1335  if (g_date_valid (gd))
1336  {
1337  gchar dateBuf[ MAX_DATE_LENGTH+1 ];
1338  qof_print_gdate (dateBuf, MAX_DATE_LENGTH, gd);
1339  gtk_label_set_text (sxed->lastOccurLabel, dateBuf);
1340  }
1341  else
1342  {
1343  gtk_label_set_text (sxed->lastOccurLabel, _("(never)"));
1344  }
1345  gd = NULL;
1346  }
1347 
1348  gd = xaccSchedXactionGetEndDate (sxed->sx);
1349  if ( g_date_valid (gd))
1350  {
1351  gtk_toggle_button_set_active (sxed->optEndDate, TRUE);
1352  tmpDate = gnc_time64_get_day_start_gdate (gd);
1353  gnc_date_edit_set_time (sxed->endDateEntry, tmpDate);
1354 
1355  set_endgroup_toggle_states (sxed, END_DATE);
1356  }
1357  else if (xaccSchedXactionHasOccurDef (sxed->sx))
1358  {
1359  gint numOccur = xaccSchedXactionGetNumOccur (sxed->sx);
1360  gint numRemain = xaccSchedXactionGetRemOccur (sxed->sx);
1361 
1362  gtk_toggle_button_set_active (sxed->optEndCount, TRUE);
1363 
1364  gtk_spin_button_set_value (GTK_SPIN_BUTTON (sxed->endCountSpin), numOccur);
1365  gtk_spin_button_set_value (GTK_SPIN_BUTTON (sxed->endRemainSpin), numRemain);
1366 
1367  set_endgroup_toggle_states (sxed, END_OCCUR);
1368  }
1369  else
1370  {
1371  gtk_toggle_button_set_active (sxed->optEndNone, TRUE);
1372  set_endgroup_toggle_states (sxed, END_NEVER);
1373  }
1374 
1375  enabledState = xaccSchedXactionGetEnabled (sxed->sx);
1376  gtk_toggle_button_set_active (sxed->enabledOpt, enabledState);
1377 
1378  /* Do auto-create/notify setup */
1379  if (sxed->newsxP)
1380  {
1381  autoCreateState =
1382  gnc_prefs_get_bool (GNC_PREFS_GROUP_SXED, GNC_PREF_CREATE_AUTO);
1383  notifyState =
1384  gnc_prefs_get_bool (GNC_PREFS_GROUP_SXED, GNC_PREF_NOTIFY);
1385  }
1386  else
1387  {
1388  xaccSchedXactionGetAutoCreate (sxed->sx,
1389  &autoCreateState,
1390  &notifyState);
1391  }
1392  gtk_toggle_button_set_active (sxed->autocreateOpt, autoCreateState);
1393  if (!autoCreateState)
1394  {
1395  notifyState = FALSE;
1396  }
1397  gtk_toggle_button_set_active( sxed->notifyOpt, notifyState );
1398 
1399  /* Do days-in-advance-to-create widget[s] setup. */
1400  if ( sxed->newsxP )
1401  {
1402  daysInAdvance =
1403  gnc_prefs_get_float (GNC_PREFS_GROUP_SXED, GNC_PREF_CREATE_DAYS);
1404  }
1405  else
1406  {
1407  daysInAdvance =
1408  xaccSchedXactionGetAdvanceCreation (sxed->sx);
1409  }
1410  if (daysInAdvance != 0)
1411  {
1412  gtk_toggle_button_set_active (sxed->advanceOpt, TRUE);
1413  gtk_spin_button_set_value (sxed->advanceSpin,
1414  (gfloat)daysInAdvance);
1415  }
1416 
1417  /* Do days-in-advance-to-remind widget[s] setup. */
1418  if (sxed->newsxP)
1419  {
1420  daysInAdvance =
1421  gnc_prefs_get_float (GNC_PREFS_GROUP_SXED, GNC_PREF_REMIND_DAYS);
1422  }
1423  else
1424  {
1425  daysInAdvance =
1426  xaccSchedXactionGetAdvanceReminder (sxed->sx);
1427  }
1428  if (daysInAdvance != 0)
1429  {
1430  gtk_toggle_button_set_active (sxed->remindOpt, TRUE);
1431  gtk_spin_button_set_value (sxed->remindSpin,
1432  (gfloat)daysInAdvance);
1433  }
1434 
1435  if (sxed->newsxP)
1436  {
1437  gnc_sx_set_instance_count (sxed->sx, 1);
1438  }
1439 
1440  /* populate the ledger */
1441  {
1442  /* create the split list */
1443  GList *splitList;
1444 
1445  splitList = xaccSchedXactionGetSplits (sxed->sx);
1446  if (splitList != NULL)
1447  {
1448  model = gnc_ledger_display2_get_split_model_register (sxed->ledger);
1449  gnc_tree_model_split_reg_load (model, splitList, NULL );
1450  } /* otherwise, use the existing stuff. */
1451  }
1452 
1453  /* Update the example cal */
1454  gnc_sxed_update_cal (sxed);
1455 }
1456 
1457 
1458 static
1459 void
1460 set_endgroup_toggle_states (GncSxEditorDialog2 *sxed, EndType type)
1461 {
1462  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endDateEntry), (type == END_DATE) );
1463  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endCountSpin), (type == END_OCCUR) );
1464  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endRemainSpin), (type == END_OCCUR) );
1465 }
1466 
1467 
1468 static
1469 void
1470 endgroup_rb_toggled_cb (GtkButton *b, gpointer d)
1471 {
1472  /* figure out which one */
1473  GncSxEditorDialog2 *sxed;
1474  gint id;
1475 
1476  sxed = (GncSxEditorDialog2*)d;
1477  id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (b), "whichOneAmI"));
1478 
1479  switch (id)
1480  {
1481  case END_NEVER_OPTION:
1482  set_endgroup_toggle_states (sxed, END_NEVER);
1483  break;
1484  case END_DATE_OPTION:
1485  set_endgroup_toggle_states (sxed, END_DATE);
1486  break;
1487  case NUM_OCCUR_OPTION:
1488  set_endgroup_toggle_states (sxed, END_OCCUR);
1489  break;
1490  default:
1491  g_critical ("Unknown id %d", id);
1492  break;
1493  }
1494  gnc_sxed_update_cal (sxed);
1495 }
1496 
1497 
1498 /********************************************************************\
1499  * gnc_register_check_close *
1500  * *
1501  * Args: regData - the data struct for this register *
1502  * Return: none *
1503 \********************************************************************/
1504 static void
1505 gnc_sxed_reg_check_close (GncSxEditorDialog2 *sxed)
1506 {
1507  GncTreeViewSplitReg *view;
1508  Transaction *dirty_trans;
1509 
1510  const char *message =
1511  _("The current template transaction "
1512  "has been changed. "
1513  "Would you like to record the changes?");
1514 
1515  view = gnc_ledger_display2_get_split_view_register (sxed->ledger);
1516 
1517  dirty_trans = gnc_tree_view_split_reg_get_dirty_trans (view);
1518 
1519  if (dirty_trans == NULL)
1520  return;
1521 
1522  if (gnc_verify_dialog (sxed->dialog, TRUE, "%s", message))
1523  {
1524  /* Save the template transactions changes */
1525  xaccTransCommitEdit (dirty_trans);
1526  gnc_tree_view_split_reg_set_dirty_trans (view, NULL);
1527  return;
1528  }
1529  else
1530  {
1531  /* Cancel the template transactions changes */
1532  gnc_tree_view_split_reg_cancel_edit (view, TRUE);
1533  }
1534 }
1535 
1536 
1537 static gboolean
1538 editor_component_sx_equality (gpointer find_data,
1539  gpointer user_data)
1540 {
1541  return ((SchedXaction*)find_data
1542  == ((GncSxEditorDialog2*)user_data)->sx);
1543 }
1544 /*
1545 typedef enum { NO_END, DATE_END, COUNT_END } END_TYPE;
1546 */
1547 
1548 static void
1549 gnc_sxed_update_cal (GncSxEditorDialog2 *sxed)
1550 {
1551  GList *recurrences = NULL;
1552  GDate start_date, first_date;
1553 
1554  g_date_clear (&start_date, 1);
1555 
1556  gnc_frequency_save_to_recurrence (sxed->gncfreq, &recurrences, &start_date);
1557  g_date_subtract_days (&start_date, 1);
1558  recurrenceListNextInstance (recurrences, &start_date, &first_date);
1559 
1560  /* Deal with the fact that this SX may have been run before [the
1561  * calendar should only show upcoming instances]... */
1562  {
1563  const GDate *last_sx_inst;
1564 
1565  last_sx_inst = xaccSchedXactionGetLastOccurDate (sxed->sx);
1566  if (g_date_valid (last_sx_inst)
1567  && g_date_valid (&first_date)
1568  && g_date_compare (last_sx_inst, &first_date) != 0)
1569  {
1570  start_date = *last_sx_inst;
1571  recurrenceListNextInstance (recurrences, &start_date, &first_date);
1572  }
1573  }
1574 
1575  if (!g_date_valid (&first_date))
1576  {
1577  /* Nothing to do. */
1578  gnc_dense_cal_store_clear (sxed->dense_cal_model);
1579  goto cleanup;
1580  }
1581 
1582  gnc_dense_cal_store_update_name (sxed->dense_cal_model, xaccSchedXactionGetName (sxed->sx));
1583  {
1584  gchar *schedule_desc = recurrenceListToCompactString (recurrences);
1585  gnc_dense_cal_store_update_info (sxed->dense_cal_model, schedule_desc);
1586  g_free (schedule_desc);
1587  }
1588 
1589  //gnc_dense_cal_set_month(sxed->example_cal, g_date_get_month(&first_date));
1590  //gnc_dense_cal_set_year(sxed->example_cal, g_date_get_year(&first_date));
1591 
1592  /* figure out the end restriction */
1593  if (gtk_toggle_button_get_active (sxed->optEndDate))
1594  {
1595  GDate end_date;
1596  g_date_clear (&end_date, 1);
1597  gnc_gdate_set_time64 (&end_date, gnc_date_edit_get_date (sxed->endDateEntry));
1598  gnc_dense_cal_store_update_recurrences_date_end (sxed->dense_cal_model, &first_date, recurrences, &end_date);
1599  }
1600  else if (gtk_toggle_button_get_active (sxed->optEndNone))
1601  {
1602  gnc_dense_cal_store_update_recurrences_no_end (sxed->dense_cal_model, &first_date, recurrences);
1603  }
1604  else if (gtk_toggle_button_get_active (sxed->optEndCount))
1605  {
1606  gint num_remain
1607  = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
1608  gnc_dense_cal_store_update_recurrences_count_end (sxed->dense_cal_model, &first_date, recurrences, num_remain);
1609  }
1610  else
1611  {
1612  g_error ("unknown end condition");
1613  }
1614 
1615 cleanup:
1616  recurrenceListFree (&recurrences);
1617 }
1618 
1619 
1620 static void
1621 gnc_sxed_freq_changed (GncFrequency *gf, gpointer ud)
1622 {
1623  gnc_sxed_update_cal ((GncSxEditorDialog2*)ud);
1624 }
1625 
1626 
1627 static void
1628 sxed_excal_update_adapt_cb (GtkObject *o, gpointer ud)
1629 {
1630  gnc_sxed_update_cal ((GncSxEditorDialog2*)ud);
1631 }
1632 
1633 
1634 static void
1635 on_sx_check_toggled_cb (GtkWidget *togglebutton, gpointer user_data)
1636 {
1637  GtkWidget *widget_notify;
1638  GHashTable *table;
1639 
1640  PINFO("Togglebutton is %p and user_data is %p", togglebutton, user_data);
1641  PINFO("Togglebutton builder name is %s", gtk_buildable_get_name (GTK_BUILDABLE (togglebutton)));
1642 
1643  /* We need to use the hash table to find the required widget to activate. */
1644  table = g_object_get_data(G_OBJECT(user_data), "prefs_widget_hash");
1645  widget_notify = g_hash_table_lookup(table, "pref/" GNC_PREFS_GROUP_SXED "/" GNC_PREF_NOTIFY);
1646 
1647  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(togglebutton)))
1648  gtk_widget_set_sensitive (widget_notify, TRUE);
1649  else
1650  gtk_widget_set_sensitive (widget_notify, FALSE);
1651 }
1652 
1653 
1654 /* ------------------------------------------------------------ */
1655 /* sx app engine; move to somewhere appropriate. :/ */
1656 
1657 typedef struct _acct_deletion_handler_data
1658 {
1659  GList *affected_sxes;
1660  GtkWidget *dialog;
1662 
1663 
1664 static void
1665 _open_editors (GtkDialog *dialog, gint response_code, gpointer data)
1666 {
1668  gtk_widget_hide (adhd->dialog);
1669  {
1670  GList *sx_iter;
1671  for (sx_iter = adhd->affected_sxes; sx_iter; sx_iter = sx_iter->next)
1672  {
1673  gnc_ui_scheduled_xaction_editor_dialog_create2 ((SchedXaction*)sx_iter->data,
1674  FALSE);
1675  }
1676  }
1677  g_list_free (adhd->affected_sxes);
1678  gtk_widget_destroy (GTK_WIDGET (adhd->dialog));
1679  g_free (adhd);
1680 }
1681 
1682 
1683 static void
1684 _sx_engine_event_handler (QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data)
1685 {
1686  Account *acct;
1687  QofBook *book;
1688  GList *affected_sxes;
1689 
1690  if (!(event_type & QOF_EVENT_DESTROY))
1691  return;
1692  if (!GNC_IS_ACCOUNT(ent))
1693  return;
1694  acct = GNC_ACCOUNT(ent);
1695  book = qof_instance_get_book (QOF_INSTANCE (acct));
1696  affected_sxes = gnc_sx_get_sxes_referencing_account (book, acct);
1697 
1698  if (g_list_length (affected_sxes) == 0)
1699  return;
1700 
1701  {
1702  GList *sx_iter;
1704  GtkBuilder *builder;
1705  GtkWidget *dialog;
1706  GtkListStore *name_list;
1707  GtkTreeView *list;
1708  GtkTreeViewColumn *name_column;
1709  GtkCellRenderer *renderer;
1710 
1711  builder = gtk_builder_new();
1712  gnc_builder_add_from_file (builder, "dialog-sx.glade", "Account Deletion");
1713 
1714  dialog = GTK_WIDGET (gtk_builder_get_object (builder, "Account Deletion"));
1715 
1716  list = GTK_TREE_VIEW (gtk_builder_get_object (builder, "sx_list"));
1717 
1719  data->dialog = dialog;
1720  data->affected_sxes = affected_sxes;
1721  name_list = gtk_list_store_new (1, G_TYPE_STRING);
1722  for (sx_iter = affected_sxes; sx_iter != NULL; sx_iter = sx_iter->next)
1723  {
1724  SchedXaction *sx;
1725  GtkTreeIter iter;
1726  gchar *sx_name;
1727 
1728  sx = (SchedXaction*)sx_iter->data;
1729  sx_name = xaccSchedXactionGetName (sx);
1730  gtk_list_store_append (name_list, &iter);
1731  gtk_list_store_set (name_list, &iter, 0, sx_name, -1);
1732  }
1733  gtk_tree_view_set_model (list, GTK_TREE_MODEL (name_list));
1734  g_object_unref (G_OBJECT (name_list));
1735 
1736  renderer = gtk_cell_renderer_text_new();
1737  name_column = gtk_tree_view_column_new_with_attributes (_("Name"),
1738  renderer,
1739  "text", 0, NULL);
1740  gtk_tree_view_append_column (list, name_column);
1741 
1742  g_signal_connect (G_OBJECT (dialog), "response",
1743  G_CALLBACK (_open_editors), data);
1744 
1745  gtk_widget_show_all (GTK_WIDGET (dialog));
1746  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, data);
1747  g_object_unref (G_OBJECT (builder));
1748  }
1749 }
1750 
1751 
1752 void
1753 gnc_ui_sx_initialize2 (void) //FIXME need to remove the 2 when live
1754 {
1755  _sx_engine_event_handler_id = qof_event_register_handler (_sx_engine_event_handler, NULL);
1756 
1757  gnc_hook_add_dangler (HOOK_BOOK_OPENED,
1758  (GFunc)gnc_sx_sxsincelast_book_opened, NULL);
1759 
1760  /* Add page to preferences page for Sheduled Transactions */
1761  /* The parameters are; glade file, items to add from glade file - last being the dialog, preference tab name */
1762  gnc_preferences_add_page ("dialog-sx.glade",
1763  "create_days_adj,remind_days_adj,sx_prefs",
1764  _("Scheduled Transactions"));
1765 }
const GDate * xaccSchedXactionGetEndDate(const SchedXaction *sx)
Definition: SchedXaction.c:628
void gnc_sx_set_schedule(SchedXaction *sx, GList *schedule)
Definition: SchedXaction.c:565
void gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num)
GList * gnc_sx_get_schedule(const SchedXaction *sx)
Definition: SchedXaction.c:559
void gnc_preferences_add_page(const gchar *filename, const gchar *widgetname, const gchar *tabname)
void gnc_plugin_page_register2_set_options(GncPluginPage *plugin_page, gint lines_default, gboolean read_only)
void qof_instance_get(const QofInstance *inst, const gchar *first_param,...)
Wrapper for g_object_get.
GncEmbeddedWindow * gnc_embedded_window_new(const gchar *action_group_name, GtkActionEntry *action_entries, gint n_action_entries, const gchar *ui_filename, GtkWidget *enclosing_win, gboolean add_accelerators, gpointer user_data)
Date and Time handling routines.
QofBook * qof_instance_get_book(gconstpointer)
utility functions for the GnuCash UI
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
#define PINFO(format, args...)
Definition: qoflog.h:249
void xaccSchedXactionSetNumOccur(SchedXaction *sx, gint new_num)
Definition: SchedXaction.c:690
void gnc_embedded_window_close_page(GncEmbeddedWindow *window, GncPluginPage *page)
time64 gnc_time64_get_day_start_gdate(const GDate *date)
void gnc_plugin_page_set_ui_description(GncPluginPage *page, const char *ui_filename)
gboolean gnc_commodity_equal(const gnc_commodity *a, const gnc_commodity *b)
void gnc_embedded_window_open_page(GncEmbeddedWindow *window, GncPluginPage *page)
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
gboolean gnc_numeric_zero_p(gnc_numeric a)
Transaction * xaccSplitGetParent(const Split *split)
Definition: Split.c:1903
gchar * gnc_numeric_to_string(gnc_numeric n)
GList * gnc_sx_get_sxes_referencing_account(QofBook *book, Account *acct)
Definition: SX-book.c:366
Definition: guid.h:65
gint qof_event_register_handler(QofEventHandler handler, gpointer handler_data)
Register a handler for events.
gboolean qof_log_check(QofLogModule log_module, QofLogLevel log_level)
Account handling public routines.
gint QofEventId
Definition: qofevent.h:45
GncPluginPage * gnc_plugin_page_register2_new_ledger(GNCLedgerDisplay2 *ledger)
Anchor Scheduled Transaction info in a book. See src/doc/books.txt for design overview.
Functions for adding plugins to a GnuCash window.
#define MAX_DATE_LENGTH
Definition: gnc-date.h:106
void xaccTransCommitEdit(Transaction *trans)
Definition: Transaction.c:1579
Dialog for handling user preferences.
#define xaccSchedXactionGetGUID(X)
Definition: SchedXaction.h:321
void gnc_gnome_help(const char *file_name, const char *anchor)
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
void xaccSchedXactionSetName(SchedXaction *sx, const gchar *newName)
Definition: SchedXaction.c:581
GDate helper routines.
Definition: SplitP.h:71
gchar * guid_to_string(const GncGUID *guid)
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Definition: Account.c:3148
Functions that are supported by all types of windows.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Definition: gnc-prefs.c:196
time64 gnc_time(time64 *tbuf)
get the current local time
const char * xaccSplitGetMemo(const Split *split)
Definition: Split.c:1968
gint64 time64
Definition: gnc-date.h:83
gboolean xaccSchedXactionHasOccurDef(const SchedXaction *sx)
Definition: SchedXaction.c:678
Scheduled Transactions public handling routines.
void xaccSchedXactionSetEndDate(SchedXaction *sx, const GDate *newEnd)
Definition: SchedXaction.c:635
#define GNC_DENOM_AUTO
Definition: gnc-numeric.h:246
void xaccSchedXactionDestroy(SchedXaction *sx)
Definition: SchedXaction.c:473
size_t qof_print_gdate(char *buf, size_t bufflen, const GDate *gd)
const gchar * QofLogModule
Definition: qofid.h:89
void gnc_gdate_set_time64(GDate *gd, time64 time)
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Definition: gnc-prefs.c:227
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
Definition: Account.c:1827