GnuCash  5.6-150-g038405b370+
dialog-sx-editor.c
1 /********************************************************************\
2  * dialog-sx-editor.c : dialog for scheduled transaction editing *
3  * Copyright (C) 2001,2002,2006 Joshua Sled <jsled@asynchronous.org>*
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 gnu@gnu.org *
33 \********************************************************************/
34 
35 #include <config.h>
36 #include <stdbool.h>
37 
38 #include <gtk/gtk.h>
39 #include <glib/gi18n.h>
40 #include <locale.h>
41 
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-editor.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"
62 #include "gnc-prefs.h"
63 #include "gnc-ui.h"
64 #include "gnc-ui-util.h"
65 #include "gnucash-sheet.h"
66 #include "gnc-session.h"
67 #include <gnc-glib-utils.h>
68 
69 #include "gnc-split-reg.h"
70 
71 #include "gnc-sx-instance-model.h"
72 #include "dialog-sx-since-last-run.h"
73 
74 #undef G_LOG_DOMAIN
75 #define G_LOG_DOMAIN "gnc.gui.sx.editor"
76 
77 static QofLogModule log_module = GNC_MOD_GUI_SX;
78 
79 static gint _sx_engine_event_handler_id = -1;
80 
81 #define END_NEVER_OPTION 0
82 #define END_DATE_OPTION 1
83 #define NUM_OCCUR_OPTION 2
84 
85 #define NUM_LEDGER_LINES_DEFAULT 6
86 
87 #define EX_CAL_NUM_MONTHS 12
88 #define EX_CAL_MO_PER_COL 3
89 
90 #define GNC_D_WIDTH 25
91 #define GNC_D_BUF_WIDTH 26
92 
95 typedef enum _EndTypeEnum
96 {
97  END_NEVER,
98  END_DATE,
99  END_OCCUR,
100 } EndType;
101 
103 {
104  GtkWidget *dialog;
105  GtkBuilder *builder;
106  GtkNotebook *notebook;
107  SchedXaction *sx;
108  /* If this is a new scheduled transaction or not. */
109  int newsxP;
110 
111  /* The various widgets in the dialog */
112  GNCLedgerDisplay *ledger;
113 
114  GncFrequency *gncfreq;
115  GncDenseCalStore *dense_cal_model;
116  GncDenseCal *example_cal;
117 
118  GtkEntry *nameEntry;
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  EndType end_type;
133  GtkEntry *endCountSpin;
134  GtkEntry *endRemainSpin;
135  GNCDateEdit *endDateEntry;
136 
137  char *sxGUIDstr;
138 
139  GncEmbeddedWindow *embed_window;
140  GncPluginPage *plugin_page;
141 };
142 
145 static void schedXact_editor_create_freq_sel (GncSxEditorDialog *sxed);
146 static void schedXact_editor_create_ledger (GncSxEditorDialog *sxed);
147 static void schedXact_editor_populate (GncSxEditorDialog *);
148 static void endgroup_rb_toggled_cb (GtkButton *b, gpointer d);
149 static void set_endgroup_toggle_states (GncSxEditorDialog *sxed, EndType t);
150 static void advance_toggled_cb (GtkButton *b, GncSxEditorDialog *sxed);
151 static void remind_toggled_cb (GtkButton *b, GncSxEditorDialog *sxed);
152 static gboolean gnc_sxed_check_consistent (GncSxEditorDialog *sxed);
153 static gboolean gnc_sxed_check_changed (GncSxEditorDialog *sxed);
154 static void gnc_sxed_save_sx (GncSxEditorDialog *sxed);
155 static void gnc_sxed_freq_changed (GncFrequency *gf, gpointer ud);
156 static void sxed_excal_update_adapt_cb (GtkWidget *o, gpointer ud);
157 static void gnc_sxed_update_cal (GncSxEditorDialog *sxed);
158 void on_sx_check_toggled_cb (GtkWidget *togglebutton, gpointer user_data);
159 static void gnc_sxed_reg_check_close (GncSxEditorDialog *sxed);
160 static gboolean sxed_delete_event (GtkWidget *widget, GdkEvent *event, gpointer ud);
161 static gboolean sxed_confirmed_cancel (GncSxEditorDialog *sxed);
162 static gboolean editor_component_sx_equality (gpointer find_data,
163  gpointer user_data);
164 
165 static GActionEntry gnc_sxed_menu_entries [] =
166 {
167  { "EditAction", NULL, NULL, NULL, NULL },
168  { "TransactionAction", NULL, NULL, NULL, NULL },
169  { "ViewAction", NULL, NULL, NULL, NULL },
170  { "ActionsAction", NULL, NULL, NULL, NULL },
171 };
172 static guint gnc_sxed_menu_n_entries = G_N_ELEMENTS(gnc_sxed_menu_entries);
173 
176 static void
177 sxed_close_handler (gpointer user_data)
178 {
179  GncSxEditorDialog *sxed = user_data;
180 
181  gnc_sxed_reg_check_close (sxed);
182  gnc_save_window_size (GNC_PREFS_GROUP_SXED, GTK_WINDOW (sxed->dialog));
183  gtk_widget_destroy (sxed->dialog);
184  /* The data will be cleaned up in the destroy handler. */
185 }
186 
187 
192 static gboolean
193 sxed_confirmed_cancel (GncSxEditorDialog *sxed)
194 {
195  SplitRegister *reg;
196 
197  reg = gnc_ledger_display_get_split_register (sxed->ledger);
198  /* check for changes */
199  if (gnc_sxed_check_changed (sxed))
200  {
201  const char *sx_changed_msg =
202  _("This Scheduled Transaction has changed; are you "
203  "sure you want to cancel?");
204  if (!gnc_verify_dialog (GTK_WINDOW (sxed->dialog), FALSE, "%s", sx_changed_msg))
205  {
206  return FALSE;
207  }
208  }
209  /* cancel ledger changes */
211  return TRUE;
212 }
213 
214 
215 /**********************************
216  * Dialog Action Button functions *
217  *********************************/
218 static void
219 editor_cancel_button_clicked_cb (GtkButton *b, GncSxEditorDialog *sxed)
220 {
221  /* close */
222  if (!sxed_confirmed_cancel (sxed))
223  return;
224 
225  gnc_close_gui_component_by_data (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS,
226  sxed);
227 }
228 
229 
230 static void
231 editor_help_button_clicked_cb (GtkButton *b, GncSxEditorDialog *sxed)
232 {
233  gnc_gnome_help (GTK_WINDOW (sxed->dialog), DF_MANUAL, DL_SXEDITOR);
234 }
235 
236 
237 static void
238 editor_ok_button_clicked_cb (GtkButton *b, GncSxEditorDialog *sxed)
239 {
240  QofBook *book;
241  SchedXactions *sxes;
242 
243  if (!gnc_sxed_check_consistent (sxed))
244  return;
245 
246  gnc_sxed_save_sx (sxed);
247 
248  /* add to list */
249  // @@fixme -- forget 'new'-flag: check for existence of the SX [?]
250  if (sxed->newsxP)
251  {
252  book = gnc_get_current_book ();
253  sxes = gnc_book_get_schedxactions (book);
254  gnc_sxes_add_sx (sxes, sxed->sx);
255  sxed->newsxP = FALSE;
256  }
257 
258  /* cleanup */
259  gnc_close_gui_component_by_data (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS,
260  sxed);
261 }
262 
263 
264 static gboolean
265 gnc_sxed_check_name_changed (GncSxEditorDialog *sxed)
266 {
267  const char *name = gtk_entry_get_text (sxed->nameEntry);
268 
269  if (!name || !name[0])
270  return TRUE;
271 
272  if (xaccSchedXactionGetName (sxed->sx) == NULL ||
273  strcmp (xaccSchedXactionGetName (sxed->sx), name) != 0)
274  return TRUE;
275 
276  return FALSE;
277 }
278 
279 static gboolean
280 gnc_sxed_check_end_date_changed (GncSxEditorDialog *sxed)
281 {
282  GDate sxEndDate, dlgEndDate;
283 
284  if (!xaccSchedXactionHasEndDate (sxed->sx))
285  return TRUE;
286 
287  sxEndDate = *xaccSchedXactionGetEndDate (sxed->sx);
288  gnc_gdate_set_time64 (&dlgEndDate,
289  gnc_date_edit_get_date (sxed-> endDateEntry));
290 
291  if (g_date_compare (&sxEndDate, &dlgEndDate) != 0)
292  return TRUE;
293 
294  return FALSE;
295 }
296 
297 static gboolean
298 gnc_sxed_check_num_occurs_changed (GncSxEditorDialog *sxed)
299 {
300  gint sxNumOccur, sxNumRem, dlgNumOccur, dlgNumRem;
301 
302  if (!xaccSchedXactionGetNumOccur (sxed->sx))
303  return TRUE;
304  dlgNumOccur =
305  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
306  dlgNumRem =
307  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
308  sxNumOccur = xaccSchedXactionGetNumOccur (sxed->sx);
309  sxNumRem = xaccSchedXactionGetRemOccur (sxed->sx);
310 
311  if (dlgNumOccur != sxNumOccur || dlgNumRem != sxNumRem)
312  return TRUE;
313 
314  return FALSE;
315 }
316 
317 static gboolean
318 gnc_sxed_check_creation_changed (GncSxEditorDialog *sxed)
319 {
320  gboolean sxAutoCreate, sxNotify;
321  gint dlgAdvance = 0;
322  gint dlgRemind = 0;
323 
324  gboolean dlgEnabled =
325  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->enabledOpt));
326  gboolean dlgAutoCreate =
327  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->autocreateOpt));
328  gboolean dlgNotify =
329  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->notifyOpt));
330 
331  if (dlgEnabled != xaccSchedXactionGetEnabled (sxed->sx))
332  return TRUE;
333 
334  xaccSchedXactionGetAutoCreate (sxed->sx, &sxAutoCreate, &sxNotify);
335  if (dlgAutoCreate != sxAutoCreate || dlgNotify != sxNotify)
336  return TRUE;
337 
338  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->advanceOpt)))
339  dlgAdvance = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->advanceSpin));
340  if (dlgAdvance != xaccSchedXactionGetAdvanceCreation (sxed->sx))
341  return TRUE;
342 
343  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sxed->remindOpt)))
344  dlgRemind = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->remindSpin));
345  if (dlgRemind != xaccSchedXactionGetAdvanceReminder (sxed->sx))
346  return TRUE;
347 
348  return FALSE;
349 }
350 
351 static gboolean
352 gnc_sxed_check_dates_changed (GncSxEditorDialog *sxed)
353 {
354  GList *dialog_schedule = NULL;
355  GDate dialog_start_date, sx_start_date;
356  gchar *dialog_schedule_str, *sx_schedule_str;
357  gboolean schedules_are_the_same, start_dates_are_the_same;
358 
359  g_date_clear (&dialog_start_date, 1);
360  gnc_frequency_save_to_recurrence (sxed->gncfreq, &dialog_schedule,
361  &dialog_start_date);
362  dialog_schedule_str = recurrenceListToString (dialog_schedule);
363  recurrenceListFree (&dialog_schedule);
364 
365  sx_start_date = *xaccSchedXactionGetStartDate (sxed->sx);
366  sx_schedule_str = recurrenceListToString (gnc_sx_get_schedule (sxed->sx));
367 
368  DEBUG ("dialog schedule [%s], sx schedule [%s]",
369  dialog_schedule_str, sx_schedule_str);
370 
371  schedules_are_the_same = (strcmp (dialog_schedule_str,
372  sx_schedule_str) == 0);
373  g_free (dialog_schedule_str);
374  g_free (sx_schedule_str);
375 
376  start_dates_are_the_same = (g_date_compare (&dialog_start_date,
377  &sx_start_date) == 0);
378 
379  if (schedules_are_the_same && start_dates_are_the_same)
380  return FALSE;
381  return TRUE;
382 }
383 
384 /*************************************************************************
385  * Checks to see if the SX has been modified from it's previously-saved
386  * state.
387  * @return TRUE if this is a 'new' SX, or if the SX has changed from it's
388  * previous configuration.
389  ************************************************************************/
390 static gboolean
391 gnc_sxed_check_changed (GncSxEditorDialog *sxed)
392 {
393  SplitRegister *sr = NULL;
394  if (sxed->newsxP)
395  return TRUE;
396 
397  /* name */
398  if (gnc_sxed_check_name_changed (sxed))
399  return TRUE;
400  /* end options */
401  /* dialog says... no end */
402  if (gtk_toggle_button_get_active (sxed->optEndNone) &&
403  (xaccSchedXactionHasEndDate (sxed->sx) ||
404  xaccSchedXactionHasOccurDef (sxed->sx)))
405  return TRUE;
406 
407  /* dialog says... end date */
408  if (gtk_toggle_button_get_active (sxed->optEndDate) &&
409  gnc_sxed_check_end_date_changed (sxed))
410  return TRUE;
411 
412  /* dialog says... num occur */
413  if (gtk_toggle_button_get_active (sxed->optEndCount) &&
414  gnc_sxed_check_num_occurs_changed (sxed))
415  return TRUE;
416  /* SX options [autocreate, notify, reminder, advance] */
417  if (gnc_sxed_check_creation_changed (sxed))
418  return TRUE;
419  /* Dates and Schedules */
420  if (gnc_sxed_check_dates_changed (sxed))
421  return TRUE;
422 
423  /* template transactions */
424  sr = gnc_ledger_display_get_split_register (sxed->ledger);
426  return TRUE;
427 
428  return FALSE;
429 }
430 
431 
432 /*****************************************************************************
433  * Holds the credit- and debit-sum for a given Transaction, as used in
434  * gnc_sxed_check_consistent.
435  ****************************************************************************/
436 typedef struct _txnCreditDebitSums
437 {
438  gnc_numeric creditSum;
439  gnc_numeric debitSum;
440  gnc_commodity *base_cmdty;
441  GtkWindow *window;
442  gboolean multi_commodity;
444 
445 static txnCreditDebitSums *
446 tcds_new (void)
447 {
448  txnCreditDebitSums *tcds = g_new0 (txnCreditDebitSums, 1);
449  tcds->creditSum = tcds->debitSum = gnc_numeric_zero ();
450  tcds->base_cmdty = NULL;
451  tcds->window = NULL;
452  tcds->multi_commodity = FALSE;
453  return tcds;
454 }
455 
456 static void
457 set_sums_to_zero (gpointer key,
458  gpointer val,
459  gpointer ud)
460 {
462  tcds->creditSum = gnc_numeric_zero ();
463  tcds->debitSum = gnc_numeric_zero ();
464  tcds->base_cmdty = NULL;
465  tcds->multi_commodity = FALSE;
466 }
467 
468 inline static gnc_numeric
469 tcds_difference (txnCreditDebitSums *tcds)
470 {
471  return gnc_numeric_sub_fixed (tcds->debitSum, tcds->creditSum);
472 }
473 
474 static void
475 check_credit_debit_balance (gpointer key, gpointer val, gpointer ud)
476 {
478  Transaction *txn = GNC_TRANSACTION(key);
479  gboolean *unbalanced = (gboolean*)ud;
480  gnc_numeric diff = tcds_difference (tcds);
481  const char *result = gnc_numeric_zero_p (diff) ? "true" : "false";
482  *unbalanced |= !(gnc_numeric_zero_p (diff));
483 
484  DEBUG ("%p | %s [%s - %s = %s]", key, result,
485  gnc_num_dbg_to_string (tcds->debitSum),
486  gnc_num_dbg_to_string (tcds->creditSum),
487  gnc_num_dbg_to_string (diff));
488 
489  if (!gnc_numeric_zero_p (diff) && !tcds->multi_commodity)
490  {
491  char string[32];
492  gchar *msg_text;
493  const gchar *desc = xaccTransGetDescription (txn);
494  GNCPrintAmountInfo print_info = gnc_commodity_print_info (tcds->base_cmdty, TRUE);
495  gnc_numeric abs_diff = gnc_numeric_abs (diff);
496  xaccSPrintAmount (string, abs_diff, print_info);
497  msg_text = g_strdup_printf (_("Transaction with description '%s' can not be balanced.\n"
498  "The difference is %s"), desc, string);
499 
500  gnc_warning_dialog (tcds->window, "%s", msg_text);
501  g_free (msg_text);
502  }
503 }
504 
505 static gboolean
506 gnc_sxed_check_names (GncSxEditorDialog *sxed)
507 {
508  const gchar *name = gtk_entry_get_text (sxed->nameEntry);
509  if (!name || !name[0])
510  {
511  const char *sx_has_no_name_msg =
512  _("Please name the Scheduled Transaction.");
513  gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", sx_has_no_name_msg);
514  return FALSE;
515 
516  }
517 
518  bool nameExists = FALSE;
519  gchar *nameKey = g_utf8_collate_key (name, -1);
520  bool nameHasChanged =
521  (xaccSchedXactionGetName (sxed->sx) == NULL)
522  || (strcmp (xaccSchedXactionGetName (sxed->sx), name) != 0);
523  for (GList *sxList = gnc_book_get_schedxactions (gnc_get_current_book ())->sx_list;
524  nameHasChanged && !nameExists && sxList;
525  sxList = sxList->next)
526  {
527  const char *existingName = xaccSchedXactionGetName ((SchedXaction*)sxList->data);
528  char *existingNameKey = g_utf8_collate_key (existingName, -1);
529  nameExists |= (strcmp (nameKey, existingNameKey) == 0);
530  g_free (existingNameKey);
531  }
532  g_free (nameKey);
533  if (nameHasChanged && nameExists)
534  {
535  const char *sx_has_existing_name_msg =
536  _("A Scheduled Transaction with the name \"%s\" already exists. "
537  "Are you sure you want to name this one the same?");
538  if (!gnc_verify_dialog (GTK_WINDOW (sxed->dialog), FALSE,
539  sx_has_existing_name_msg, name))
540  return FALSE;
541  }
542  return TRUE;
543 }
544 
545 static gboolean
546 gnc_sxed_check_endpoint (GncSxEditorDialog *sxed)
547 {
548  GDate startDate, endDate, nextDate;
549  GList *schedule = NULL;
550 
551  if (!gtk_toggle_button_get_active (sxed->optEndDate)
552  && !gtk_toggle_button_get_active (sxed->optEndCount)
553  && !gtk_toggle_button_get_active (sxed->optEndNone))
554  {
555  const char *sx_end_spec_msg =
556  _("Please provide a valid end selection.");
557  gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", sx_end_spec_msg);
558  return FALSE;
559  }
560 
561  if (gtk_toggle_button_get_active (sxed->optEndCount))
562  {
563  gint occur =
564  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
565  gint rem =
566  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
567 
568  if (occur == 0)
569  {
570  const char *sx_occur_count_zero_msg =
571  _("There must be some number of occurrences.");
572  gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", sx_occur_count_zero_msg);
573  return FALSE;
574  }
575 
576  if (rem > occur)
577  {
578  const char *sx_occur_counts_wrong_msg =
579  _("The number of remaining occurrences (%d) is greater than "
580  "the number of total occurrences (%d).");
581  gnc_error_dialog (GTK_WINDOW (sxed->dialog), sx_occur_counts_wrong_msg,
582  rem, occur);
583  return FALSE;
584  }
585  return TRUE;
586  }
587 
588  g_date_clear (&endDate, 1);
589  if (gtk_toggle_button_get_active (sxed->optEndDate))
590  {
591  gnc_gdate_set_time64 (&endDate,
592  gnc_date_edit_get_date (sxed-> endDateEntry));
593  }
594 
595  g_date_clear (&nextDate, 1);
596  gnc_frequency_save_to_recurrence (sxed->gncfreq, &schedule, &startDate);
597  if (gnc_list_length_cmp (schedule, 0))
598  {
599  g_date_subtract_days (&startDate, 1);
600  recurrenceListNextInstance (schedule, &startDate, &nextDate);
601  }
602  recurrenceListFree (&schedule);
603 
604  if (!g_date_valid (&nextDate) ||
605  (g_date_valid (&endDate) && (g_date_compare (&nextDate, &endDate) > 0)))
606  {
607  const char *invalid_sx_check_msg =
608  _("You have attempted to create a Scheduled Transaction which "
609  "will never run. Do you really want to do this?");
610  if (!gnc_verify_dialog (GTK_WINDOW (sxed->dialog), FALSE,
611  "%s", invalid_sx_check_msg))
612  return FALSE;
613  }
614  return TRUE;
615 }
616 
617 static gboolean
618 gnc_sxed_check_autocreate (GncSxEditorDialog *sxed, int ttVarCount,
619  int splitCount, gboolean multi_commodity)
620 {
621  gboolean autocreateState;
622 
623  autocreateState =
624  gtk_toggle_button_get_active (
625  GTK_TOGGLE_BUTTON (sxed->autocreateOpt));
626 
627  if (((ttVarCount > 0) || multi_commodity) && autocreateState)
628  {
629  gnc_warning_dialog (GTK_WINDOW (sxed->dialog), "%s",
630  _("Scheduled Transactions with variables "
631  "or involving more than one commodity "
632  "cannot be automatically created."));
633  return FALSE;
634  }
635 
636  /* Fix for part of Bug#121740 -- auto-create transactions are
637  * only valid if there's actually a transaction to create. */
638  if (autocreateState && splitCount == 0)
639  {
640  gnc_warning_dialog (GTK_WINDOW (sxed->dialog), "%s",
641  _("Scheduled Transactions without a template "
642  "transaction cannot be automatically created."));
643  return FALSE;
644  }
645  return TRUE;
646 }
647 
648 static gboolean
649 gnc_sxed_split_check_account (GncSxEditorDialog *sxed, Split *s, txnCreditDebitSums *tcds)
650 {
651  GncGUID *acct_guid = NULL;
652  qof_instance_get (QOF_INSTANCE (s), "sx-account", &acct_guid, NULL);
653  Account *acct = xaccAccountLookup (acct_guid, gnc_get_current_book ());
654  guid_free (acct_guid);
655  // If the split is being destroyed always return TRUE.
656  if (acct == NULL && !qof_instance_get_destroying (s))
657  return FALSE;
658 
659  gnc_commodity *split_cmdty = xaccAccountGetCommodity (acct);
660 
661  if (!tcds->base_cmdty)
662  tcds->base_cmdty = split_cmdty;
663 
664  tcds->multi_commodity |= !gnc_commodity_equal (split_cmdty, tcds->base_cmdty);
665 
666  return TRUE;
667 }
668 
669 static gboolean
670 gnc_sxed_split_calculate_formula (GncSxEditorDialog *sxed, Split *s,
671  GHashTable *vars, const char *key,
672  txnCreditDebitSums *tcds)
673 {
674  gnc_numeric tmp = gnc_numeric_zero ();
675  char *str = NULL;
676  qof_instance_get (QOF_INSTANCE (s),
677  key, &str,
678  NULL);
679  if (!str || !str[0])
680  {
681  if (str)
682  g_free (str);
683  return TRUE; /* No formula no foul */
684  }
685  if (gnc_sx_parse_vars_from_formula (str, vars, &tmp) < 0)
686  {
687  gchar *err = g_strdup_printf (_("Couldn't parse %s for split \"%s\"."),
688  key, xaccSplitGetMemo (s));
689  gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", err);
690  g_free (err);
691  g_free (str);
692 
693  return FALSE;
694  }
695  if (g_strcmp0 (key, "sx-credit-formula") == 0)
696  tcds->creditSum = gnc_numeric_add (tcds->creditSum, tmp, 100,
698  else
699  tcds->debitSum = gnc_numeric_add (tcds->debitSum, tmp, 100,
701  g_free (str);
702  return TRUE;
703 }
704 
705 typedef struct
706 {
707  GncSxEditorDialog *sxed;
708  GHashTable *txns;
709  GHashTable *vars;
710  txnCreditDebitSums *tcds;
711  gboolean multi_commodity;
712  gboolean err;
714 
715 static void
716 split_error_warning_dialog (GtkWidget *parent, const gchar *title,
717  gchar *message)
718 {
719  GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW (parent), 0,
720  GTK_MESSAGE_ERROR,
721  GTK_BUTTONS_CLOSE,
722  "%s", title);
723  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
724  "%s", message);
725  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent));
726  g_signal_connect_swapped (dialog, "response",
727  G_CALLBACK (gtk_widget_destroy), dialog);
728  gtk_dialog_run (GTK_DIALOG (dialog));
729 
730 }
731 
732 static gboolean
733 check_transaction_splits (Transaction *txn, gpointer data)
734 {
735  GList *splitList = xaccTransGetSplitList (txn);
737 
738  for (; splitList; splitList = splitList->next)
739  {
740  Split *s = (Split*)splitList->data;
741 
742  if (g_hash_table_lookup (sd->txns, (gpointer)txn) == NULL)
743  {
744  sd->tcds = tcds_new ();
745  sd->tcds->window = GTK_WINDOW(sd->sxed->dialog);
746  g_hash_table_insert (sd->txns, (gpointer)txn, (gpointer)(sd->tcds));
747  }
748 
749  if (!gnc_sxed_split_check_account (sd->sxed, s, sd->tcds))
750  {
751  gchar *message = g_strdup_printf
752  (_("Split with memo %s has an invalid account."),
753  xaccSplitGetMemo (s));
754  split_error_warning_dialog (sd->sxed->dialog,
755  _("Invalid Account in Split"),
756  message);
757  g_free (message);
758  sd->err = TRUE;
759  return FALSE;
760  }
761 
762  sd->multi_commodity |= sd->tcds->multi_commodity;
763 
764  if (!gnc_sxed_split_calculate_formula (sd->sxed, s, sd->vars,
765  "sx-credit-formula",
766  sd->tcds))
767  {
768  gchar *message = g_strdup_printf
769  (_("Split with memo %s has an unparsable Credit Formula."),
770  xaccSplitGetMemo (s));
771  split_error_warning_dialog (sd->sxed->dialog,
772  _("Unparsable Formula in Split"),
773  message);
774  g_free (message);
775  sd->err = TRUE;
776  return FALSE;
777  }
778 
779  if (!gnc_sxed_split_calculate_formula (sd->sxed, s, sd->vars,
780  "sx-debit-formula",
781  sd->tcds))
782 
783  {
784  gchar *message = g_strdup_printf
785  (_("Split with memo %s has an unparsable Debit Formula."),
786  xaccSplitGetMemo (s));
787  split_error_warning_dialog (sd->sxed->dialog,
788  _("Unparsable Formula in Split"),
789  message);
790  g_free (message);
791  sd->err = TRUE;
792  return FALSE;
793  }
794  }
795  return FALSE; // return FALSE to continue to next transaction
796 }
797 
798 /*******************************************************************************
799  * Checks to make sure that the SX is in a reasonable state to save.
800  * @return true if checks out okay, false otherwise.
801  ******************************************************************************/
802 static gboolean
803 gnc_sxed_check_consistent (GncSxEditorDialog *sxed)
804 {
805 
806  /* Do checks on validity and such, interrupting the user if
807  * things aren't right.
808  *
809  * Features...
810  * X support formulas [?!]
811  * X balancing the SX if contain numeric-only formula data.
812  * X agreement with create-automagically/notification controls
813  * X the 'will ever be valid' check should take num-occur vals into
814  * account.
815  * X SX name is unique
816  * X SX has a name
817  * X "weekly" FS has some days set.
818  * X "once" with reasonable start/end dates.
819  * X This doesn't work at the time the 'weekly' one was fixed with
820  * user-confirmation, below; the once SX is always valid.
821  * [X more generically, creating a "not scheduled" SX is probably not
822  * right... ]
823  */
824 
825  gint ttVarCount = 0, splitCount = 0;
826  static const int NUM_ITERS_WITH_VARS = 5;
827  static const int NUM_ITERS_NO_VARS = 1;
828  int numIters = NUM_ITERS_NO_VARS, i;
829  gboolean unbalanceable = FALSE;
830  gpointer unusedKey, unusedValue;
831 
832  GHashTable *vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
833  (GDestroyNotify)gnc_sx_variable_free);
834  GHashTable *txns = g_hash_table_new_full (g_direct_hash, g_direct_equal,
835  NULL, g_free);
836  CheckTxnSplitData sd = {sxed, txns, vars, NULL, FALSE, FALSE};
837 
847  gnc_sxed_reg_check_close (sxed);
848  /* numeric-formulas-get-balanced determination */
849  gnc_sx_get_variables (sxed->sx, vars);
850 
851  ttVarCount = g_hash_table_size (vars);
852  if (ttVarCount != 0)
853  {
854  /* balance with random variable bindings some number of times in an
855  * attempt to ferret out un-balanceable transactions.
856  */
857  numIters = NUM_ITERS_WITH_VARS;
858  }
859 
860  for (i = 0; i < numIters && !unbalanceable; i++)
861  {
862  GList *splitList = xaccSchedXactionGetSplits (sxed->sx);
863  Account *tmpl_acct = gnc_sx_get_template_transaction_account (sxed->sx);
864  gnc_sx_randomize_variables (vars);
865  g_hash_table_foreach (txns, set_sums_to_zero, NULL);
866 
867  splitCount += g_list_length (splitList);
868  g_list_free (splitList);
869 
870  xaccAccountForEachTransaction (tmpl_acct, check_transaction_splits, &sd);
871 
872  if (sd.err)
873  {
874  g_hash_table_destroy (vars);
875  g_hash_table_destroy (txns);
876  return FALSE;
877  }
878 
879  g_hash_table_foreach (txns, check_credit_debit_balance, &unbalanceable);
880  }
881 
882  /* Subtract out pre-defined vars */
883  if (g_hash_table_lookup_extended (vars, "i", &unusedKey, &unusedValue))
884  ttVarCount -= 1;
885 
886  g_hash_table_destroy (vars);
887  g_hash_table_destroy (txns);
888 
889  if (unbalanceable)
890  {
891  const char *msg =
892  _("The Scheduled Transaction Editor cannot automatically balance "
893  "all of the transactions in this this Scheduled Transaction.\n"
894  "Should it still be entered?");
895  if (!gnc_verify_dialog (GTK_WINDOW (sxed->dialog), FALSE, "%s", msg))
896  return FALSE;
897  }
898 
899  if (!gnc_sxed_check_names (sxed))
900  return FALSE;
901 
902  if (!gnc_sxed_check_autocreate (sxed, ttVarCount,
903  splitCount, sd.multi_commodity))
904  return FALSE;
905 
906  if (!gnc_sxed_check_endpoint (sxed))
907  return FALSE;
908  return TRUE;
909 }
910 
911 
912 /******************************************************************************
913  * Saves the contents of the SX. This assumes that gnc_sxed_check_consistent
914  * has returned true.
915  *****************************************************************************/
916 static void
917 gnc_sxed_save_sx (GncSxEditorDialog *sxed)
918 {
919  gnc_sx_begin_edit (sxed->sx);
920 
921  /* name */
922  const gchar *name = gtk_entry_get_text (sxed->nameEntry);
923  if (name && *name)
924  xaccSchedXactionSetName (sxed->sx, name);
925 
926  /* date */
927  {
928  GDate gdate;
929 
930  if (gtk_toggle_button_get_active (sxed->optEndDate))
931  {
932  /* get the end date data */
933  gnc_gdate_set_time64(&gdate,
934  gnc_date_edit_get_date (
935  sxed->endDateEntry));
936  xaccSchedXactionSetEndDate (sxed->sx, &gdate);
937  /* set the num occurrences data */
938  xaccSchedXactionSetNumOccur (sxed->sx, 0);
939  }
940  else if (gtk_toggle_button_get_active (sxed->optEndCount))
941  {
942  gint num;
943 
944  /* get the occurrences data */
945  num =
946  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
947  xaccSchedXactionSetNumOccur (sxed->sx, num);
948 
949  num =
950  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
951  xaccSchedXactionSetRemOccur (sxed->sx, num);
952 
953  g_date_clear (&gdate, 1);
954  xaccSchedXactionSetEndDate (sxed->sx, &gdate);
955  }
956  else if (gtk_toggle_button_get_active (sxed->optEndNone))
957  {
958  xaccSchedXactionSetNumOccur (sxed->sx, 0);
959  g_date_clear (&gdate, 1);
960  xaccSchedXactionSetEndDate (sxed->sx, &gdate);
961  }
962  else
963  {
964  g_critical ("no valid end specified\n");
965  }
966  }
967 
968  /* Enabled states */
969  {
970  gboolean enabledState;
971 
972  enabledState = gtk_toggle_button_get_active (sxed->enabledOpt);
973  xaccSchedXactionSetEnabled (sxed->sx, enabledState);
974  }
975 
976  /* Auto-create/notification states */
977  {
978  gboolean autocreateState, notifyState;
979 
980  autocreateState = gtk_toggle_button_get_active (sxed->autocreateOpt);
981  notifyState = gtk_toggle_button_get_active (sxed->notifyOpt);
982  /* "Notify" only makes sense if AutoCreate is activated;
983  * enforce that here. */
984  xaccSchedXactionSetAutoCreate (sxed->sx,
985  autocreateState,
986  (autocreateState & notifyState));
987  }
988 
989  /* days in advance */
990  {
991  int daysInAdvance;
992 
993  daysInAdvance = 0;
994  if (gtk_toggle_button_get_active (sxed->advanceOpt))
995  {
996  daysInAdvance =
997  gtk_spin_button_get_value_as_int (sxed->advanceSpin);
998  }
999  xaccSchedXactionSetAdvanceCreation (sxed->sx, daysInAdvance);
1000 
1001  daysInAdvance = 0;
1002  if (gtk_toggle_button_get_active (sxed->remindOpt))
1003  {
1004  daysInAdvance =
1005  gtk_spin_button_get_value_as_int (sxed->remindSpin);
1006  }
1007  xaccSchedXactionSetAdvanceReminder (sxed->sx, daysInAdvance);
1008  }
1009 
1010  /* start date and freq spec */
1011  {
1012  GDate gdate;
1013  GList *schedule = NULL;
1014 
1015  gnc_frequency_save_to_recurrence (sxed->gncfreq, &schedule, &gdate);
1016  gnc_sx_set_schedule (sxed->sx, schedule);
1017  {
1018  gchar *recurrence_str = recurrenceListToCompactString (schedule);
1019  DEBUG ("recurrences parsed [%s]", recurrence_str);
1020  g_free (recurrence_str);
1021  }
1022 
1023  /* now that we have it, set the start date */
1024  xaccSchedXactionSetStartDate (sxed->sx, &gdate);
1025  }
1026 
1027  gnc_sx_commit_edit (sxed->sx);
1028 }
1029 
1030 static void
1031 update_sensitivity (GncSxEditorDialog *sxed)
1032 {
1033  gboolean enabled = gtk_toggle_button_get_active (sxed->enabledOpt);
1034  gboolean autocreate = gtk_toggle_button_get_active (sxed->autocreateOpt);
1035  gboolean advance = gtk_toggle_button_get_active (sxed->advanceOpt);
1036  gboolean remind = gtk_toggle_button_get_active (sxed->remindOpt);
1037  gboolean type_date = (sxed->end_type == END_DATE);
1038  gboolean type_occur = (sxed->end_type == END_OCCUR);
1039 
1040  gnc_suspend_gui_refresh ();
1041 
1042  gtk_widget_set_sensitive (GTK_WIDGET (sxed->autocreateOpt), enabled);
1043  gtk_widget_set_sensitive (GTK_WIDGET (sxed->notifyOpt), enabled && autocreate);
1044 
1045  gtk_widget_set_sensitive (GTK_WIDGET (sxed->advanceOpt), enabled);
1046  gtk_widget_set_sensitive (GTK_WIDGET (sxed->advanceSpin), enabled && advance);
1047 
1048  gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindOpt), enabled);
1049  gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindSpin), enabled && remind);
1050 
1051  gtk_widget_set_sensitive (GTK_WIDGET (sxed->optEndNone), enabled);
1052  gtk_widget_set_sensitive (GTK_WIDGET (sxed->optEndDate), enabled);
1053  gtk_widget_set_sensitive (GTK_WIDGET (sxed->optEndCount), enabled);
1054 
1055  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endDateEntry), enabled && type_date);
1056  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endCountSpin), enabled && type_occur);
1057  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endRemainSpin), enabled && type_occur);
1058 
1059  gtk_widget_set_sensitive (gtk_notebook_get_nth_page (sxed->notebook, 1), enabled);
1060  gtk_widget_set_sensitive (gtk_notebook_get_nth_page (sxed->notebook, 2), enabled);
1061 
1062  gnc_resume_gui_refresh ();
1063 }
1064 
1065 static void
1066 enabled_toggled_cb (GtkToggleButton *o, GncSxEditorDialog *sxed)
1067 {
1068  update_sensitivity (sxed);
1069 }
1070 
1071 static void
1072 autocreate_toggled_cb (GtkToggleButton *o, GncSxEditorDialog *sxed)
1073 {
1074  update_sensitivity (sxed);
1075 }
1076 
1077 static void
1078 advance_toggled_cb (GtkButton *o, GncSxEditorDialog *sxed)
1079 {
1080  update_sensitivity (sxed);
1081 }
1082 
1083 static void
1084 remind_toggled_cb (GtkButton *o, GncSxEditorDialog *sxed)
1085 {
1086  update_sensitivity (sxed);
1087 }
1088 
1089 
1090 /* Local destruction of dialog */
1091 static void
1092 scheduledxaction_editor_dialog_destroy (GtkWidget *object, gpointer data)
1093 {
1094  GncSxEditorDialog *sxed = data;
1095 
1096  if (sxed == NULL)
1097  return;
1098 
1099  gnc_unregister_gui_component_by_data
1100  (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS, sxed);
1101 
1102  gnc_embedded_window_close_page (sxed->embed_window, sxed->plugin_page);
1103  gtk_widget_destroy (GTK_WIDGET (sxed->embed_window));
1104  sxed->embed_window = NULL;
1105  sxed->plugin_page = NULL;
1106  sxed->ledger = NULL;
1107 
1108  g_free (sxed->sxGUIDstr);
1109  sxed->sxGUIDstr = NULL;
1110 
1111  if (sxed->newsxP)
1112  {
1113  /* FIXME: WTF???
1114  *
1115  * "WTF" explanation: in the "new" click from the caller, we
1116  * set this flag. When "ok" is pressed on the dialog, we set
1117  * this flag to false, and thus leave the SX live. If
1118  * "Cancel" is clicked, the flag will still be true, and this
1119  * SX will be cleaned, here. -- jsled
1120  */
1121  gnc_sx_begin_edit (sxed->sx);
1122  xaccSchedXactionDestroy (sxed->sx);
1123  }
1124  sxed->sx = NULL;
1125 
1126  g_free (sxed);
1127 }
1128 
1129 
1130 static
1131 gboolean
1132 sxed_delete_event (GtkWidget *widget, GdkEvent *event, gpointer ud)
1133 {
1134  GncSxEditorDialog *sxed = (GncSxEditorDialog*)ud;
1135 
1136  /* We've already processed the SX, likely because of "ok" being
1137  * clicked. */
1138  if (sxed->sx == NULL)
1139  {
1140  return FALSE;
1141  }
1142 
1143  if (!sxed_confirmed_cancel (sxed))
1144  {
1145  return TRUE;
1146  }
1147  return FALSE;
1148 }
1149 
1150 static gboolean
1151 focus_idle_callback(gpointer user_data)
1152 {
1153  GNCLedgerDisplay *ledger_display = (GNCLedgerDisplay *)user_data;
1154 
1155  if (ledger_display)
1156  gnc_ledger_display_refresh(ledger_display);
1157 
1158  return FALSE;
1159 }
1160 
1161 static void
1162 on_notebook_switch_page(GtkNotebook *notebook, GtkWidget *page,
1163  guint page_num, gpointer user_data)
1164 {
1165  GtkWidget *current_page = gtk_notebook_get_nth_page(notebook, page_num);
1166  if (current_page && page_num == 2)
1167  {
1168  GncSxEditorDialog *sxed = (GncSxEditorDialog *)user_data;
1169 
1170  // Wait until Gtk is idle to refresh the display.
1171  g_idle_add (focus_idle_callback, sxed->ledger);
1172  }
1173 }
1174 
1175 /*************************************
1176  * Create the Schedule Editor Dialog *
1177  ************************************/
1178 GncSxEditorDialog *
1179 gnc_ui_scheduled_xaction_editor_dialog_create (GtkWindow *parent,
1180  SchedXaction *sx, gboolean newSX)
1181 {
1182  GncSxEditorDialog *sxed;
1183  GtkBuilder *builder;
1184  GtkWidget *button;
1185  int i;
1186  int id;
1187  GList *dlgExists = NULL;
1188 
1189  static struct widgetSignalCallback
1190  {
1191  char *name;
1192  char *signal;
1193  void (*fn)();
1194  gpointer objectData;
1195  } widgets[] =
1196  {
1197  { "ok_button", "clicked", editor_ok_button_clicked_cb, NULL },
1198  { "cancel_button", "clicked", editor_cancel_button_clicked_cb, NULL },
1199  { "help_button", "clicked", editor_help_button_clicked_cb, NULL },
1200  { "rb_noend", "toggled", endgroup_rb_toggled_cb, GINT_TO_POINTER (END_NEVER_OPTION) },
1201  { "rb_enddate", "toggled", endgroup_rb_toggled_cb, GINT_TO_POINTER (END_DATE_OPTION) },
1202  { "rb_num_occur", "toggled", endgroup_rb_toggled_cb, GINT_TO_POINTER (NUM_OCCUR_OPTION) },
1203  { "remain_spin" , "value-changed", sxed_excal_update_adapt_cb, NULL },
1204  { "enabled_opt", "toggled", enabled_toggled_cb, NULL },
1205  { "autocreate_opt", "toggled", autocreate_toggled_cb, NULL },
1206  { "advance_opt", "toggled", advance_toggled_cb, NULL },
1207  { "remind_opt", "toggled", remind_toggled_cb, NULL },
1208  { NULL, NULL, NULL, NULL }
1209  };
1210 
1211  dlgExists = gnc_find_gui_components (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS,
1212  editor_component_sx_equality,
1213  sx);
1214  if (dlgExists)
1215  {
1216  DEBUG ("dialog already exists; using that one.");
1217  sxed = (GncSxEditorDialog*)dlgExists->data;
1218  gtk_window_present (GTK_WINDOW (sxed->dialog));
1219  g_list_free (dlgExists);
1220  return sxed;
1221  }
1222 
1223  sxed = g_new0(GncSxEditorDialog, 1);
1224 
1225  sxed->sx = sx;
1226  sxed->newsxP = newSX;
1227 
1228  /* Load up Glade file */
1229  builder = gtk_builder_new ();
1230  gnc_builder_add_from_file (builder, "dialog-sx.glade", "advance_days_adj");
1231  gnc_builder_add_from_file (builder, "dialog-sx.glade", "remind_days_adj");
1232  gnc_builder_add_from_file (builder, "dialog-sx.glade", "end_spin_adj");
1233  gnc_builder_add_from_file (builder, "dialog-sx.glade", "remain_spin_adj");
1234  gnc_builder_add_from_file (builder, "dialog-sx.glade", "scheduled_transaction_editor_dialog");
1235 
1236  sxed->builder = builder;
1237 
1238  /* Connect the Widgets */
1239  sxed->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "scheduled_transaction_editor_dialog"));
1240  sxed->notebook = GTK_NOTEBOOK (gtk_builder_get_object (builder, "editor_notebook"));
1241  sxed->nameEntry = GTK_ENTRY (gtk_builder_get_object (builder, "sxe_name"));
1242  sxed->enabledOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "enabled_opt"));
1243  sxed->autocreateOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "autocreate_opt"));
1244  sxed->notifyOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "notify_opt"));
1245  sxed->advanceOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "advance_opt"));
1246  sxed->advanceSpin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "advance_days"));
1247  sxed->remindOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "remind_opt"));
1248  sxed->remindSpin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "remind_days"));
1249  sxed->lastOccurLabel = GTK_LABEL (gtk_builder_get_object (builder, "last_occur_label"));
1250  sxed->optEndNone = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_noend"));
1251  sxed->optEndDate = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_enddate"));
1252  sxed->optEndCount = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_num_occur"));
1253  sxed->endCountSpin = GTK_ENTRY (gtk_builder_get_object (builder, "end_spin"));
1254  sxed->endRemainSpin = GTK_ENTRY (gtk_builder_get_object (builder, "remain_spin"));
1255 
1256  // Set the name of this dialog so it can be easily manipulated with css
1257  gtk_widget_set_name (GTK_WIDGET (sxed->dialog), "gnc-id-sx-editor");
1258  gnc_widget_style_context_add_class (GTK_WIDGET (sxed->dialog), "gnc-class-sx");
1259 
1260  gtk_window_set_transient_for (GTK_WINDOW (sxed->dialog), parent);
1261 
1262  /* Setup the end-date GNC widget */
1263  {
1264  GtkWidget *endDateBox = GTK_WIDGET (gtk_builder_get_object (builder, "editor_end_date_box"));
1265  sxed->endDateEntry = GNC_DATE_EDIT (gnc_date_edit_new (gnc_time (NULL), FALSE, FALSE));
1266  gtk_widget_show (GTK_WIDGET (sxed->endDateEntry));
1267  g_signal_connect (sxed->endDateEntry, "date-changed",
1268  G_CALLBACK (sxed_excal_update_adapt_cb), sxed);
1269  gtk_box_pack_start (GTK_BOX (endDateBox), GTK_WIDGET (sxed->endDateEntry),
1270  TRUE, TRUE, 0);
1271  }
1272 
1273  id = gnc_register_gui_component (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS,
1274  NULL, /* no refresh handler */
1275  sxed_close_handler,
1276  sxed);
1277  // This ensure this dialog is closed when the session is closed.
1278  gnc_gui_component_set_session (id, gnc_get_current_session ());
1279 
1280  g_signal_connect (sxed->dialog, "delete_event",
1281  G_CALLBACK (sxed_delete_event), sxed);
1282  g_signal_connect (sxed->dialog, "destroy",
1283  G_CALLBACK (scheduledxaction_editor_dialog_destroy),
1284  sxed);
1285  g_signal_connect (sxed->notebook, "switch-page",
1286  G_CALLBACK(on_notebook_switch_page),
1287  sxed);
1288 
1289  for (i = 0; widgets[i].name; i++)
1290  {
1291  button = GTK_WIDGET (gtk_builder_get_object (builder, widgets[i].name));
1292  if (widgets[i].objectData)
1293  {
1294  g_object_set_data (G_OBJECT (button), "whichOneAmI",
1295  widgets[i].objectData);
1296  }
1297  g_signal_connect (button, widgets[i].signal,
1298  G_CALLBACK (widgets[i].fn), sxed);
1299  }
1300 
1301  /* Set sensitivity settings */
1302  gtk_widget_set_sensitive (GTK_WIDGET (sxed->notifyOpt), FALSE);
1303  gtk_widget_set_sensitive (GTK_WIDGET (sxed->advanceSpin), FALSE);
1304  gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindSpin), FALSE);
1305  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endCountSpin), FALSE);
1306  gtk_widget_set_sensitive (GTK_WIDGET (sxed->endRemainSpin), FALSE);
1307  gtk_editable_set_editable (GTK_EDITABLE (sxed->advanceSpin), TRUE);
1308  gtk_editable_set_editable (GTK_EDITABLE (sxed->remindSpin), TRUE);
1309 
1310  /* Allow resize */
1311  gtk_window_set_resizable (GTK_WINDOW (sxed->dialog), TRUE);
1312  gnc_restore_window_size (GNC_PREFS_GROUP_SXED, GTK_WINDOW (sxed->dialog), parent);
1313 
1314  /* create the frequency-selection widget and example [dense-]calendar. */
1315  schedXact_editor_create_freq_sel (sxed);
1316 
1317  /* create the template-transaction ledger window */
1318  schedXact_editor_create_ledger (sxed);
1319 
1320  /* populate */
1321  schedXact_editor_populate (sxed);
1322 
1323  /* Do not call show_all here */
1324  gtk_widget_show (sxed->dialog);
1325  gtk_notebook_set_current_page (GTK_NOTEBOOK (sxed->notebook), 0);
1326 
1327  /* Refresh the cal and the ledger */
1328  gtk_widget_queue_resize (GTK_WIDGET (sxed->example_cal));
1329 
1330  gnc_ledger_display_refresh (sxed->ledger);
1331 
1332  /* Move keyboard focus to the name entry */
1333  gtk_widget_grab_focus (GTK_WIDGET (sxed->nameEntry));
1334 
1335  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, sxed);
1336  g_object_unref (G_OBJECT (builder));
1337 
1338  return sxed;
1339 }
1340 
1341 
1342 static void
1343 schedXact_editor_create_freq_sel (GncSxEditorDialog *sxed)
1344 {
1345  GtkBox *b;
1346  GtkWidget *example_cal_scrolled_win = NULL;
1347 
1348  b = GTK_BOX (gtk_builder_get_object (sxed->builder, "gncfreq_hbox"));
1349 
1350  sxed->gncfreq =
1351  GNC_FREQUENCY (gnc_frequency_new_from_recurrence (gnc_sx_get_schedule (sxed->sx),
1352  xaccSchedXactionGetStartDate (sxed->sx)));
1353  g_assert (sxed->gncfreq);
1354  g_signal_connect (sxed->gncfreq, "changed",
1355  G_CALLBACK (gnc_sxed_freq_changed),
1356  sxed);
1357 
1358  gtk_box_pack_start (GTK_BOX (b), GTK_WIDGET (sxed->gncfreq), TRUE, TRUE, 0);
1359 
1360  b = GTK_BOX (gtk_builder_get_object (sxed->builder, "example_cal_hbox"));
1361 
1362  example_cal_scrolled_win = gtk_scrolled_window_new (NULL, NULL);
1363  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (example_cal_scrolled_win),
1364  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1365  gtk_box_pack_start (GTK_BOX (b), example_cal_scrolled_win, TRUE, TRUE, 0);
1366 
1367  sxed->dense_cal_model = gnc_dense_cal_store_new (EX_CAL_NUM_MONTHS * 31);
1368  sxed->example_cal = GNC_DENSE_CAL(gnc_dense_cal_new_with_model (GTK_WINDOW(sxed->dialog),
1369  GNC_DENSE_CAL_MODEL(sxed->dense_cal_model)));
1370  g_assert (sxed->example_cal);
1371  gnc_dense_cal_set_num_months (sxed->example_cal, EX_CAL_NUM_MONTHS);
1372  gnc_dense_cal_set_months_per_col (sxed->example_cal, EX_CAL_MO_PER_COL);
1373  gtk_container_add (GTK_CONTAINER (example_cal_scrolled_win), GTK_WIDGET (sxed->example_cal));
1374 
1375 
1376  gtk_widget_show_all (example_cal_scrolled_win);
1377 }
1378 
1379 
1380 static void
1381 schedXact_editor_create_ledger (GncSxEditorDialog *sxed)
1382 {
1383  SplitRegister *splitreg;
1384  GtkWidget *main_vbox;
1385 
1386  /* Create the ledger */
1387  sxed->sxGUIDstr = guid_to_string (xaccSchedXactionGetGUID (sxed->sx));
1388  sxed->ledger = gnc_ledger_display_template_gl (sxed->sxGUIDstr);
1389  splitreg = gnc_ledger_display_get_split_register (sxed->ledger);
1390 
1391  /* First the embedded window */
1392  main_vbox = GTK_WIDGET (gtk_builder_get_object (sxed->builder, "register_vbox"));
1393  sxed->embed_window =
1394  gnc_embedded_window_new ("embedded-win",
1395  gnc_sxed_menu_entries,
1396  gnc_sxed_menu_n_entries,
1397  "gnc-embedded-register-window.ui",
1398  sxed->dialog,
1399  FALSE, /* no accelerators */
1400  sxed);
1401  gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (sxed->embed_window),
1402  TRUE, TRUE, 0);
1403 
1404  /* Now create the register plugin page. */
1405  sxed->plugin_page = gnc_plugin_page_register_new_ledger (sxed->ledger);
1406 
1407  gnc_plugin_page_merge_actions (sxed->plugin_page);
1408 
1409  gtk_widget_insert_action_group (GTK_WIDGET(sxed->embed_window),
1411  G_ACTION_GROUP(gnc_plugin_page_get_action_group (sxed->plugin_page)));
1412 
1413  gnc_plugin_page_register_set_options (sxed->plugin_page,
1414  NUM_LEDGER_LINES_DEFAULT, FALSE);
1415  gnc_embedded_window_open_page (sxed->embed_window, sxed->plugin_page);
1416 
1417  /* configure... */
1418  /* use double-line, so scheduled transaction Notes can be edited */
1419  gnc_split_register_config (splitreg,
1420  splitreg->type, splitreg->style,
1421  TRUE);
1422  gnc_split_register_set_auto_complete (splitreg, FALSE);
1423 
1424  /* don't show present/future divider [by definition, not necessary] */
1425  gnc_split_register_show_present_divider (splitreg, FALSE);
1426 }
1427 
1428 
1429 static void
1430 schedXact_editor_populate (GncSxEditorDialog *sxed)
1431 {
1432  char *name;
1433  time64 tmpDate;
1434  SplitRegister *splitReg;
1435  const GDate *gd;
1436  gint daysInAdvance;
1437  gboolean enabledState, autoCreateState, notifyState;
1438 
1439  name = xaccSchedXactionGetName (sxed->sx);
1440  if (name)
1441  {
1442  gtk_entry_set_text (sxed->nameEntry, name);
1443  }
1444  {
1445  gd = xaccSchedXactionGetLastOccurDate (sxed->sx);
1446  if (g_date_valid (gd))
1447  {
1448  gchar dateBuf [MAX_DATE_LENGTH+1];
1449  qof_print_gdate (dateBuf, MAX_DATE_LENGTH, gd);
1450  gtk_label_set_text (sxed->lastOccurLabel, dateBuf);
1451  }
1452  else
1453  {
1454  gtk_label_set_text (sxed->lastOccurLabel, _("(never)"));
1455  }
1456  gd = NULL;
1457  }
1458 
1459  gd = xaccSchedXactionGetEndDate (sxed->sx);
1460  if (g_date_valid (gd))
1461  {
1462  gtk_toggle_button_set_active (sxed->optEndDate, TRUE);
1463  tmpDate = gnc_time64_get_day_start_gdate (gd);
1464  gnc_date_edit_set_time (sxed->endDateEntry, tmpDate);
1465 
1466  set_endgroup_toggle_states (sxed, END_DATE);
1467  }
1468  else if (xaccSchedXactionHasOccurDef (sxed->sx))
1469  {
1470  gint numOccur = xaccSchedXactionGetNumOccur (sxed->sx);
1471  gint numRemain = xaccSchedXactionGetRemOccur (sxed->sx);
1472 
1473  gtk_toggle_button_set_active (sxed->optEndCount, TRUE);
1474 
1475  gtk_spin_button_set_value (GTK_SPIN_BUTTON (sxed->endCountSpin), numOccur);
1476  gtk_spin_button_set_value (GTK_SPIN_BUTTON (sxed->endRemainSpin), numRemain);
1477 
1478  set_endgroup_toggle_states (sxed, END_OCCUR);
1479  }
1480  else
1481  {
1482  gtk_toggle_button_set_active (sxed->optEndNone, TRUE);
1483  set_endgroup_toggle_states (sxed, END_NEVER);
1484  }
1485 
1486  enabledState = xaccSchedXactionGetEnabled (sxed->sx);
1487  gtk_toggle_button_set_active (sxed->enabledOpt, enabledState);
1488 
1489  /* Do auto-create/notify setup */
1490  if (sxed->newsxP)
1491  {
1492  autoCreateState =
1493  gnc_prefs_get_bool (GNC_PREFS_GROUP_SXED, GNC_PREF_CREATE_AUTO);
1494  notifyState =
1495  gnc_prefs_get_bool (GNC_PREFS_GROUP_SXED, GNC_PREF_NOTIFY);
1496  }
1497  else
1498  {
1499  xaccSchedXactionGetAutoCreate (sxed->sx,
1500  &autoCreateState,
1501  &notifyState);
1502  }
1503  gtk_toggle_button_set_active (sxed->autocreateOpt, autoCreateState);
1504  if (!autoCreateState)
1505  {
1506  notifyState = FALSE;
1507  }
1508  gtk_toggle_button_set_active (sxed->notifyOpt, notifyState);
1509 
1510  /* Do days-in-advance-to-create widget[s] setup. */
1511  if (sxed->newsxP)
1512  {
1513  daysInAdvance =
1514  gnc_prefs_get_float (GNC_PREFS_GROUP_SXED, GNC_PREF_CREATE_DAYS);
1515  }
1516  else
1517  {
1518  daysInAdvance =
1519  xaccSchedXactionGetAdvanceCreation (sxed->sx);
1520  }
1521  if (daysInAdvance != 0)
1522  {
1523  gtk_toggle_button_set_active (sxed->advanceOpt, TRUE);
1524  gtk_spin_button_set_value (sxed->advanceSpin,
1525  (gfloat)daysInAdvance);
1526  }
1527 
1528  /* Do days-in-advance-to-remind widget[s] setup. */
1529  if (sxed->newsxP)
1530  {
1531  daysInAdvance =
1532  gnc_prefs_get_float (GNC_PREFS_GROUP_SXED, GNC_PREF_REMIND_DAYS);
1533  }
1534  else
1535  {
1536  daysInAdvance =
1537  xaccSchedXactionGetAdvanceReminder (sxed->sx);
1538  }
1539  if (daysInAdvance != 0)
1540  {
1541  gtk_toggle_button_set_active (sxed->remindOpt, TRUE);
1542  gtk_spin_button_set_value (sxed->remindSpin,
1543  (gfloat)daysInAdvance);
1544  }
1545 
1546  if (sxed->newsxP)
1547  {
1548  gnc_sx_set_instance_count (sxed->sx, 1);
1549  }
1550 
1551  /* populate the ledger */
1552  {
1553  /* create the split list */
1554  GList *splitList = xaccSchedXactionGetSplits (sxed->sx);
1555  if (splitList)
1556  {
1557  splitReg = gnc_ledger_display_get_split_register (sxed->ledger);
1558  gnc_split_register_load (splitReg, splitList, NULL, NULL);
1559  } /* otherwise, use the existing stuff. */
1560  g_list_free (splitList);
1561  }
1562 
1563  /* Update the example cal */
1564  gnc_sxed_update_cal (sxed);
1565 }
1566 
1567 
1568 static void
1569 set_endgroup_toggle_states (GncSxEditorDialog *sxed, EndType type)
1570 {
1571  sxed->end_type = type;
1572  update_sensitivity (sxed);
1573 }
1574 
1575 
1576 static void
1577 endgroup_rb_toggled_cb (GtkButton *b, gpointer d)
1578 {
1579  /* figure out which one */
1580  GncSxEditorDialog *sxed;
1581  gint id;
1582 
1583  sxed = (GncSxEditorDialog*)d;
1584  id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (b), "whichOneAmI"));
1585 
1586  switch (id)
1587  {
1588  case END_NEVER_OPTION:
1589  set_endgroup_toggle_states (sxed, END_NEVER);
1590  break;
1591  case END_DATE_OPTION:
1592  set_endgroup_toggle_states (sxed, END_DATE);
1593  break;
1594  case NUM_OCCUR_OPTION:
1595  set_endgroup_toggle_states (sxed, END_OCCUR);
1596  break;
1597  default:
1598  g_critical ("Unknown id %d", id);
1599  break;
1600  }
1601  gnc_sxed_update_cal (sxed);
1602 }
1603 
1604 
1605 /********************************************************************\
1606  * gnc_register_check_close *
1607  * *
1608  * Args: regData - the data struct for this register *
1609  * Return: none *
1610 \********************************************************************/
1611 static void
1612 gnc_sxed_reg_check_close (GncSxEditorDialog *sxed)
1613 {
1614  gboolean pending_changes;
1615  SplitRegister *reg;
1616  const char *message =
1617  _("The current template transaction "
1618  "has been changed. "
1619  "Would you like to record the changes?");
1620 
1621  reg = gnc_ledger_display_get_split_register (sxed->ledger);
1622  pending_changes = gnc_split_register_changed (reg);
1623  if (!pending_changes)
1624  {
1625  return;
1626  }
1627 
1628  if (gnc_verify_dialog (GTK_WINDOW (sxed->dialog), TRUE, "%s", message))
1629  {
1630  if (!gnc_split_register_save (reg, TRUE))
1631  return;
1632 
1634  }
1635  else
1636  {
1638  }
1639 }
1640 
1641 
1642 static gboolean
1643 editor_component_sx_equality (gpointer find_data,
1644  gpointer user_data)
1645 {
1646  return ((SchedXaction*)find_data
1647  == ((GncSxEditorDialog*)user_data)->sx);
1648 }
1649 
1650 static void
1651 gnc_sxed_update_cal (GncSxEditorDialog *sxed)
1652 {
1653  GList *recurrences = NULL;
1654  GDate start_date, first_date;
1655 
1656  g_date_clear (&start_date, 1);
1657 
1658  gnc_frequency_save_to_recurrence (sxed->gncfreq, &recurrences, &start_date);
1659  recurrenceListNextInstance (recurrences, &start_date, &first_date);
1660 
1661  /* Deal with the fact that this SX may have been run before [the
1662  * calendar should only show upcoming instances]... */
1663  {
1664  const GDate *last_sx_inst;
1665 
1666  last_sx_inst = xaccSchedXactionGetLastOccurDate (sxed->sx);
1667  if (g_date_valid (last_sx_inst)
1668  && g_date_valid (&first_date)
1669  && g_date_compare (last_sx_inst, &first_date) > 0)
1670  {
1671  /* last occurrence will be passed as initial date to update store
1672  * later on as well, but only if it's past first_date */
1673  start_date = *last_sx_inst;
1674  recurrenceListNextInstance (recurrences, &start_date, &first_date);
1675  }
1676  else
1677  /* move one day back so the store can get the proper first recurrence. */
1678  g_date_subtract_days (&start_date, 1);
1679 
1680  }
1681 
1682  if (!g_date_valid (&first_date))
1683  {
1684  /* Note: There are no recurrences for PERIOD_NONE and on initial setting
1685  * of PERIOD_WEEKLY (no days set), so still need to 'do nothing' */
1686  gboolean do_nothing = TRUE;
1687  if (recurrences)
1688  {
1689  Recurrence *r = g_list_nth_data (recurrences, 0);
1690  if (r && r->ptype == PERIOD_ONCE)
1691  do_nothing = FALSE;
1692  }
1693  /* Nothing to do. */
1694  if (do_nothing)
1695  {
1696  gnc_dense_cal_store_clear (sxed->dense_cal_model);
1697  goto cleanup;
1698  }
1699  }
1700 
1701  gnc_dense_cal_store_update_name (sxed->dense_cal_model, xaccSchedXactionGetName (sxed->sx));
1702  {
1703  gchar *schedule_desc = recurrenceListToCompactString (recurrences);
1704  gnc_dense_cal_store_update_info (sxed->dense_cal_model, schedule_desc);
1705  g_free (schedule_desc);
1706  }
1707 
1708  //gnc_dense_cal_set_month (sxed->example_cal, g_date_get_month (&first_date));
1709  //gnc_dense_cal_set_year (sxed->example_cal, g_date_get_year (&first_date));
1710 
1711  /* figure out the end restriction */
1712  if (gtk_toggle_button_get_active (sxed->optEndDate))
1713  {
1714  GDate end_date;
1715  g_date_clear (&end_date, 1);
1716  gnc_gdate_set_time64 (&end_date, gnc_date_edit_get_date (sxed->endDateEntry));
1717  gnc_dense_cal_store_update_recurrences_date_end (sxed->dense_cal_model, &start_date, recurrences, &end_date);
1718  }
1719  else if (gtk_toggle_button_get_active (sxed->optEndNone))
1720  {
1721  gnc_dense_cal_store_update_recurrences_no_end (sxed->dense_cal_model, &start_date, recurrences);
1722  }
1723  else if (gtk_toggle_button_get_active (sxed->optEndCount))
1724  {
1725  gint num_remain
1726  = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
1727  gnc_dense_cal_store_update_recurrences_count_end (sxed->dense_cal_model, &start_date, recurrences, num_remain);
1728  }
1729  else
1730  {
1731  g_error ("unknown end condition");
1732  }
1733 
1734  cleanup:
1735  recurrenceListFree (&recurrences);
1736 }
1737 
1738 
1739 static void
1740 gnc_sxed_freq_changed (GncFrequency *gf, gpointer ud)
1741 {
1742  gnc_sxed_update_cal ((GncSxEditorDialog*)ud);
1743 }
1744 
1745 
1746 static void
1747 sxed_excal_update_adapt_cb (GtkWidget *o, gpointer ud)
1748 {
1749  gnc_sxed_update_cal ((GncSxEditorDialog*)ud);
1750 }
1751 
1752 
1753 void
1754 on_sx_check_toggled_cb (GtkWidget *togglebutton, gpointer user_data)
1755 {
1756  GtkWidget *widget_auto;
1757  GtkWidget *widget_notify;
1758  GHashTable *table;
1759 
1760  PINFO ("Togglebutton is %p and user_data is %p", togglebutton, user_data);
1761  PINFO ("Togglebutton builder name is %s", gtk_buildable_get_name (GTK_BUILDABLE (togglebutton)));
1762 
1763  /* We need to use the hash table to find the required widget to activate. */
1764  table = g_object_get_data (G_OBJECT (user_data), "prefs_widget_hash");
1765 
1766  /* "Auto-create" enables "notify before creation" setting */
1767  widget_auto = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_SXED "/" GNC_PREF_CREATE_AUTO);
1768  widget_notify = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_SXED "/" GNC_PREF_NOTIFY);
1769 
1770  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget_auto)))
1771  gtk_widget_set_sensitive (widget_notify, TRUE);
1772  else
1773  gtk_widget_set_sensitive (widget_notify, FALSE);
1774 
1775  /* "Run when opened" enables "show notification window" setting */
1776  widget_auto = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_STARTUP "/" GNC_PREF_RUN_AT_FOPEN);
1777  widget_notify = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_STARTUP "/" GNC_PREF_SHOW_AT_FOPEN);
1778 
1779  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget_auto)))
1780  gtk_widget_set_sensitive (widget_notify, TRUE);
1781  else
1782  gtk_widget_set_sensitive (widget_notify, FALSE);
1783 }
1784 
1785 
1786 /* ------------------------------------------------------------ */
1787 /* sx app engine; move to somewhere appropriate. :/ */
1788 
1789 typedef struct _acct_deletion_handler_data
1790 {
1791  GList *affected_sxes;
1792  GtkWidget *dialog;
1793  GtkWindow *parent;
1795 
1796 
1797 static void
1798 _open_editors (GtkDialog *dialog, gint response_code, gpointer data)
1799 {
1801  gtk_widget_hide (adhd->dialog);
1802  {
1803  GList *sx_iter;
1804  for (sx_iter = adhd->affected_sxes; sx_iter; sx_iter = sx_iter->next)
1805  {
1806  gnc_ui_scheduled_xaction_editor_dialog_create (GTK_WINDOW (adhd->parent),
1807  (SchedXaction*)sx_iter->data, FALSE);
1808  }
1809  }
1810  g_list_free (adhd->affected_sxes);
1811  gtk_widget_destroy (GTK_WIDGET (adhd->dialog));
1812  g_free (adhd);
1813 }
1814 
1815 
1816 static void
1817 _sx_engine_event_handler (QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data)
1818 {
1819  Account *acct;
1820  QofBook *book;
1821  GList *affected_sxes;
1822 
1823  if (!(event_type & QOF_EVENT_DESTROY))
1824  return;
1825  if (!GNC_IS_ACCOUNT (ent))
1826  return;
1827  acct = GNC_ACCOUNT (ent);
1828  book = qof_instance_get_book (QOF_INSTANCE (acct));
1829  affected_sxes = gnc_sx_get_sxes_referencing_account (book, acct);
1830 
1831  if (!gnc_list_length_cmp (affected_sxes, 0))
1832  return;
1833 
1834  {
1835  GList *sx_iter;
1837  GtkBuilder *builder;
1838  GtkWidget *dialog;
1839  GtkWindow *parent;
1840  GtkListStore *name_list;
1841  GtkTreeView *list;
1842  GtkTreeViewColumn *name_column;
1843  GtkCellRenderer *renderer;
1844 
1845  builder = gtk_builder_new ();
1846  gnc_builder_add_from_file (builder, "dialog-sx.glade", "account_deletion_dialog");
1847 
1848  dialog = GTK_WIDGET (gtk_builder_get_object (builder, "account_deletion_dialog"));
1849  parent = gnc_ui_get_main_window (NULL);
1850 
1851  gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
1852 
1853  list = GTK_TREE_VIEW (gtk_builder_get_object (builder, "sx_list"));
1854 
1855  // Set grid lines option to preference
1856  gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (list), gnc_tree_view_get_grid_lines_pref ());
1857 
1859  data->dialog = dialog;
1860  data->parent = parent;
1861  data->affected_sxes = affected_sxes;
1862  name_list = gtk_list_store_new (1, G_TYPE_STRING);
1863  for (sx_iter = affected_sxes; sx_iter; sx_iter = sx_iter->next)
1864  {
1865  SchedXaction *sx;
1866  GtkTreeIter iter;
1867  gchar *sx_name;
1868 
1869  sx = (SchedXaction*)sx_iter->data;
1870  sx_name = xaccSchedXactionGetName (sx);
1871  gtk_list_store_append (name_list, &iter);
1872  gtk_list_store_set (name_list, &iter, 0, sx_name, -1);
1873  }
1874  gtk_tree_view_set_model (list, GTK_TREE_MODEL (name_list));
1875  g_object_unref (G_OBJECT (name_list));
1876 
1877  renderer = gtk_cell_renderer_text_new ();
1878  name_column = gtk_tree_view_column_new_with_attributes (_("Name"),
1879  renderer,
1880  "text", 0, NULL);
1881  gtk_tree_view_append_column (list, name_column);
1882 
1883  g_signal_connect (G_OBJECT (dialog), "response",
1884  G_CALLBACK (_open_editors), data);
1885 
1886  gtk_widget_show_all (GTK_WIDGET (dialog));
1887  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, data);
1888  g_object_unref (G_OBJECT (builder));
1889  }
1890 }
1891 
1892 
1893 void
1894 gnc_ui_sx_initialize (void)
1895 {
1896  _sx_engine_event_handler_id = qof_event_register_handler (_sx_engine_event_handler, NULL);
1897 
1898  gnc_hook_add_dangler (HOOK_BOOK_OPENED,
1899  (GFunc)gnc_sx_sxsincelast_book_opened, NULL, NULL);
1900 
1901  /* Add page to preferences page for Scheduled Transactions */
1902  /* The parameters are; glade file, items to add from glade file - last being the dialog, preference tab name */
1903  gnc_preferences_add_page ("dialog-sx.glade",
1904  "create_days_adj,remind_days_adj,sx_prefs",
1905  _("Scheduled Transactions"));
1906 }
const GDate * xaccSchedXactionGetEndDate(const SchedXaction *sx)
Returns invalid date when there is no end-date specified.
gint xaccAccountForEachTransaction(const Account *acc, TransactionCallback proc, void *data)
The xaccAccountForEachTransaction() routine will traverse all of the transactions in account and call...
Definition: Account.cpp:5109
Public declarations for GncLedgerDisplay class.
void gnc_sx_set_schedule(SchedXaction *sx, GList *schedule)
void gnc_ledger_display_refresh(GNCLedgerDisplay *ld)
redisplay/redraw only the indicated window.
void gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num)
Sets the instance count to something other than the default.
GList * gnc_sx_get_schedule(const SchedXaction *sx)
void gnc_preferences_add_page(const gchar *filename, const gchar *widgetname, const gchar *tabname)
This function adds a full page of preferences to the preferences dialog.
gchar * gnc_num_dbg_to_string(gnc_numeric n)
Convert to string.
void qof_instance_get(const QofInstance *inst, const gchar *first_prop,...)
Wrapper for g_object_get.
The instance data structure for a content plugin.
Date and Time handling routines.
gboolean gnc_split_register_save(SplitRegister *reg, gboolean do_commit)
Copy the contents of the current cursor to a split.
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
void xaccSchedXactionSetNumOccur(SchedXaction *sx, gint new_num)
Set to &#39;0&#39; to turn off number-of-occurrences definition.
void gnc_plugin_page_merge_actions(GncPluginPage *page)
Add the actions for a content page to the specified window.
void gnc_embedded_window_close_page(GncEmbeddedWindow *window, GncPluginPage *page)
Remove a data plugin page from a window.
STRUCTS.
size_t qof_print_gdate(char *buf, size_t bufflen, const GDate *gd)
Convenience; calls through to qof_print_date_dmy_buff().
Definition: gnc-date.cpp:598
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
gboolean gnc_commodity_equal(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equal.
void gnc_embedded_window_open_page(GncEmbeddedWindow *window, GncPluginPage *page)
Display a data plugin page in a window.
GSimpleActionGroup * gnc_plugin_page_get_action_group(GncPluginPage *page)
Retrieve the GSimpleActionGroup object associated with this page.
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a+b.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
GncEmbeddedWindow * gnc_embedded_window_new(const gchar *action_group_name, GActionEntry *action_entries, gint n_action_entries, const gchar *ui_filename, GtkWidget *enclosing_win, gboolean add_accelerators, gpointer user_data)
Create a new gnc embedded window plugin.
void gnc_split_register_load(SplitRegister *reg, GList *slist, GList *pre_filter_slist, Account *default_account)
Populates the rows of a register.
GList * gnc_sx_get_sxes_referencing_account(QofBook *book, Account *acct)
Definition: SX-book.cpp:365
GncPluginPage * gnc_plugin_page_register_new_ledger(GNCLedgerDisplay *ledger)
Create a new "register" plugin page, given a pointer to an already created ledger.
gint qof_event_register_handler(QofEventHandler handler, gpointer user_data)
Register a handler for events.
Definition: qofevent.cpp:73
void gnc_split_register_redraw(SplitRegister *reg)
Causes a redraw of the register window associated with reg.
Functions providing a register page for the GnuCash UI.
Account handling public routines.
Find the least common multiple of the arguments&#39; denominators and use that as the denominator of the ...
Definition: gnc-numeric.h:200
gint QofEventId
Define the type of events allowed.
Definition: qofevent.h:45
void gnc_gnome_help(GtkWindow *parent, const char *file_name, const char *anchor)
Launch the systems default help browser, gnome&#39;s yelp for linux, and open to a given link within a gi...
gboolean gnc_split_register_changed(SplitRegister *reg)
Returns TRUE if the register has changed cells.
Anchor Scheduled Transaction info in a book.
Functions for adding plugins to a GnuCash window.
void gnc_split_register_config(SplitRegister *reg, SplitRegisterType newtype, SplitRegisterStyle newstyle, gboolean use_double_line)
Sets a split register&#39;s type, style or line use.
const gchar * gnc_plugin_page_get_simple_action_group_name(GncPluginPage *page)
Retrieve the simple action group name associated with this plugin page.
const char * xaccTransGetDescription(const Transaction *trans)
Gets the transaction Description.
Public declarations of GnucashRegister class.
gnc_numeric gnc_numeric_abs(gnc_numeric a)
Returns a newly created gnc_numeric that is the absolute value of the given gnc_numeric value...
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition: gnc-date.h:108
Dialog for handling user preferences.
#define xaccSchedXactionGetGUID(X)
Definition: SchedXaction.h:319
All type declarations for the whole Gnucash engine.
int xaccSPrintAmount(char *bufp, gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
GLib helper routines.
Generic api to store and retrieve preferences.
void gnc_split_register_cancel_cursor_trans_changes(SplitRegister *reg)
Cancels any changes made to the current pending transaction, reloads the table from the engine...
GNCLedgerDisplay * gnc_ledger_display_template_gl(char *id)
Displays a template ledger.
void xaccSchedXactionSetName(SchedXaction *sx, const gchar *newName)
A copy of the name is made.
void gnc_gdate_set_time64(GDate *gd, time64 time)
Set a GDate to a time64.
Definition: gnc-date.cpp:1314
gchar * guid_to_string(const GncGUID *guid)
The guid_to_string() routine returns a null-terminated string encoding of the id. ...
Definition: guid.cpp:199
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3375
void gnc_split_register_set_auto_complete(SplitRegister *reg, gboolean do_auto_complete)
Sets whether a register uses auto-completion.
Functions that are supported by all types of windows.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:262
const char * xaccSplitGetMemo(const Split *split)
Returns the memo string.
Definition: gmock-Split.cpp:99
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
SplitRegister * gnc_ledger_display_get_split_register(GNCLedgerDisplay *ld)
return the split register associated with a ledger display
void gnc_split_register_show_present_divider(SplitRegister *reg, gboolean show_present)
If TRUE, visually indicate the demarcation between splits with post dates prior to the present...
gboolean xaccSchedXactionHasOccurDef(const SchedXaction *sx)
Returns true if the scheduled transaction has a defined number of occurrences, false if not...
Scheduled Transactions public handling routines.
gint gnc_list_length_cmp(const GList *list, size_t len)
Scans the GList elements the minimum number of iterations required to test it against a specified siz...
void xaccSchedXactionSetEndDate(SchedXaction *sx, const GDate *newEnd)
Set to an invalid GDate to turn off &#39;end-date&#39; definition.
#define GNC_DENOM_AUTO
Values that can be passed as the &#39;denom&#39; argument.
Definition: gnc-numeric.h:245
The type used to store guids in C.
Definition: guid.h:75
time64 gnc_time64_get_day_start_gdate(const GDate *date)
The gnc_time64_get_day_start() routine will take the given time in GLib GDate format and adjust it to...
Definition: gnc-date.cpp:1488
Implementations.
void xaccSchedXactionDestroy(SchedXaction *sx)
Cleans up and frees a SchedXaction and its associated data.
void gnc_plugin_page_register_set_options(GncPluginPage *plugin_page, gint lines_default, gboolean read_only)
Set various register options on a newly created "register" plugin page.
SplitList * xaccTransGetSplitList(const Transaction *trans)
The xaccTransGetSplitList() method returns a GList of the splits in a transaction.
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Get an float value from the preferences backend.
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id...
Definition: Account.cpp:2048