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 ¬ifyState);
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