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