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 
37 #include <gtk/gtk.h>
38 #include <glib/gi18n.h>
39 #include <locale.h>
40 
41 #include "qof.h"
42 #include "Account.h"
43 #include "SchedXaction.h"
44 #include "SX-book.h"
45 #include "dialog-preferences.h"
46 #include "dialog-sx-editor.h"
47 #include "dialog-utils.h"
48 #include "gnc-component-manager.h"
49 #include "gnc-date.h"
50 #include "gnc-date-edit.h"
51 #include "gnc-dense-cal.h"
52 #include "gnc-dense-cal-store.h"
53 #include "gnc-embedded-window.h"
54 #include "gnc-engine.h"
55 #include "gnc-frequency.h"
56 #include "gnc-gui-query.h"
57 #include "gnc-hooks.h"
58 #include "gnc-ledger-display.h"
59 #include "gnc-plugin-page.h"
60 #include "gnc-plugin-page-register.h"
61 #include "gnc-prefs.h"
62 #include "gnc-ui.h"
63 #include "gnc-ui-util.h"
64 #include "gnucash-sheet.h"
65 #include "gnc-session.h"
66 #include <gnc-glib-utils.h>
67 
68 #include "gnc-split-reg.h"
69 
70 #include "gnc-sx-instance-model.h"
71 #include "dialog-sx-since-last-run.h"
72 
73 #undef G_LOG_DOMAIN
74 #define G_LOG_DOMAIN "gnc.gui.sx.editor"
75 
76 static QofLogModule log_module = GNC_MOD_GUI_SX;
77 
78 static gint _sx_engine_event_handler_id = -1;
79 
80 #define END_NEVER_OPTION 0
81 #define END_DATE_OPTION  1
82 #define NUM_OCCUR_OPTION 2
83 
84 #define NUM_LEDGER_LINES_DEFAULT 6
85 
86 #define EX_CAL_NUM_MONTHS 6
87 #define EX_CAL_MO_PER_COL 3
88 
89 #define GNC_D_WIDTH 25
90 #define GNC_D_BUF_WIDTH 26
91 
92 /** Datatypes ***********************************************************/
93 
94 typedef enum _EndTypeEnum
95 {
96     END_NEVER,
97     END_DATE,
98     END_OCCUR,
99 } EndType;
100 
101 struct _GncSxEditorDialog
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     GNCLedgerDisplay *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     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 
143 /** Prototypes **********************************************************/
144 
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 GtkActionEntry gnc_sxed_menu_entries [] =
166 {
167     { "EditAction", NULL, N_("_Edit"), NULL, NULL, NULL },
168     { "TransactionAction", NULL, N_("_Transaction"), NULL, NULL, NULL },
169     { "ViewAction", NULL, N_("_View"), NULL, NULL, NULL },
170     { "ActionsAction", NULL, N_("_Actions"), NULL, NULL, NULL },
171 };
172 static guint gnc_sxed_menu_n_entries = G_N_ELEMENTS (gnc_sxed_menu_entries);
173 
174 /** Implementations *****************************************************/
175 
176 static void
sxed_close_handler(gpointer user_data)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 
188 /**
189  * @return TRUE if the user does want to cancel, FALSE if not.  If TRUE is
190  * returned, the register's changes have been cancelled.
191  **/
192 static gboolean
sxed_confirmed_cancel(GncSxEditorDialog * sxed)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 */
210     gnc_split_register_cancel_cursor_trans_changes (reg);
211     return TRUE;
212 }
213 
214 
215 /**********************************
216  * Dialog Action Button functions *
217  *********************************/
218 static void
editor_cancel_button_clicked_cb(GtkButton * b,GncSxEditorDialog * sxed)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
editor_help_button_clicked_cb(GtkButton * b,GncSxEditorDialog * sxed)231 editor_help_button_clicked_cb (GtkButton *b, GncSxEditorDialog *sxed)
232 {
233     gnc_gnome_help (GTK_WINDOW (sxed->dialog), HF_HELP, HL_SXEDITOR);
234 }
235 
236 
237 static void
editor_ok_button_clicked_cb(GtkButton * b,GncSxEditorDialog * sxed)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
gnc_sxed_check_name_changed(GncSxEditorDialog * sxed)265 gnc_sxed_check_name_changed (GncSxEditorDialog *sxed)
266 {
267     char *name = gtk_editable_get_chars (GTK_EDITABLE (sxed->nameEntry), 0, -1);
268 
269     if (strlen (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
gnc_sxed_check_end_date_changed(GncSxEditorDialog * sxed)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
gnc_sxed_check_num_occurs_changed(GncSxEditorDialog * sxed)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
gnc_sxed_check_creation_changed(GncSxEditorDialog * sxed)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
gnc_sxed_check_dates_changed(GncSxEditorDialog * sxed)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
gnc_sxed_check_changed(GncSxEditorDialog * sxed)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);
425     if (gnc_split_register_changed (sr))
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 } txnCreditDebitSums;
441 
442 static txnCreditDebitSums *
tcds_new(void)443 tcds_new (void)
444 {
445     txnCreditDebitSums *tcds = g_new0 (txnCreditDebitSums, 1);
446     tcds->creditSum = tcds->debitSum = gnc_numeric_zero ();
447     return tcds;
448 }
449 
450 static void
set_sums_to_zero(gpointer key,gpointer val,gpointer ud)451 set_sums_to_zero (gpointer key,
452                   gpointer val,
453                   gpointer ud)
454 {
455     txnCreditDebitSums *tcds = (txnCreditDebitSums*)val;
456     tcds->creditSum = gnc_numeric_zero ();
457     tcds->debitSum  = gnc_numeric_zero ();
458 }
459 
460 inline static gnc_numeric
tcds_difference(txnCreditDebitSums * tcds)461 tcds_difference (txnCreditDebitSums *tcds)
462 {
463     return gnc_numeric_sub_fixed (tcds->debitSum, tcds->creditSum);
464 }
465 
466 static void
check_credit_debit_balance(gpointer key,gpointer val,gpointer ud)467 check_credit_debit_balance (gpointer key, gpointer val, gpointer ud)
468 {
469     txnCreditDebitSums *tcds = (txnCreditDebitSums*)val;
470     gboolean *unbalanced = (gboolean*)ud;
471     gnc_numeric diff = tcds_difference (tcds);
472     const char *result = gnc_numeric_zero_p (diff) ? "true" : "false";
473     *unbalanced |= !(gnc_numeric_zero_p (diff));
474 
475     DEBUG ("%p | %s [%s - %s = %s]", key, result,
476            gnc_numeric_to_string (tcds->debitSum),
477            gnc_numeric_to_string (tcds->creditSum),
478            gnc_numeric_to_string (diff));
479 }
480 
481 static gboolean
gnc_sxed_check_names(GncSxEditorDialog * sxed)482 gnc_sxed_check_names (GncSxEditorDialog *sxed)
483 {
484     gchar *name, *nameKey;
485     gboolean nameExists, nameHasChanged;
486     GList *sxList;
487 
488     name = gtk_editable_get_chars (GTK_EDITABLE (sxed->nameEntry), 0, -1);
489     if (strlen (name) == 0)
490     {
491         const char *sx_has_no_name_msg =
492             _("Please name the Scheduled Transaction.");
493         gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", sx_has_no_name_msg);
494         g_free (name);
495         return FALSE;
496 
497     }
498 
499     nameExists = FALSE;
500     nameKey = g_utf8_collate_key (name, -1);
501     nameHasChanged =
502         (xaccSchedXactionGetName (sxed->sx) == NULL)
503         || (strcmp (xaccSchedXactionGetName (sxed->sx), name) != 0);
504     for (sxList = gnc_book_get_schedxactions (gnc_get_current_book ())->sx_list;
505          nameHasChanged && !nameExists && sxList;
506          sxList = sxList->next)
507     {
508         char *existingName, *existingNameKey;
509         existingName = xaccSchedXactionGetName ((SchedXaction*)sxList->data);
510         existingNameKey = g_utf8_collate_key (existingName, -1);
511         nameExists |=  (strcmp (nameKey, existingNameKey) == 0);
512         g_free (existingNameKey);
513     }
514     g_free (nameKey);
515     if (nameHasChanged && nameExists)
516     {
517         const char *sx_has_existing_name_msg =
518             _("A Scheduled Transaction with the name \"%s\" already exists. "
519               "Are you sure you want to name this one the same?");
520         if (!gnc_verify_dialog (GTK_WINDOW (sxed->dialog), FALSE,
521                                 sx_has_existing_name_msg, name))
522         {
523             g_free (name);
524             return FALSE;
525         }
526     }
527     g_free (name);
528     return TRUE;
529 }
530 
531 static gboolean
gnc_sxed_check_endpoint(GncSxEditorDialog * sxed)532 gnc_sxed_check_endpoint (GncSxEditorDialog *sxed)
533 {
534     GDate startDate, endDate, nextDate;
535     GList *schedule = NULL;
536 
537     if (!gtk_toggle_button_get_active (sxed->optEndDate)
538          && !gtk_toggle_button_get_active (sxed->optEndCount)
539          && !gtk_toggle_button_get_active (sxed->optEndNone))
540     {
541         const char *sx_end_spec_msg =
542             _("Please provide a valid end selection.");
543         gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", sx_end_spec_msg);
544         return FALSE;
545     }
546 
547     if (gtk_toggle_button_get_active (sxed->optEndCount))
548     {
549         gint occur  =
550             gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
551         gint rem =
552             gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
553 
554         if (occur == 0)
555         {
556             const char *sx_occur_count_zero_msg =
557                 _("There must be some number of occurrences.");
558             gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", sx_occur_count_zero_msg);
559             return FALSE;
560         }
561 
562         if (rem > occur)
563         {
564             const char *sx_occur_counts_wrong_msg =
565                 _("The number of remaining occurrences (%d) is greater than "
566                   "the number of total occurrences (%d).");
567             gnc_error_dialog (GTK_WINDOW (sxed->dialog), sx_occur_counts_wrong_msg,
568                               rem, occur);
569             return FALSE;
570         }
571         return TRUE;
572     }
573 
574     g_date_clear (&endDate, 1);
575     if (gtk_toggle_button_get_active (sxed->optEndDate))
576     {
577         gnc_gdate_set_time64 (&endDate,
578                               gnc_date_edit_get_date (sxed-> endDateEntry));
579     }
580 
581     g_date_clear (&nextDate, 1);
582     gnc_frequency_save_to_recurrence (sxed->gncfreq, &schedule, &startDate);
583     if (gnc_list_length_cmp (schedule, 0))
584     {
585         g_date_subtract_days (&startDate, 1);
586         recurrenceListNextInstance (schedule, &startDate, &nextDate);
587     }
588     recurrenceListFree (&schedule);
589 
590     if (!g_date_valid (&nextDate) ||
591         (g_date_valid (&endDate) && (g_date_compare (&nextDate, &endDate) > 0)))
592     {
593         const char *invalid_sx_check_msg =
594             _("You have attempted to create a Scheduled Transaction which "
595               "will never run. Do you really want to do this?");
596         if (!gnc_verify_dialog (GTK_WINDOW (sxed->dialog), FALSE,
597                                "%s", invalid_sx_check_msg))
598             return FALSE;
599     }
600     return TRUE;
601 }
602 
603 static gboolean
gnc_sxed_check_autocreate(GncSxEditorDialog * sxed,int ttVarCount,int splitCount,gboolean multi_commodity)604 gnc_sxed_check_autocreate (GncSxEditorDialog *sxed, int ttVarCount,
605                            int splitCount, gboolean multi_commodity)
606 {
607     gboolean autocreateState;
608 
609     autocreateState =
610         gtk_toggle_button_get_active (
611             GTK_TOGGLE_BUTTON (sxed->autocreateOpt));
612 
613     if (((ttVarCount > 0) || multi_commodity) && autocreateState)
614     {
615         gnc_warning_dialog (GTK_WINDOW (sxed->dialog), "%s",
616                            _("Scheduled Transactions with variables "
617                              "or involving more than one commodity "
618                              "cannot be automatically created."));
619         return FALSE;
620     }
621 
622     /* Fix for part of Bug#121740 -- auto-create transactions are
623      * only valid if there's actually a transaction to create. */
624     if (autocreateState && splitCount == 0)
625     {
626         gnc_warning_dialog (GTK_WINDOW (sxed->dialog), "%s",
627                            _("Scheduled Transactions without a template "
628                              "transaction cannot be automatically created."));
629         return FALSE;
630     }
631     return TRUE;
632 }
633 
634 static gboolean
gnc_sxed_split_check_account(GncSxEditorDialog * sxed,Split * s,gnc_commodity * base_cmdty,gboolean * multi_cmdty)635 gnc_sxed_split_check_account (GncSxEditorDialog *sxed, Split *s,
636                               gnc_commodity *base_cmdty, gboolean *multi_cmdty)
637 {
638     gnc_commodity *split_cmdty = NULL;
639     gnc_numeric split_amount;
640     Account *acct = NULL;
641     GncGUID *acct_guid = NULL;
642     qof_instance_get (QOF_INSTANCE (s),
643                       "sx-account", &acct_guid,
644                       NULL);
645     acct = xaccAccountLookup (acct_guid, gnc_get_current_book ());
646     guid_free (acct_guid);
647     // If the split is being destroyed always return TRUE.
648     if (acct == NULL && !qof_instance_get_destroying (s))
649         return FALSE;
650     split_cmdty = xaccAccountGetCommodity (acct);
651     split_amount = xaccSplitGetAmount (s);
652     if (!gnc_numeric_zero_p (split_amount) && base_cmdty == NULL)
653     {
654         base_cmdty = split_cmdty;
655     }
656     *multi_cmdty |= (!gnc_numeric_zero_p (split_amount) &&
657                     !gnc_commodity_equal (split_cmdty, base_cmdty));
658     return TRUE;
659 }
660 
661 static gboolean
gnc_sxed_split_calculate_formula(GncSxEditorDialog * sxed,Split * s,GHashTable * vars,const char * key,txnCreditDebitSums * tcds)662 gnc_sxed_split_calculate_formula (GncSxEditorDialog *sxed, Split *s,
663                                   GHashTable *vars, const char *key,
664                                   txnCreditDebitSums *tcds)
665 {
666     gnc_numeric tmp = gnc_numeric_zero ();
667     char *str = NULL;
668     qof_instance_get (QOF_INSTANCE (s),
669                       key, &str,
670                       NULL);
671     if (str == NULL || strlen (str) == 0)
672         return TRUE; /* No formula no foul */
673     if (gnc_sx_parse_vars_from_formula (str, vars, &tmp) < 0)
674     {
675         gchar *err = g_strdup_printf (_("Couldn't parse %s for split \"%s\"."),
676                                       key, xaccSplitGetMemo (s));
677         gnc_error_dialog (GTK_WINDOW (sxed->dialog), "%s", err);
678         g_free (err);
679 
680         return FALSE;
681     }
682     if (g_strcmp0 (key, "sx-credit-formula") == 0)
683         tcds->creditSum = gnc_numeric_add (tcds->creditSum, tmp, 100,
684                                           GNC_DENOM_AUTO | GNC_HOW_DENOM_LCD);
685     else
686         tcds->debitSum = gnc_numeric_add (tcds->debitSum, tmp, 100,
687                                           GNC_DENOM_AUTO | GNC_HOW_DENOM_LCD);
688     return TRUE;
689 }
690 
691 typedef struct
692 {
693     GncSxEditorDialog *sxed;
694     GHashTable *txns;
695     GHashTable *vars;
696     txnCreditDebitSums *tcds;
697     gboolean multi_commodity;
698     gboolean err;
699 } CheckTxnSplitData;
700 
701 static void
split_error_warning_dialog(GtkWidget * parent,const gchar * title,gchar * message)702 split_error_warning_dialog (GtkWidget *parent, const gchar *title,
703                             gchar *message)
704 {
705     GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW (parent), 0,
706                                                 GTK_MESSAGE_ERROR,
707                                                 GTK_BUTTONS_CLOSE,
708                                                 "%s", title);
709     gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
710                                               "%s", message);
711     gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent));
712     g_signal_connect_swapped (dialog, "response",
713                               G_CALLBACK (gtk_widget_destroy), dialog);
714     gtk_dialog_run (GTK_DIALOG (dialog));
715 
716 }
717 
718 static gboolean
check_transaction_splits(Transaction * txn,gpointer data)719 check_transaction_splits (Transaction *txn, gpointer data)
720 {
721     GList *splitList = xaccTransGetSplitList (txn);
722     CheckTxnSplitData *sd = (CheckTxnSplitData*)data;
723 
724     for (; splitList; splitList = splitList->next)
725     {
726         gnc_commodity *base_cmdty = NULL;
727         Split *s = (Split*)splitList->data;
728 
729         if (sd->tcds == NULL)
730         {
731             sd->tcds = tcds_new ();
732             g_hash_table_insert (sd->txns, (gpointer)txn, (gpointer)(sd->tcds));
733         }
734 
735         if (!gnc_sxed_split_check_account (sd->sxed, s, base_cmdty,
736                                            &sd->multi_commodity))
737         {
738             gchar *message = g_strdup_printf
739                 (_("Split with memo %s has an invalid account."),
740                  xaccSplitGetMemo (s));
741             split_error_warning_dialog (sd->sxed->dialog,
742                                         _("Invalid Account in Split"),
743                                         message);
744             g_free (message);
745             sd->err = TRUE;
746             return FALSE;
747         }
748 
749         if (!gnc_sxed_split_calculate_formula (sd->sxed, s, sd->vars,
750                                                "sx-credit-formula",
751                                                sd->tcds))
752         {
753             gchar *message = g_strdup_printf
754                 (_("Split with memo %s has an unparseable Credit Formula."),
755                  xaccSplitGetMemo (s));
756             split_error_warning_dialog (sd->sxed->dialog,
757                                         _("Unparsable Formula in Split"),
758                                         message);
759             g_free (message);
760             sd->err = TRUE;
761             return FALSE;
762         }
763 
764         if (!gnc_sxed_split_calculate_formula (sd->sxed, s, sd->vars,
765                                                "sx-debit-formula",
766                                                sd->tcds))
767 
768         {
769             gchar *message = g_strdup_printf
770                 (_("Split with memo %s has an unparseable Debit Formula."),
771                  xaccSplitGetMemo (s));
772             split_error_warning_dialog (sd->sxed->dialog,
773                                         _("Unparsable Formula in Split"),
774                                         message);
775             g_free (message);
776             sd->err = TRUE;
777             return FALSE;
778         }
779     }
780     return TRUE;
781 }
782 
783 /*******************************************************************************
784  * Checks to make sure that the SX is in a reasonable state to save.
785  * @return true if checks out okay, false otherwise.
786  ******************************************************************************/
787 static gboolean
gnc_sxed_check_consistent(GncSxEditorDialog * sxed)788 gnc_sxed_check_consistent (GncSxEditorDialog *sxed)
789 {
790 
791     /* Do checks on validity and such, interrupting the user if
792      * things aren't right.
793      *
794      * Features...
795      * X support formulas [?!]
796      * X balancing the SX if contain numeric-only formula data.
797      *   X agreement with create-automagically/notification controls
798      * X the 'will ever be valid' check should take num-occur vals into
799      *   account.
800      * X SX name is unique
801      * X SX has a name
802      * X "weekly" FS has some days set.
803      * X "once" with reasonable start/end dates.
804      *   X This doesn't work at the time the 'weekly' one was fixed with
805      *     user-confirmation, below; the once SX is always valid.
806      * [X more generically, creating a "not scheduled" SX is probably not
807      *   right... ]
808      */
809 
810     gint ttVarCount = 0, splitCount = 0;
811     static const int NUM_ITERS_WITH_VARS = 5;
812     static const int NUM_ITERS_NO_VARS = 1;
813     int numIters = NUM_ITERS_NO_VARS, i;
814     gboolean unbalanceable = FALSE;
815     gpointer unusedKey, unusedValue;
816 
817     GHashTable *vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
818                                   (GDestroyNotify)gnc_sx_variable_free);
819     GHashTable *txns = g_hash_table_new_full (g_direct_hash, g_direct_equal,
820                                               NULL, g_free);
821     CheckTxnSplitData sd = {sxed, txns, vars, NULL, FALSE, FALSE};
822 
823     /**
824      * Plan:
825      * . Do a first pass to get the variables.
826      * . Set each variable to random values.
827      * . see if we balance after that
828      *   . true: all good
829      *   . false: indicate to user, allow decision.
830      */
831 
832     /* FIXME: This is probably superfluous. */
833     gnc_split_register_save (
834         gnc_ledger_display_get_split_register (sxed->ledger), FALSE);
835     /* numeric-formulas-get-balanced determination */
836     gnc_sx_get_variables (sxed->sx, vars);
837 
838     ttVarCount = g_hash_table_size (vars);
839     if (ttVarCount != 0)
840     {
841         /* balance with random variable bindings some number of times in an
842          * attempt to ferret out un-balanceable transactions.
843          */
844         numIters = NUM_ITERS_WITH_VARS;
845     }
846 
847     for (i = 0; i < numIters && !unbalanceable; i++)
848     {
849         GList *splitList = xaccSchedXactionGetSplits (sxed->sx);
850         Account *tmpl_acct = gnc_sx_get_template_transaction_account (sxed->sx);
851         gnc_sx_randomize_variables (vars);
852         g_hash_table_foreach (txns, set_sums_to_zero, NULL);
853 
854         splitCount += g_list_length (splitList);
855 
856         xaccAccountForEachTransaction (tmpl_acct, check_transaction_splits, &sd);
857 
858         if (sd.err)
859             return FALSE;
860 
861         g_hash_table_foreach (txns, check_credit_debit_balance, &unbalanceable);
862     }
863 
864     /* Subtract out pre-defined vars */
865     if (g_hash_table_lookup_extended (vars, "i", &unusedKey, &unusedValue))
866         ttVarCount -= 1;
867 
868     g_hash_table_destroy (vars);
869     g_hash_table_destroy (txns);
870 
871     if (unbalanceable)
872     {
873         const char *msg =
874             _("The Scheduled Transaction Editor cannot automatically "
875               "balance this transaction. Should it still be entered?");
876         if (!gnc_verify_dialog (GTK_WINDOW (sxed->dialog), FALSE, "%s", msg))
877             return FALSE;
878     }
879 
880     if (!gnc_sxed_check_names (sxed))
881         return FALSE;
882 
883     if (!gnc_sxed_check_autocreate (sxed, ttVarCount,
884                                     splitCount, sd.multi_commodity))
885         return FALSE;
886 
887     if (!gnc_sxed_check_endpoint (sxed))
888         return FALSE;
889     return TRUE;
890 }
891 
892 
893 /******************************************************************************
894  * Saves the contents of the SX.  This assumes that gnc_sxed_check_consistent
895  * has returned true.
896   *****************************************************************************/
897 static void
gnc_sxed_save_sx(GncSxEditorDialog * sxed)898 gnc_sxed_save_sx (GncSxEditorDialog *sxed)
899 {
900     gnc_sx_begin_edit (sxed->sx);
901 
902     /* name */
903     {
904         char *name;
905 
906         name = gtk_editable_get_chars (sxed->nameEntry, 0, -1);
907         xaccSchedXactionSetName (sxed->sx, name);
908         g_free (name);
909     }
910 
911     /* date */
912     {
913         GDate gdate;
914 
915         if (gtk_toggle_button_get_active (sxed->optEndDate))
916         {
917             /* get the end date data */
918             gnc_gdate_set_time64(&gdate,
919                                   gnc_date_edit_get_date (
920                                       sxed->endDateEntry));
921             xaccSchedXactionSetEndDate (sxed->sx, &gdate);
922             /* set the num occurrences data */
923             xaccSchedXactionSetNumOccur (sxed->sx, 0);
924         }
925         else if (gtk_toggle_button_get_active (sxed->optEndCount))
926         {
927             gint num;
928 
929             /* get the occurrences data */
930             num  =
931                 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endCountSpin));
932             xaccSchedXactionSetNumOccur (sxed->sx, num);
933 
934             num =
935                 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
936             xaccSchedXactionSetRemOccur (sxed->sx, num);
937 
938             g_date_clear (&gdate, 1);
939             xaccSchedXactionSetEndDate (sxed->sx, &gdate);
940         }
941         else if (gtk_toggle_button_get_active (sxed->optEndNone))
942         {
943             xaccSchedXactionSetNumOccur (sxed->sx, 0);
944             g_date_clear (&gdate, 1);
945             xaccSchedXactionSetEndDate (sxed->sx, &gdate);
946         }
947         else
948         {
949             g_critical ("no valid end specified\n");
950         }
951     }
952 
953     /* Enabled states */
954     {
955         gboolean enabledState;
956 
957         enabledState = gtk_toggle_button_get_active (sxed->enabledOpt);
958         xaccSchedXactionSetEnabled (sxed->sx, enabledState);
959     }
960 
961     /* Auto-create/notification states */
962     {
963         gboolean autocreateState, notifyState;
964 
965         autocreateState = gtk_toggle_button_get_active (sxed->autocreateOpt);
966         notifyState = gtk_toggle_button_get_active (sxed->notifyOpt);
967         /* "Notify" only makes sense if AutoCreate is activated;
968          * enforce that here. */
969         xaccSchedXactionSetAutoCreate (sxed->sx,
970                                        autocreateState,
971                                        (autocreateState & notifyState));
972     }
973 
974     /* days in advance */
975     {
976         int daysInAdvance;
977 
978         daysInAdvance = 0;
979         if (gtk_toggle_button_get_active (sxed->advanceOpt))
980         {
981             daysInAdvance =
982                 gtk_spin_button_get_value_as_int (sxed->advanceSpin);
983         }
984         xaccSchedXactionSetAdvanceCreation (sxed->sx, daysInAdvance);
985 
986         daysInAdvance = 0;
987         if (gtk_toggle_button_get_active (sxed->remindOpt))
988         {
989             daysInAdvance =
990                 gtk_spin_button_get_value_as_int (sxed->remindSpin);
991         }
992         xaccSchedXactionSetAdvanceReminder (sxed->sx, daysInAdvance);
993     }
994 
995     /* start date and freq spec */
996     {
997         GDate gdate;
998         GList *schedule = NULL;
999 
1000         gnc_frequency_save_to_recurrence (sxed->gncfreq, &schedule, &gdate);
1001         gnc_sx_set_schedule (sxed->sx, schedule);
1002         {
1003             gchar *recurrence_str = recurrenceListToCompactString (schedule);
1004             DEBUG ("recurrences parsed [%s]", recurrence_str);
1005             g_free (recurrence_str);
1006         }
1007 
1008         /* now that we have it, set the start date */
1009         xaccSchedXactionSetStartDate (sxed->sx, &gdate);
1010     }
1011 
1012     gnc_sx_commit_edit (sxed->sx);
1013 }
1014 
1015 static void
update_sensitivity(GncSxEditorDialog * sxed)1016 update_sensitivity (GncSxEditorDialog *sxed)
1017 {
1018     gboolean enabled = gtk_toggle_button_get_active (sxed->enabledOpt);
1019     gboolean autocreate = gtk_toggle_button_get_active (sxed->autocreateOpt);
1020     gboolean advance = gtk_toggle_button_get_active (sxed->advanceOpt);
1021     gboolean remind = gtk_toggle_button_get_active (sxed->remindOpt);
1022     gboolean type_date = (sxed->end_type == END_DATE);
1023     gboolean type_occur = (sxed->end_type == END_OCCUR);
1024 
1025     gnc_suspend_gui_refresh ();
1026 
1027     gtk_widget_set_sensitive (GTK_WIDGET (sxed->autocreateOpt), enabled);
1028     gtk_widget_set_sensitive (GTK_WIDGET (sxed->notifyOpt), enabled && autocreate);
1029 
1030     gtk_widget_set_sensitive (GTK_WIDGET (sxed->advanceOpt), enabled);
1031     gtk_widget_set_sensitive (GTK_WIDGET (sxed->advanceSpin), enabled && advance);
1032 
1033     gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindOpt), enabled);
1034     gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindSpin), enabled && remind);
1035 
1036     gtk_widget_set_sensitive (GTK_WIDGET (sxed->optEndNone), enabled);
1037     gtk_widget_set_sensitive (GTK_WIDGET (sxed->optEndDate), enabled);
1038     gtk_widget_set_sensitive (GTK_WIDGET (sxed->optEndCount), enabled);
1039 
1040     gtk_widget_set_sensitive (GTK_WIDGET (sxed->endDateEntry), enabled && type_date);
1041     gtk_widget_set_sensitive (GTK_WIDGET (sxed->endCountSpin), enabled && type_occur);
1042     gtk_widget_set_sensitive (GTK_WIDGET (sxed->endRemainSpin), enabled && type_occur);
1043 
1044     gtk_widget_set_sensitive (gtk_notebook_get_nth_page (sxed->notebook, 1), enabled);
1045     gtk_widget_set_sensitive (gtk_notebook_get_nth_page (sxed->notebook, 2), enabled);
1046 
1047     gnc_resume_gui_refresh ();
1048 }
1049 
1050 static void
enabled_toggled_cb(GtkToggleButton * o,GncSxEditorDialog * sxed)1051 enabled_toggled_cb (GtkToggleButton *o, GncSxEditorDialog *sxed)
1052 {
1053     update_sensitivity (sxed);
1054 }
1055 
1056 static void
autocreate_toggled_cb(GtkToggleButton * o,GncSxEditorDialog * sxed)1057 autocreate_toggled_cb (GtkToggleButton *o, GncSxEditorDialog *sxed)
1058 {
1059     update_sensitivity (sxed);
1060 }
1061 
1062 static void
advance_toggled_cb(GtkButton * o,GncSxEditorDialog * sxed)1063 advance_toggled_cb (GtkButton *o, GncSxEditorDialog *sxed)
1064 {
1065     update_sensitivity (sxed);
1066 }
1067 
1068 static void
remind_toggled_cb(GtkButton * o,GncSxEditorDialog * sxed)1069 remind_toggled_cb (GtkButton *o, GncSxEditorDialog *sxed)
1070 {
1071     update_sensitivity (sxed);
1072 }
1073 
1074 
1075 /* Local destruction of dialog */
1076 static void
scheduledxaction_editor_dialog_destroy(GtkWidget * object,gpointer data)1077 scheduledxaction_editor_dialog_destroy (GtkWidget *object, gpointer data)
1078 {
1079     GncSxEditorDialog *sxed = data;
1080 
1081     if (sxed == NULL)
1082         return;
1083 
1084     gnc_unregister_gui_component_by_data
1085         (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS, sxed);
1086 
1087     gnc_embedded_window_close_page (sxed->embed_window, sxed->plugin_page);
1088     gtk_widget_destroy (GTK_WIDGET (sxed->embed_window));
1089     sxed->embed_window = NULL;
1090     sxed->plugin_page = NULL;
1091     sxed->ledger = NULL;
1092 
1093     g_free (sxed->sxGUIDstr);
1094     sxed->sxGUIDstr = NULL;
1095 
1096     if (sxed->newsxP)
1097     {
1098         /* FIXME: WTF???
1099          *
1100          * "WTF" explanation: in the "new" click from the caller, we
1101          * set this flag.  When "ok" is pressed on the dialog, we set
1102          * this flag to false, and thus leave the SX live.  If
1103          * "Cancel" is clicked, the flag will still be true, and this
1104          * SX will be cleaned, here. -- jsled
1105          */
1106         gnc_sx_begin_edit (sxed->sx);
1107         xaccSchedXactionDestroy (sxed->sx);
1108     }
1109     sxed->sx = NULL;
1110 
1111     g_free (sxed);
1112 }
1113 
1114 
1115 static
1116 gboolean
sxed_delete_event(GtkWidget * widget,GdkEvent * event,gpointer ud)1117 sxed_delete_event (GtkWidget *widget, GdkEvent *event, gpointer ud)
1118 {
1119     GncSxEditorDialog *sxed = (GncSxEditorDialog*)ud;
1120 
1121     /* We've already processed the SX, likely because of "ok" being
1122      * clicked. */
1123     if (sxed->sx == NULL)
1124     {
1125         return FALSE;
1126     }
1127 
1128     if (!sxed_confirmed_cancel (sxed))
1129     {
1130         return TRUE;
1131     }
1132     return FALSE;
1133 }
1134 
1135 
1136 /*************************************
1137  * Create the Schedule Editor Dialog *
1138  ************************************/
1139 GncSxEditorDialog *
gnc_ui_scheduled_xaction_editor_dialog_create(GtkWindow * parent,SchedXaction * sx,gboolean newSX)1140 gnc_ui_scheduled_xaction_editor_dialog_create (GtkWindow *parent,
1141                                                SchedXaction *sx, gboolean newSX)
1142 {
1143     GncSxEditorDialog *sxed;
1144     GtkBuilder *builder;
1145     GtkWidget *button;
1146     int i;
1147     int id;
1148     GList *dlgExists = NULL;
1149 
1150     static struct widgetSignalCallback
1151     {
1152         char     *name;
1153         char     *signal;
1154         void     (*fn)();
1155         gpointer objectData;
1156     } widgets[] =
1157           {
1158               { "ok_button",      "clicked",       editor_ok_button_clicked_cb,     NULL },
1159               { "cancel_button",  "clicked",       editor_cancel_button_clicked_cb, NULL },
1160               { "help_button",    "clicked",       editor_help_button_clicked_cb,   NULL },
1161               { "rb_noend",       "toggled",       endgroup_rb_toggled_cb,          GINT_TO_POINTER (END_NEVER_OPTION) },
1162               { "rb_enddate",     "toggled",       endgroup_rb_toggled_cb,          GINT_TO_POINTER (END_DATE_OPTION) },
1163               { "rb_num_occur",   "toggled",       endgroup_rb_toggled_cb,          GINT_TO_POINTER (NUM_OCCUR_OPTION) },
1164               { "remain_spin" ,   "value-changed", sxed_excal_update_adapt_cb,      NULL },
1165               { "enabled_opt",    "toggled",       enabled_toggled_cb,              NULL },
1166               { "autocreate_opt", "toggled",       autocreate_toggled_cb,           NULL },
1167               { "advance_opt",    "toggled",       advance_toggled_cb,              NULL },
1168               { "remind_opt",     "toggled",       remind_toggled_cb,               NULL },
1169               { NULL,             NULL,            NULL,                            NULL }
1170           };
1171 
1172     dlgExists = gnc_find_gui_components (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS,
1173                                          editor_component_sx_equality,
1174                                          sx);
1175     if (dlgExists)
1176     {
1177         DEBUG ("dialog already exists; using that one.");
1178         sxed = (GncSxEditorDialog*)dlgExists->data;
1179         gtk_window_present (GTK_WINDOW (sxed->dialog));
1180         g_list_free (dlgExists);
1181         return sxed;
1182     }
1183 
1184     sxed = g_new0(GncSxEditorDialog, 1);
1185 
1186     sxed->sx     = sx;
1187     sxed->newsxP = newSX;
1188 
1189     /* Load up Glade file */
1190     builder = gtk_builder_new ();
1191     gnc_builder_add_from_file (builder, "dialog-sx.glade", "advance_days_adj");
1192     gnc_builder_add_from_file (builder, "dialog-sx.glade", "remind_days_adj");
1193     gnc_builder_add_from_file (builder, "dialog-sx.glade", "end_spin_adj");
1194     gnc_builder_add_from_file (builder, "dialog-sx.glade", "remain_spin_adj");
1195     gnc_builder_add_from_file (builder, "dialog-sx.glade", "scheduled_transaction_editor_dialog");
1196 
1197     sxed->builder = builder;
1198 
1199     /* Connect the Widgets */
1200     sxed->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "scheduled_transaction_editor_dialog"));
1201     sxed->notebook = GTK_NOTEBOOK (gtk_builder_get_object (builder, "editor_notebook"));
1202     sxed->nameEntry = GTK_EDITABLE (gtk_builder_get_object (builder, "sxe_name"));
1203     sxed->enabledOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "enabled_opt"));
1204     sxed->autocreateOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "autocreate_opt"));
1205     sxed->notifyOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "notify_opt"));
1206     sxed->advanceOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "advance_opt"));
1207     sxed->advanceSpin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "advance_days"));
1208     sxed->remindOpt = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "remind_opt"));
1209     sxed->remindSpin = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "remind_days"));
1210     sxed->lastOccurLabel = GTK_LABEL (gtk_builder_get_object (builder, "last_occur_label"));
1211     sxed->optEndNone = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_noend"));
1212     sxed->optEndDate = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_enddate"));
1213     sxed->optEndCount = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "rb_num_occur"));
1214     sxed->endCountSpin = GTK_ENTRY (gtk_builder_get_object (builder, "end_spin"));
1215     sxed->endRemainSpin = GTK_ENTRY (gtk_builder_get_object (builder, "remain_spin"));
1216 
1217     // Set the name of this dialog so it can be easily manipulated with css
1218     gtk_widget_set_name (GTK_WIDGET (sxed->dialog), "gnc-id-sx-editor");
1219     gnc_widget_style_context_add_class (GTK_WIDGET (sxed->dialog), "gnc-class-sx");
1220 
1221     gtk_window_set_transient_for (GTK_WINDOW (sxed->dialog), parent);
1222 
1223     /* Setup the end-date GNC widget */
1224     {
1225         GtkWidget *endDateBox = GTK_WIDGET (gtk_builder_get_object (builder, "editor_end_date_box"));
1226         sxed->endDateEntry = GNC_DATE_EDIT (gnc_date_edit_new (gnc_time (NULL), FALSE, FALSE));
1227         gtk_widget_show (GTK_WIDGET (sxed->endDateEntry));
1228         g_signal_connect (sxed->endDateEntry, "date-changed",
1229                           G_CALLBACK (sxed_excal_update_adapt_cb), sxed);
1230         gtk_box_pack_start (GTK_BOX (endDateBox), GTK_WIDGET (sxed->endDateEntry),
1231                             TRUE, TRUE, 0);
1232     }
1233 
1234     id = gnc_register_gui_component (DIALOG_SCHEDXACTION_EDITOR_CM_CLASS,
1235                                      NULL, /* no refresh handler */
1236                                      sxed_close_handler,
1237                                      sxed);
1238     // This ensure this dialog is closed when the session is closed.
1239     gnc_gui_component_set_session (id, gnc_get_current_session ());
1240 
1241     g_signal_connect (sxed->dialog, "delete_event",
1242                       G_CALLBACK (sxed_delete_event), sxed);
1243     g_signal_connect (sxed->dialog, "destroy",
1244                       G_CALLBACK (scheduledxaction_editor_dialog_destroy),
1245                       sxed);
1246 
1247     for (i = 0; widgets[i].name; i++)
1248     {
1249         button = GTK_WIDGET (gtk_builder_get_object (builder, widgets[i].name));
1250         if (widgets[i].objectData)
1251         {
1252             g_object_set_data (G_OBJECT (button), "whichOneAmI",
1253                                widgets[i].objectData);
1254         }
1255         g_signal_connect (button, widgets[i].signal,
1256                           G_CALLBACK (widgets[i].fn), sxed);
1257     }
1258 
1259     /* Set sensitivity settings  */
1260     gtk_widget_set_sensitive (GTK_WIDGET (sxed->notifyOpt), FALSE);
1261     gtk_widget_set_sensitive (GTK_WIDGET (sxed->advanceSpin), FALSE);
1262     gtk_widget_set_sensitive (GTK_WIDGET (sxed->remindSpin), FALSE);
1263     gtk_widget_set_sensitive (GTK_WIDGET (sxed->endCountSpin), FALSE);
1264     gtk_widget_set_sensitive (GTK_WIDGET (sxed->endRemainSpin), FALSE);
1265     gtk_editable_set_editable (GTK_EDITABLE (sxed->advanceSpin), TRUE);
1266     gtk_editable_set_editable (GTK_EDITABLE (sxed->remindSpin), TRUE);
1267 
1268     /* Allow resize */
1269     gtk_window_set_resizable (GTK_WINDOW (sxed->dialog), TRUE);
1270     gnc_restore_window_size (GNC_PREFS_GROUP_SXED, GTK_WINDOW (sxed->dialog), parent);
1271 
1272     /* create the frequency-selection widget and example [dense-]calendar. */
1273     schedXact_editor_create_freq_sel (sxed);
1274 
1275     /* create the template-transaction ledger window */
1276     schedXact_editor_create_ledger (sxed);
1277 
1278     /* populate */
1279     schedXact_editor_populate (sxed);
1280 
1281     /* Do not call show_all here. Screws up the gtkuimanager code */
1282     gtk_widget_show (sxed->dialog);
1283     gtk_notebook_set_current_page (GTK_NOTEBOOK (sxed->notebook), 0);
1284 
1285     /* Refresh the cal and the ledger */
1286     gtk_widget_queue_resize (GTK_WIDGET (sxed->example_cal));
1287 
1288     gnc_ledger_display_refresh (sxed->ledger);
1289 
1290     /* Move keyboard focus to the name entry */
1291     gtk_widget_grab_focus (GTK_WIDGET (sxed->nameEntry));
1292 
1293     gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, sxed);
1294     g_object_unref (G_OBJECT (builder));
1295 
1296     return sxed;
1297 }
1298 
1299 
1300 static void
schedXact_editor_create_freq_sel(GncSxEditorDialog * sxed)1301 schedXact_editor_create_freq_sel (GncSxEditorDialog *sxed)
1302 {
1303     GtkBox *b;
1304     GtkWidget *example_cal_scrolled_win = NULL;
1305 
1306     b = GTK_BOX (gtk_builder_get_object (sxed->builder, "gncfreq_hbox"));
1307 
1308     sxed->gncfreq =
1309         GNC_FREQUENCY (gnc_frequency_new_from_recurrence (gnc_sx_get_schedule (sxed->sx),
1310                                                           xaccSchedXactionGetStartDate (sxed->sx)));
1311     g_assert (sxed->gncfreq);
1312     g_signal_connect (sxed->gncfreq, "changed",
1313                       G_CALLBACK (gnc_sxed_freq_changed),
1314                       sxed);
1315 
1316     gtk_box_pack_start (GTK_BOX (b), GTK_WIDGET (sxed->gncfreq), TRUE, TRUE, 0);
1317 
1318     b = GTK_BOX (gtk_builder_get_object (sxed->builder, "example_cal_hbox"));
1319 
1320     example_cal_scrolled_win = gtk_scrolled_window_new (NULL, NULL);
1321     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (example_cal_scrolled_win),
1322                                     GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1323     gtk_box_pack_start (GTK_BOX (b), example_cal_scrolled_win, TRUE, TRUE, 0);
1324 
1325     sxed->dense_cal_model = gnc_dense_cal_store_new (EX_CAL_NUM_MONTHS * 31);
1326     sxed->example_cal = GNC_DENSE_CAL (gnc_dense_cal_new_with_model (GNC_DENSE_CAL_MODEL (sxed->dense_cal_model)));
1327     g_assert (sxed->example_cal);
1328     gnc_dense_cal_set_num_months (sxed->example_cal, EX_CAL_NUM_MONTHS);
1329     gnc_dense_cal_set_months_per_col (sxed->example_cal, EX_CAL_MO_PER_COL);
1330     gtk_container_add (GTK_CONTAINER (example_cal_scrolled_win), GTK_WIDGET (sxed->example_cal));
1331 
1332 
1333     gtk_widget_show_all (example_cal_scrolled_win);
1334 }
1335 
1336 
1337 static void
schedXact_editor_create_ledger(GncSxEditorDialog * sxed)1338 schedXact_editor_create_ledger (GncSxEditorDialog *sxed)
1339 {
1340     SplitRegister *splitreg;
1341     GtkWidget *main_vbox;
1342 
1343     /* Create the ledger */
1344     sxed->sxGUIDstr = guid_to_string (xaccSchedXactionGetGUID (sxed->sx));
1345     sxed->ledger = gnc_ledger_display_template_gl (sxed->sxGUIDstr);
1346     splitreg = gnc_ledger_display_get_split_register (sxed->ledger);
1347 
1348     /* First the embedded window */
1349     main_vbox = GTK_WIDGET (gtk_builder_get_object (sxed->builder, "register_vbox"));
1350     sxed->embed_window =
1351         gnc_embedded_window_new ("SXWindowActions",
1352                                  gnc_sxed_menu_entries,
1353                                  gnc_sxed_menu_n_entries,
1354                                  "gnc-sxed-window-ui.xml",
1355                                  sxed->dialog,
1356                                  FALSE, /* no accelerators */
1357                                  sxed);
1358     gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (sxed->embed_window),
1359                         TRUE, TRUE, 0);
1360 
1361     /* Now create the register plugin page. */
1362     sxed->plugin_page = gnc_plugin_page_register_new_ledger (sxed->ledger);
1363     gnc_plugin_page_set_ui_description (sxed->plugin_page,
1364                                         "gnc-sxed-window-ui-full.xml");
1365     gnc_plugin_page_register_set_options (sxed->plugin_page,
1366                                           NUM_LEDGER_LINES_DEFAULT, FALSE);
1367     gnc_embedded_window_open_page (sxed->embed_window, sxed->plugin_page);
1368 
1369     /* configure... */
1370     /* use double-line, so scheduled transaction Notes can be edited */
1371     gnc_split_register_config (splitreg,
1372                               splitreg->type, splitreg->style,
1373                               TRUE);
1374     gnc_split_register_set_auto_complete (splitreg, FALSE);
1375 
1376     /* don't show present/future divider [by definition, not necessary] */
1377     gnc_split_register_show_present_divider (splitreg, FALSE);
1378 }
1379 
1380 
1381 static void
schedXact_editor_populate(GncSxEditorDialog * sxed)1382 schedXact_editor_populate (GncSxEditorDialog *sxed)
1383 {
1384     char *name;
1385     time64 tmpDate;
1386     SplitRegister *splitReg;
1387     const GDate *gd;
1388     gint daysInAdvance;
1389     gboolean enabledState, autoCreateState, notifyState;
1390 
1391     name = xaccSchedXactionGetName (sxed->sx);
1392     if (name)
1393     {
1394         gtk_entry_set_text (GTK_ENTRY (sxed->nameEntry), name );
1395     }
1396     {
1397         gd = xaccSchedXactionGetLastOccurDate (sxed->sx);
1398         if (g_date_valid (gd))
1399         {
1400             gchar dateBuf [MAX_DATE_LENGTH+1];
1401             qof_print_gdate (dateBuf, MAX_DATE_LENGTH, gd);
1402             gtk_label_set_text (sxed->lastOccurLabel, dateBuf);
1403         }
1404         else
1405         {
1406             gtk_label_set_text (sxed->lastOccurLabel, _("(never)"));
1407         }
1408         gd = NULL;
1409     }
1410 
1411     gd = xaccSchedXactionGetEndDate (sxed->sx);
1412     if (g_date_valid (gd))
1413     {
1414         gtk_toggle_button_set_active (sxed->optEndDate, TRUE);
1415         tmpDate = gnc_time64_get_day_start_gdate (gd);
1416         gnc_date_edit_set_time (sxed->endDateEntry, tmpDate);
1417 
1418         set_endgroup_toggle_states (sxed, END_DATE);
1419     }
1420     else if (xaccSchedXactionHasOccurDef (sxed->sx))
1421     {
1422         gint numOccur = xaccSchedXactionGetNumOccur (sxed->sx);
1423         gint numRemain = xaccSchedXactionGetRemOccur (sxed->sx);
1424 
1425         gtk_toggle_button_set_active (sxed->optEndCount, TRUE);
1426 
1427         gtk_spin_button_set_value (GTK_SPIN_BUTTON (sxed->endCountSpin), numOccur);
1428         gtk_spin_button_set_value (GTK_SPIN_BUTTON (sxed->endRemainSpin), numRemain);
1429 
1430         set_endgroup_toggle_states (sxed, END_OCCUR);
1431     }
1432     else
1433     {
1434         gtk_toggle_button_set_active (sxed->optEndNone, TRUE);
1435         set_endgroup_toggle_states (sxed, END_NEVER);
1436     }
1437 
1438     enabledState = xaccSchedXactionGetEnabled (sxed->sx);
1439     gtk_toggle_button_set_active (sxed->enabledOpt, enabledState);
1440 
1441     /* Do auto-create/notify setup */
1442     if (sxed->newsxP)
1443     {
1444         autoCreateState =
1445             gnc_prefs_get_bool (GNC_PREFS_GROUP_SXED, GNC_PREF_CREATE_AUTO);
1446         notifyState =
1447             gnc_prefs_get_bool (GNC_PREFS_GROUP_SXED, GNC_PREF_NOTIFY);
1448     }
1449     else
1450     {
1451         xaccSchedXactionGetAutoCreate (sxed->sx,
1452                                        &autoCreateState,
1453                                        &notifyState);
1454     }
1455     gtk_toggle_button_set_active (sxed->autocreateOpt, autoCreateState);
1456     if (!autoCreateState)
1457     {
1458         notifyState = FALSE;
1459     }
1460     gtk_toggle_button_set_active (sxed->notifyOpt, notifyState);
1461 
1462     /* Do days-in-advance-to-create widget[s] setup. */
1463     if (sxed->newsxP)
1464     {
1465         daysInAdvance =
1466             gnc_prefs_get_float (GNC_PREFS_GROUP_SXED, GNC_PREF_CREATE_DAYS);
1467     }
1468     else
1469     {
1470         daysInAdvance =
1471             xaccSchedXactionGetAdvanceCreation (sxed->sx);
1472     }
1473     if (daysInAdvance != 0)
1474     {
1475         gtk_toggle_button_set_active (sxed->advanceOpt, TRUE);
1476         gtk_spin_button_set_value (sxed->advanceSpin,
1477                                    (gfloat)daysInAdvance);
1478     }
1479 
1480     /* Do days-in-advance-to-remind widget[s] setup. */
1481     if (sxed->newsxP)
1482     {
1483         daysInAdvance =
1484             gnc_prefs_get_float (GNC_PREFS_GROUP_SXED, GNC_PREF_REMIND_DAYS);
1485     }
1486     else
1487     {
1488         daysInAdvance =
1489             xaccSchedXactionGetAdvanceReminder (sxed->sx);
1490     }
1491     if (daysInAdvance != 0)
1492     {
1493         gtk_toggle_button_set_active (sxed->remindOpt, TRUE);
1494         gtk_spin_button_set_value (sxed->remindSpin,
1495                                    (gfloat)daysInAdvance);
1496     }
1497 
1498     if (sxed->newsxP)
1499     {
1500         gnc_sx_set_instance_count (sxed->sx, 1);
1501     }
1502 
1503     /* populate the ledger */
1504     {
1505         /* create the split list */
1506         GList *splitList = xaccSchedXactionGetSplits (sxed->sx);
1507         if (splitList)
1508         {
1509             splitReg = gnc_ledger_display_get_split_register (sxed->ledger);
1510             gnc_split_register_load (splitReg, splitList, NULL);
1511         } /* otherwise, use the existing stuff. */
1512     }
1513 
1514     /* Update the example cal */
1515     gnc_sxed_update_cal (sxed);
1516 }
1517 
1518 
1519 static void
set_endgroup_toggle_states(GncSxEditorDialog * sxed,EndType type)1520 set_endgroup_toggle_states (GncSxEditorDialog *sxed, EndType type)
1521 {
1522     sxed->end_type = type;
1523     update_sensitivity (sxed);
1524 }
1525 
1526 
1527 static void
endgroup_rb_toggled_cb(GtkButton * b,gpointer d)1528 endgroup_rb_toggled_cb (GtkButton *b, gpointer d)
1529 {
1530     /* figure out which one */
1531     GncSxEditorDialog *sxed;
1532     gint id;
1533 
1534     sxed = (GncSxEditorDialog*)d;
1535     id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (b), "whichOneAmI"));
1536 
1537     switch (id)
1538     {
1539         case END_NEVER_OPTION:
1540             set_endgroup_toggle_states (sxed, END_NEVER);
1541             break;
1542         case END_DATE_OPTION:
1543             set_endgroup_toggle_states (sxed, END_DATE);
1544             break;
1545         case NUM_OCCUR_OPTION:
1546             set_endgroup_toggle_states (sxed, END_OCCUR);
1547             break;
1548         default:
1549             g_critical ("Unknown id %d", id);
1550             break;
1551     }
1552     gnc_sxed_update_cal (sxed);
1553 }
1554 
1555 
1556 /********************************************************************\
1557  * gnc_register_check_close                                         *
1558  *                                                                  *
1559  * Args:   regData - the data struct for this register              *
1560  * Return: none                                                     *
1561 \********************************************************************/
1562 static void
gnc_sxed_reg_check_close(GncSxEditorDialog * sxed)1563 gnc_sxed_reg_check_close (GncSxEditorDialog *sxed)
1564 {
1565     gboolean pending_changes;
1566     SplitRegister *reg;
1567     const char *message =
1568         _("The current template transaction "
1569           "has been changed. "
1570           "Would you like to record the changes?");
1571 
1572     reg = gnc_ledger_display_get_split_register (sxed->ledger);
1573     pending_changes = gnc_split_register_changed (reg);
1574     if (!pending_changes)
1575     {
1576         return;
1577     }
1578 
1579     if (gnc_verify_dialog (GTK_WINDOW (sxed->dialog), TRUE, "%s", message))
1580     {
1581         if (!gnc_split_register_save (reg, TRUE))
1582             return;
1583 
1584         gnc_split_register_redraw (reg);
1585     }
1586     else
1587     {
1588         gnc_split_register_cancel_cursor_trans_changes (reg);
1589     }
1590 }
1591 
1592 
1593 static gboolean
editor_component_sx_equality(gpointer find_data,gpointer user_data)1594 editor_component_sx_equality (gpointer find_data,
1595                               gpointer user_data)
1596 {
1597     return ((SchedXaction*)find_data
1598             == ((GncSxEditorDialog*)user_data)->sx);
1599 }
1600 
1601 static void
gnc_sxed_update_cal(GncSxEditorDialog * sxed)1602 gnc_sxed_update_cal (GncSxEditorDialog *sxed)
1603 {
1604     GList *recurrences = NULL;
1605     GDate start_date, first_date;
1606 
1607     g_date_clear (&start_date, 1);
1608 
1609     gnc_frequency_save_to_recurrence (sxed->gncfreq, &recurrences, &start_date);
1610     recurrenceListNextInstance (recurrences, &start_date, &first_date);
1611 
1612     /* Deal with the fact that this SX may have been run before [the
1613      * calendar should only show upcoming instances]... */
1614     {
1615         const GDate *last_sx_inst;
1616 
1617         last_sx_inst = xaccSchedXactionGetLastOccurDate (sxed->sx);
1618         if (g_date_valid (last_sx_inst)
1619             && g_date_valid (&first_date)
1620             && g_date_compare (last_sx_inst, &first_date) > 0)
1621         {
1622             /* last occurrence will be passed as initial date to update store
1623              * later on as well, but only if it's past first_date */
1624             start_date = *last_sx_inst;
1625             recurrenceListNextInstance (recurrences, &start_date, &first_date);
1626         }
1627         else
1628             /* move one day back so the store can get the proper first recurrence. */
1629             g_date_subtract_days (&start_date, 1);
1630 
1631     }
1632 
1633     if (!g_date_valid (&first_date))
1634     {
1635         /* Nothing to do. */
1636         gnc_dense_cal_store_clear (sxed->dense_cal_model);
1637         goto cleanup;
1638     }
1639 
1640     gnc_dense_cal_store_update_name (sxed->dense_cal_model, xaccSchedXactionGetName (sxed->sx));
1641     {
1642         gchar *schedule_desc = recurrenceListToCompactString (recurrences);
1643         gnc_dense_cal_store_update_info (sxed->dense_cal_model, schedule_desc);
1644         g_free (schedule_desc);
1645     }
1646 
1647     //gnc_dense_cal_set_month (sxed->example_cal, g_date_get_month (&first_date));
1648     //gnc_dense_cal_set_year (sxed->example_cal, g_date_get_year (&first_date));
1649 
1650     /* figure out the end restriction */
1651     if (gtk_toggle_button_get_active (sxed->optEndDate))
1652     {
1653         GDate end_date;
1654         g_date_clear (&end_date, 1);
1655         gnc_gdate_set_time64 (&end_date, gnc_date_edit_get_date (sxed->endDateEntry));
1656         gnc_dense_cal_store_update_recurrences_date_end (sxed->dense_cal_model, &start_date, recurrences, &end_date);
1657     }
1658     else if (gtk_toggle_button_get_active (sxed->optEndNone))
1659     {
1660         gnc_dense_cal_store_update_recurrences_no_end (sxed->dense_cal_model, &start_date, recurrences);
1661     }
1662     else if (gtk_toggle_button_get_active (sxed->optEndCount))
1663     {
1664         gint num_remain
1665             = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (sxed->endRemainSpin));
1666         gnc_dense_cal_store_update_recurrences_count_end (sxed->dense_cal_model, &start_date, recurrences, num_remain);
1667     }
1668     else
1669     {
1670         g_error ("unknown end condition");
1671     }
1672 
1673  cleanup:
1674     recurrenceListFree (&recurrences);
1675 }
1676 
1677 
1678 static void
gnc_sxed_freq_changed(GncFrequency * gf,gpointer ud)1679 gnc_sxed_freq_changed (GncFrequency *gf, gpointer ud)
1680 {
1681     gnc_sxed_update_cal ((GncSxEditorDialog*)ud);
1682 }
1683 
1684 
1685 static void
sxed_excal_update_adapt_cb(GtkWidget * o,gpointer ud)1686 sxed_excal_update_adapt_cb (GtkWidget *o, gpointer ud)
1687 {
1688     gnc_sxed_update_cal ((GncSxEditorDialog*)ud);
1689 }
1690 
1691 
1692 void
on_sx_check_toggled_cb(GtkWidget * togglebutton,gpointer user_data)1693 on_sx_check_toggled_cb (GtkWidget *togglebutton, gpointer user_data)
1694 {
1695     GtkWidget *widget_auto;
1696     GtkWidget *widget_notify;
1697     GHashTable *table;
1698 
1699     PINFO ("Togglebutton is %p and user_data is %p", togglebutton, user_data);
1700     PINFO ("Togglebutton builder name is %s", gtk_buildable_get_name (GTK_BUILDABLE (togglebutton)));
1701 
1702     /* We need to use the hash table to find the required widget to activate. */
1703     table = g_object_get_data (G_OBJECT (user_data), "prefs_widget_hash");
1704 
1705     /* "Auto-create" enables "notify before creation" setting */
1706     widget_auto = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_SXED "/" GNC_PREF_CREATE_AUTO);
1707     widget_notify = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_SXED "/" GNC_PREF_NOTIFY);
1708 
1709     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget_auto)))
1710         gtk_widget_set_sensitive (widget_notify, TRUE);
1711     else
1712         gtk_widget_set_sensitive (widget_notify, FALSE);
1713 
1714     /* "Run when opened" enables "show notification window" setting */
1715     widget_auto = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_STARTUP "/" GNC_PREF_RUN_AT_FOPEN);
1716     widget_notify = g_hash_table_lookup (table, "pref/" GNC_PREFS_GROUP_STARTUP "/" GNC_PREF_SHOW_AT_FOPEN);
1717 
1718     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget_auto)))
1719         gtk_widget_set_sensitive (widget_notify, TRUE);
1720     else
1721         gtk_widget_set_sensitive (widget_notify, FALSE);
1722 }
1723 
1724 
1725 /* ------------------------------------------------------------ */
1726 /* sx app engine;  move to somewhere appropriate. :/            */
1727 
1728 typedef struct _acct_deletion_handler_data
1729 {
1730     GList *affected_sxes;
1731     GtkWidget *dialog;
1732     GtkWindow *parent;
1733 } acct_deletion_handler_data;
1734 
1735 
1736 static void
_open_editors(GtkDialog * dialog,gint response_code,gpointer data)1737 _open_editors (GtkDialog *dialog, gint response_code, gpointer data)
1738 {
1739     acct_deletion_handler_data *adhd = (acct_deletion_handler_data *)data;
1740     gtk_widget_hide (adhd->dialog);
1741     {
1742         GList *sx_iter;
1743         for (sx_iter = adhd->affected_sxes; sx_iter; sx_iter = sx_iter->next)
1744         {
1745             gnc_ui_scheduled_xaction_editor_dialog_create (GTK_WINDOW (adhd->parent),
1746                 (SchedXaction*)sx_iter->data, FALSE);
1747         }
1748     }
1749     g_list_free (adhd->affected_sxes);
1750     gtk_widget_destroy (GTK_WIDGET (adhd->dialog));
1751     g_free (adhd);
1752 }
1753 
1754 
1755 static void
_sx_engine_event_handler(QofInstance * ent,QofEventId event_type,gpointer user_data,gpointer evt_data)1756 _sx_engine_event_handler (QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data)
1757 {
1758     Account *acct;
1759     QofBook *book;
1760     GList *affected_sxes;
1761 
1762     if (!(event_type & QOF_EVENT_DESTROY))
1763         return;
1764     if (!GNC_IS_ACCOUNT (ent))
1765         return;
1766     acct = GNC_ACCOUNT (ent);
1767     book = qof_instance_get_book (QOF_INSTANCE (acct));
1768     affected_sxes = gnc_sx_get_sxes_referencing_account (book, acct);
1769 
1770     if (!gnc_list_length_cmp (affected_sxes, 0))
1771         return;
1772 
1773     {
1774         GList *sx_iter;
1775         acct_deletion_handler_data *data;
1776         GtkBuilder *builder;
1777         GtkWidget *dialog;
1778         GtkWindow *parent;
1779         GtkListStore *name_list;
1780         GtkTreeView *list;
1781         GtkTreeViewColumn *name_column;
1782         GtkCellRenderer *renderer;
1783 
1784         builder = gtk_builder_new ();
1785         gnc_builder_add_from_file (builder, "dialog-sx.glade", "account_deletion_dialog");
1786 
1787         dialog = GTK_WIDGET (gtk_builder_get_object (builder, "account_deletion_dialog"));
1788         parent = gnc_ui_get_main_window (NULL);
1789 
1790         gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
1791 
1792         list = GTK_TREE_VIEW (gtk_builder_get_object (builder, "sx_list"));
1793 
1794         // Set grid lines option to preference
1795         gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (list), gnc_tree_view_get_grid_lines_pref ());
1796 
1797         data = (acct_deletion_handler_data*)g_new0(acct_deletion_handler_data, 1);
1798         data->dialog = dialog;
1799         data->parent = parent;
1800         data->affected_sxes = affected_sxes;
1801         name_list = gtk_list_store_new (1, G_TYPE_STRING);
1802         for (sx_iter = affected_sxes; sx_iter; sx_iter = sx_iter->next)
1803         {
1804             SchedXaction *sx;
1805             GtkTreeIter iter;
1806             gchar *sx_name;
1807 
1808             sx = (SchedXaction*)sx_iter->data;
1809             sx_name = xaccSchedXactionGetName (sx);
1810             gtk_list_store_append (name_list, &iter);
1811             gtk_list_store_set (name_list, &iter, 0, sx_name, -1);
1812         }
1813         gtk_tree_view_set_model (list, GTK_TREE_MODEL (name_list));
1814         g_object_unref (G_OBJECT (name_list));
1815 
1816         renderer = gtk_cell_renderer_text_new ();
1817         name_column = gtk_tree_view_column_new_with_attributes (_("Name"),
1818                                                                 renderer,
1819                                                                 "text", 0, NULL);
1820         gtk_tree_view_append_column (list, name_column);
1821 
1822         g_signal_connect (G_OBJECT (dialog), "response",
1823                           G_CALLBACK (_open_editors), data);
1824 
1825         gtk_widget_show_all (GTK_WIDGET (dialog));
1826         gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, data);
1827         g_object_unref (G_OBJECT (builder));
1828     }
1829 }
1830 
1831 
1832 void
gnc_ui_sx_initialize(void)1833 gnc_ui_sx_initialize (void)
1834 {
1835     _sx_engine_event_handler_id = qof_event_register_handler (_sx_engine_event_handler, NULL);
1836 
1837     gnc_hook_add_dangler (HOOK_BOOK_OPENED,
1838                           (GFunc)gnc_sx_sxsincelast_book_opened, NULL, NULL);
1839 
1840     /* Add page to preferences page for Scheduled Transactions */
1841     /* The parameters are; glade file, items to add from glade file - last being the dialog, preference tab name */
1842     gnc_preferences_add_page ("dialog-sx.glade",
1843                               "create_days_adj,remind_days_adj,sx_prefs",
1844                               _("Scheduled Transactions"));
1845 }
1846