GnuCash  5.6-150-g038405b370+
window-reconcile.cpp
1 /********************************************************************\
2  * window-reconcile.c -- the reconcile window *
3  * Copyright (C) 1997 Robin D. Clark *
4  * Copyright (C) 1998-2000 Linas Vepstas *
5  * Copyright (C) 2002 Christian Stimming *
6  * Copyright (C) 2006 David Hampton *
7  * *
8  * This program is free software; you can redistribute it and/or *
9  * modify it under the terms of the GNU General Public License as *
10  * published by the Free Software Foundation; either version 2 of *
11  * the License, or (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License*
19  * along with this program; if not, contact: *
20  * *
21  * Free Software Foundation Voice: +1-617-542-5942 *
22  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23  * Boston, MA 02110-1301, USA gnu@gnu.org *
24  * *
25  * Author: Rob Clark *
26  * Internet: rclark@cs.hmc.edu *
27  * Address: 609 8th Street *
28  * Huntington Beach, CA 92648-4632 *
29 \********************************************************************/
30 
31 #include <config.h>
32 
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35 #ifdef __G_IR_SCANNER__
36 #undef __G_IR_SCANNER__
37 #endif
38 #include <gdk/gdkkeysyms.h>
39 
40 #include <algorithm>
41 
42 #include "Account.hpp"
43 #include "Scrub.h"
44 #include "Scrub3.h"
45 #include "dialog-account.h"
46 #include "dialog-transfer.h"
47 #include "dialog-utils.h"
48 #include "gnc-amount-edit.h"
49 #include "gnc-autoclear.h"
50 #include "gnc-component-manager.h"
51 #include "gnc-date.h"
52 #include "gnc-date-edit.h"
53 #include "gnc-event.h"
54 #include "gnc-filepath-utils.h"
55 #include "gnc-gnome-utils.h"
56 #include "gnc-gtk-utils.h"
57 //#include "gnc-main-window.h"
59 #include "gnc-prefs.h"
60 #include "gnc-ui.h"
61 #include "gnc-ui-balances.h"
62 #include "gnc-window.h"
63 #include "reconcile-view.h"
64 #include "window-reconcile.h"
65 #include "gnc-session.h"
66 #ifdef MAC_INTEGRATION
67 #include <gtkmacintegration/gtkosxapplication.h>
68 #endif
69 
70 #define WINDOW_RECONCILE_CM_CLASS "window-reconcile"
71 #define GNC_PREF_AUTO_CC_PAYMENT "auto-cc-payment"
72 #define GNC_PREF_ALWAYS_REC_TO_TODAY "always-reconcile-to-today"
73 
74 
77 {
78  GncGUID account; /* The account that we are reconciling */
79  gnc_numeric new_ending; /* The new ending balance */
80  time64 statement_date; /* The statement date */
81 
82  gint component_id; /* id of component */
83 
84  GtkWidget *window; /* The reconcile window */
85 
86  GtkBuilder *builder; /* The builder object */
87  GSimpleActionGroup *simple_action_group; /* The action group for the window */
88  GtkWidget *autoclear_button;
89  GtkAccelGroup *accel_group;
90 
91  GncPluginPage *page;
92 
93  SplitsVec autoclear_splits;
94  SplitsVec initially_cleared_splits;
95 
96  GtkWidget *starting; /* The starting balance */
97  GtkWidget *ending; /* The ending balance */
98  GtkWidget *recn_date; /* The statement date */
99  GtkWidget *reconciled; /* The reconciled balance */
100  GtkWidget *difference; /* Text field, amount left to reconcile */
101 
102  GtkWidget *total_debit; /* Text field, total debit reconciled */
103  GtkWidget *total_credit; /* Text field, total credit reconciled */
104 
105  GtkWidget *debit; /* Debit matrix show unreconciled debit */
106  GtkWidget *credit; /* Credit matrix, shows credits... */
107 
108  GtkWidget *debit_frame; /* Frame around debit matrix */
109  GtkWidget *credit_frame; /* Frame around credit matrix */
110 
111  gboolean delete_refresh; /* do a refresh upon a window deletion */
112 };
113 
114 
115 /* This structure doesn't contain everything involved in the
116  * startRecnWindow, just pointers that have to be passed in to
117  * callbacks that need more than one piece of data to operate on.
118  * This is also used by the interest transfer dialog code.
119  */
120 typedef struct _startRecnWindowData
121 {
122  Account *account; /* the account being reconciled */
123  GNCAccountType account_type; /* the type of the account */
124 
125  GtkWidget *startRecnWindow; /* the startRecnWindow dialog */
126  GtkWidget *xfer_button; /* the dialog's interest transfer button */
127  GtkWidget *date_value; /* the dialog's ending date field */
128  GtkWidget *future_icon;
129  GtkWidget *future_text;
130  GNCAmountEdit *end_value; /* the dialog's ending balance amount edit */
131  gnc_numeric original_value; /* the dialog's original ending balance */
132  gboolean user_set_value; /* the user changed the ending value */
133 
134  XferDialog *xferData; /* the interest xfer dialog (if it exists) */
135  gboolean include_children;
136 
137  time64 date; /* the interest xfer reconcile date */
139 
141 static gnc_numeric recnRecalculateBalance (RecnWindow *recnData);
142 
143 static void recn_destroy_cb (GtkWidget *w, gpointer data);
144 static void recn_cancel (RecnWindow *recnData);
145 static gboolean recn_delete_cb (GtkWidget *widget, GdkEvent *event, gpointer data);
146 static gboolean recn_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data);
147 static void recnAutoClearCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
148 static void recnFinishCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
149 static void recnPostponeCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
150 static void recnCancelCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data);
151 
152 extern "C" {
153 void gnc_start_recn_children_changed (GtkWidget *widget, startRecnWindowData *data);
154 void gnc_start_recn_interest_clicked_cb (GtkButton *button, startRecnWindowData *data);
155 }
156 
157 static void gnc_reconcile_window_set_sensitivity (RecnWindow *recnData);
158 static char * gnc_recn_make_window_name (Account *account);
159 static void gnc_recn_set_window_name (RecnWindow *recnData);
160 static gboolean find_by_account (gpointer find_data, gpointer user_data);
161 
163 /* This static indicates the debugging module that this .o belongs to. */
164 G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_GUI;
165 
166 static time64 gnc_reconcile_last_statement_date = 0;
167 
170 static gpointer
171 commodity_compare(Account *account, gpointer user_data) {
172  gboolean equal = gnc_commodity_equiv (xaccAccountGetCommodity (account),
173  (gnc_commodity*) user_data);
174 
175  return equal ? NULL : account;
176 }
177 
178 
179 /********************************************************************\
180  * has_account_different_commodities *
181  * *
182  * Args: parent account - the account to look in *
183  * Return: true if there exists a subaccount with different *
184  * commodity then the parent account. *
185 \********************************************************************/
186 static gboolean
187 has_account_different_commodities(const Account *account)
188 {
189  gnc_commodity *parent_commodity;
190  gpointer result;
191 
192  if (account == NULL)
193  return FALSE;
194 
195  parent_commodity = xaccAccountGetCommodity (account);
196 
197  result = gnc_account_foreach_descendant_until (account,
198  commodity_compare,
199  parent_commodity);
200 
201  return result != NULL;
202 }
203 
204 static const char*
205 get_autoclear_icon (GError* error)
206 {
207  static std::unordered_map<gint,const char*> icon_names =
208  {
209  { Autoclear::ABORT_NONE, "media-playback-start" },
210  { Autoclear::ABORT_NOP, "media-playback-stop" },
211  { Autoclear::ABORT_MULTI, "dialog-information" },
212  { Autoclear::ABORT_TIMEOUT, "dialog-error" },
213  { Autoclear::ABORT_UNREACHABLE, "dialog-error" },
214  };
215  auto it = icon_names.find (error ? error->code : Autoclear::ABORT_NONE);
216  return it == icon_names.end() ? "dialog-information" : it->second;
217 }
218 
219 #define GNC_PREF_ENABLE_AUTOCLEAR "enable-autoclear-in-reconcile"
220 
221 static void
222 calculate_autoclear (RecnWindow *recnData)
223 {
224  g_return_if_fail (recnData);
225 
226  bool enabled = gnc_prefs_get_bool (GNC_PREFS_GROUP_RECONCILE, GNC_PREF_ENABLE_AUTOCLEAR);
227  auto action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group),
228  "RecnAutoClearAction");
229  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), enabled);
230  gtk_widget_set_visible (recnData->autoclear_button, enabled);
231  if (!enabled)
232  return;
233 
234  GError* error = nullptr;
235  Account* acct = xaccAccountLookup (&recnData->account, gnc_get_current_book ());
236 
237  static const unsigned int MAX_AUTOCLEAR_SECONDS = 1;
238 
239  GList *splits_to_clear = gnc_account_get_autoclear_splits
240  (acct, recnData->new_ending, recnData->statement_date, &error, MAX_AUTOCLEAR_SECONDS);
241 
242  gtk_widget_set_sensitive (recnData->autoclear_button, error == nullptr);
243 
244  gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (recnData->autoclear_button),
245  get_autoclear_icon (error));
246 
247  recnData->autoclear_splits = recnData->initially_cleared_splits;
248  for (auto n = splits_to_clear; n; n = n->next)
249  recnData->autoclear_splits.push_back (GNC_SPLIT (n->data));
250 
251  if (error)
252  {
253  gtk_widget_set_tooltip_text (recnData->autoclear_button, _(error->message));
254  g_error_free (error);
255  return;
256  }
257 
258  auto num_splits = g_list_length (splits_to_clear);
259  char date_buff[MAX_DATE_LENGTH+1];
260  qof_print_date_buff (date_buff, MAX_DATE_LENGTH, recnData->statement_date);
261  char* tooltip = g_strdup_printf
262  (ngettext("Automatically select %u transaction up to %s that clears to %s",
263  "Automatically select %u transactions up to %s that clear to %s",
264  num_splits),
265  num_splits, date_buff,
266  xaccPrintAmount (recnData->new_ending, gnc_account_print_info (acct, true)));
267  gtk_widget_set_tooltip_text (recnData->autoclear_button, tooltip);
268 
269  g_free (tooltip);
270  g_list_free (splits_to_clear);
271 }
272 
273 /********************************************************************\
274  * recnRefresh *
275  * refreshes the transactions in the reconcile window *
276  * *
277  * Args: account - the account of the reconcile window to refresh *
278  * Return: none *
279 \********************************************************************/
280 static void
281 recnRefresh (RecnWindow *recnData)
282 {
283  if (recnData == NULL)
284  return;
285 
286  gnc_reconcile_view_refresh(GNC_RECONCILE_VIEW(recnData->debit));
287  gnc_reconcile_view_refresh(GNC_RECONCILE_VIEW(recnData->credit));
288 
289  gnc_reconcile_window_set_sensitivity(recnData);
290 
291  gnc_recn_set_window_name(recnData);
292 
293  recnRecalculateBalance(recnData);
294 
295  gtk_widget_queue_resize(recnData->window);
296 }
297 
298 
299 static Account *
300 recn_get_account (RecnWindow *recnData)
301 {
302  if (!recnData)
303  return NULL;
304 
305  return xaccAccountLookup (&recnData->account, gnc_get_current_book ());
306 }
307 
308 
309 static void
310 gnc_add_colorized_amount (gpointer obj, gnc_numeric amt,
311  GNCPrintAmountInfo print_info, gboolean reverse)
312 {
313  if (!obj) return;
314  if (reverse) amt = gnc_numeric_neg (amt);
315  gnc_set_label_color (GTK_WIDGET (obj), amt);
316  gtk_label_set_text (GTK_LABEL (obj), xaccPrintAmount (amt, print_info));
317 }
318 
319 /********************************************************************\
320  * recnRecalculateBalance *
321  * refreshes the balances in the reconcile window *
322  * *
323  * Args: recnData -- the reconcile window to refresh *
324  * Return: the difference between the nominal ending balance *
325  * and the 'effective' ending balance. *
326 \********************************************************************/
327 static gnc_numeric
328 recnRecalculateBalance (RecnWindow *recnData)
329 {
330  Account *account;
331  gnc_numeric debit;
332  gnc_numeric credit;
333  gnc_numeric starting;
334  gnc_numeric ending;
335  gnc_numeric reconciled;
336  gnc_numeric diff;
337  gchar *datestr;
338  GNCPrintAmountInfo print_info;
339  gboolean reverse_balance, include_children;
340  GAction *action;
341 
342  account = recn_get_account (recnData);
343  if (!account)
344  return gnc_numeric_zero ();
345 
346  reverse_balance = gnc_reverse_balance(account);
347  include_children = xaccAccountGetReconcileChildrenStatus(account);
348  starting = gnc_ui_account_get_reconciled_balance(account, include_children);
349  print_info = gnc_account_print_info (account, TRUE);
350 
351  ending = recnData->new_ending;
352  debit = gnc_reconcile_view_reconciled_balance
353  (GNC_RECONCILE_VIEW(recnData->debit));
354  credit = gnc_reconcile_view_reconciled_balance
355  (GNC_RECONCILE_VIEW(recnData->credit));
356 
357  reconciled = gnc_numeric_sub_fixed (debit, credit);
358  if (reverse_balance)
359  reconciled = gnc_numeric_sub_fixed (reconciled, starting);
360  else
361  reconciled = gnc_numeric_add_fixed (reconciled, starting);
362 
363  diff = gnc_numeric_sub_fixed (ending, reconciled);
364 
365  datestr = qof_print_date (recnData->statement_date);
366  gtk_label_set_text (GTK_LABEL(recnData->recn_date), datestr);
367  g_free (datestr);
368 
369  gnc_add_colorized_amount (recnData->starting, starting, print_info, FALSE);
370  gnc_add_colorized_amount (recnData->ending, ending, print_info, reverse_balance);
371  gnc_add_colorized_amount (recnData->total_debit, debit, print_info, FALSE);
372  gnc_add_colorized_amount (recnData->total_credit, credit, print_info, FALSE);
373  gnc_add_colorized_amount (recnData->reconciled, reconciled, print_info, reverse_balance);
374  gnc_add_colorized_amount (recnData->difference, diff, print_info, reverse_balance);
375 
376  action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group),
377  "RecnFinishAction");
378  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), gnc_numeric_zero_p (diff));
379 
380  action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group),
381  "TransBalanceAction");
382  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), !gnc_numeric_zero_p (diff));
383 
384  calculate_autoclear (recnData);
385 
386  return diff;
387 }
388 
389 /* amount_edit_cb
390  * Callback on activate event for statement Ending Balance.
391  * Sets the user_set_value flag true if the amount entered is
392  * different to the calculated Ending Balance as at the default
393  * Statement Date. This prevents the entered Ending Balance
394  * being recalculated if the Statement Date is changed.
395  *
396  * Args: widget - Ending Balance widget
397  * data - structure containing info about this
398  * reconciliation process.
399  */
400 static void
401 amount_edit_cb(GtkWidget *widget, startRecnWindowData *data)
402 {
403  gnc_numeric value;
404  gint result = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(data->end_value),
405  &value, TRUE, NULL);
406 
407  data->user_set_value = FALSE;
408 
409  if (result < 1) // OK
410  {
411  if (result == -1) // blank entry is valid
412  {
413  gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT(data->end_value), value);
414  gnc_amount_edit_select_region (GNC_AMOUNT_EDIT(data->end_value), 0, -1);
415  }
416  data->user_set_value = !gnc_numeric_equal (value, data->original_value);
417  }
418 }
419 
420 /* amount_edit_focus_out_cb
421  * Callback on focus-out event for statement Ending Balance.
422  *
423  * Args: widget - Ending Balance widget
424  * event - event triggering this callback
425  * data - structure containing info about this
426  * reconciliation process.
427  * Returns: False - propagate the event to the widget's parent.
428  */
429 static gboolean
430 amount_edit_focus_out_cb(GtkWidget *widget, GdkEventFocus *event,
431  startRecnWindowData *data)
432 {
433  amount_edit_cb(widget, data);
434  return FALSE;
435 }
436 
437 
438 /* recn_date_changed_cb
439  * Callback on date_changed event for Statement Date.
440  * If the user changed the date edit widget, and the Ending
441  * Balance wasn't entered, update the Ending Balance to reflect
442  * the ending balance of the account as at Statement Date.
443  *
444  * Args: widget - Statement Date edit widget
445  * data - structure containing info about this
446  * reconciliation.
447  * Returns: none.
448  */
449 static void
450 recn_date_changed_cb (GtkWidget *widget, startRecnWindowData *data)
451 {
452  GNCDateEdit *gde = GNC_DATE_EDIT (widget);
453  gnc_numeric new_balance;
454  time64 new_date;
455 
456  gboolean show_warning = FALSE;
457  gint days_after_today;
458  static const time64 secs_per_day = 86400;
459  static const time64 secs_per_hour = 3600;
460 
461  new_date = gnc_date_edit_get_date_end (gde);
462 
463  /* Add secs_per_hour to the difference to compensate for the short
464  * day when transitioning from standard to daylight time.
465  */
466  days_after_today = (gnc_time64_get_day_end (new_date) -
468  secs_per_hour) / secs_per_day;
469 
470  if (days_after_today > 0)
471  {
472  gchar *str = g_strdup_printf
473  /* Translators: %d is the number of days in the future */
474  (ngettext ("Statement Date is %d day after today.",
475  "Statement Date is %d days after today.",
476  days_after_today),
477  days_after_today);
478 
479  gchar *tip_start = g_strdup_printf
480  /* Translators: %d is the number of days in the future */
481  (ngettext ("The statement date you have chosen is %d day in the future.",
482  "The statement date you have chosen is %d days in the future.",
483  days_after_today),
484  days_after_today);
485 
486  gchar *tip_end = g_strdup (_("This may cause issues for future reconciliation \
487 actions on this account. Please double-check this is the date you intended."));
488  gchar *tip = g_strdup_printf ("%s %s", tip_start, tip_end);
489 
490  show_warning = TRUE;
491 
492  gtk_label_set_text (GTK_LABEL(data->future_text), str);
493  gtk_widget_set_tooltip_text (GTK_WIDGET(data->future_text), tip);
494  g_free (str);
495  g_free (tip_end);
496  g_free (tip_start);
497  g_free (tip);
498  }
499  gtk_widget_set_visible (GTK_WIDGET(data->future_icon), show_warning);
500  gtk_widget_set_visible (GTK_WIDGET(data->future_text), show_warning);
501 
502  if (data->user_set_value)
503  return;
504 
505  /* get the balance for the account as of the new date */
506  new_balance = gnc_ui_account_get_balance_as_of_date (data->account, new_date,
507  data->include_children);
508  /* update the amount edit with the amount */
509  gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT (data->end_value),
510  new_balance);
511 }
512 
513 
514 void
515 gnc_start_recn_children_changed (GtkWidget *widget, startRecnWindowData *data)
516 {
517  data->include_children =
518  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
519 
520  /* Force an update of the ending balance */
521  recn_date_changed_cb (data->date_value, data);
522 }
523 
524 
525 /********************************************************************\
526  * recnInterestXferWindow *
527  * opens up a window to prompt the user to enter an interest *
528  * charge or payment for an account prior to reconciling it. *
529  * Only to be called for some types of accounts, as defined *
530  * in the macros at the top of this file. *
531  * *
532  * NOTE: This function does not return until the user presses "Ok" *
533  * or "Cancel", which means that the transaction must be *
534  * resolved before the startRecnWindow will work. *
535  * *
536  * Args: data - jumbo structure containing info *
537  * about the start of the reconcile *
538  * process needed by this function. *
539  * Returns: none. *
540 \********************************************************************/
541 
542 /* helper function */
543 static char *
544 gnc_recn_make_interest_window_name(Account *account, char *text)
545 {
546  char *fullname;
547  char *title;
548 
549  fullname = gnc_account_get_full_name(account);
550  title = g_strconcat(fullname, " - ", text && *text ? _(text) : "", NULL);
551 
552  g_free(fullname);
553 
554  return title;
555 }
556 
557 
558 static void
559 recnInterestXferWindow( startRecnWindowData *data)
560 {
561  gchar *title;
562 
563  if ( !account_type_has_auto_interest_xfer( data->account_type ) )
564  return;
565 
566  /* get a normal transfer dialog... */
567  data->xferData = gnc_xfer_dialog( GTK_WIDGET(data->startRecnWindow),
568  data->account );
569 
570  /* ...and start changing things: */
571 
572  /* change title */
573  if ( account_type_has_auto_interest_payment( data->account_type ) )
574  title = gnc_recn_make_interest_window_name( data->account,
575  _("Interest Payment") );
576  else
577  title = gnc_recn_make_interest_window_name( data->account,
578  _("Interest Charge") );
579 
580  gnc_xfer_dialog_set_title( data->xferData, title );
581  g_free( title );
582 
583 
584  /* change frame labels */
585  gnc_xfer_dialog_set_information_label( data->xferData,
586  _("Payment Information") );
587 
588  /* Interest accrued is a transaction from an income account
589  * to a bank account. Interest charged is a transaction from
590  * a credit account to an expense account. The user isn't allowed
591  * to change the account (bank or credit) being reconciled.
592  */
593  if ( account_type_has_auto_interest_payment( data->account_type ) )
594  {
595  gnc_xfer_dialog_set_from_account_label( data->xferData,
596  _("Payment From") );
597  gnc_xfer_dialog_set_from_show_button_active( data->xferData, TRUE );
598 
599  // XXX: Set "from" account from previous interest payment.
600 
601  gnc_xfer_dialog_set_to_account_label( data->xferData,
602  _("Reconcile Account") );
603  gnc_xfer_dialog_select_to_account( data->xferData, data->account );
604  gnc_xfer_dialog_lock_to_account_tree( data->xferData );
605 
606  /* Quickfill based on the reconcile account, which is the "To" acct. */
607  gnc_xfer_dialog_quickfill_to_account( data->xferData, TRUE );
608  }
609  else /* interest charged to account rather than paid to it */
610  {
611  gnc_xfer_dialog_set_from_account_label( data->xferData,
612  _("Reconcile Account") );
613  gnc_xfer_dialog_select_from_account( data->xferData, data->account );
614  gnc_xfer_dialog_lock_from_account_tree( data->xferData );
615 
616  gnc_xfer_dialog_set_to_account_label( data->xferData,
617  _("Payment To") );
618  gnc_xfer_dialog_set_to_show_button_active( data->xferData, TRUE );
619 
620  // XXX: Set "to" account from previous interest payment.
621 
622  /* Quickfill based on the reconcile account, which is the "From" acct. */
623  gnc_xfer_dialog_quickfill_to_account( data->xferData, FALSE );
624  }
625 
626  /* no currency frame */
627  gnc_xfer_dialog_toggle_currency_table( data->xferData, FALSE );
628 
629  /* set the reconcile date for the transaction date */
630  gnc_xfer_dialog_set_date( data->xferData, data->date );
631 
632  /* Now run the transfer dialog. This blocks until done.
633  * If the user hit Cancel, make the button clickable so that
634  * the user can retry if they want. We don't make the button
635  * clickable if they successfully entered a transaction, since
636  * the fact that the button was clickable again might make
637  * the user think that the transaction didn't actually go through.
638  */
639  if ( ! gnc_xfer_dialog_run_until_done( data->xferData ) )
640  if ( data->xfer_button )
641  gtk_widget_set_sensitive(GTK_WIDGET(data->xfer_button), TRUE);
642 
643  /* done with the XferDialog */
644  data->xferData = NULL;
645 }
646 
647 
648 /* Set up for the interest xfer window, run the window, and update
649  * the startRecnWindow if the interest xfer changed anything that matters.
650  */
651 static void
652 gnc_reconcile_interest_xfer_run(startRecnWindowData *data)
653 {
654  GtkWidget *entry = gnc_amount_edit_gtk_entry(
655  GNC_AMOUNT_EDIT(data->end_value) );
656  gnc_numeric before = gnc_amount_edit_get_amount(
657  GNC_AMOUNT_EDIT(data->end_value) );
658  gnc_numeric after;
659 
660  recnInterestXferWindow( data );
661 
662  /* recompute the ending balance */
663  after = xaccAccountGetBalanceAsOfDate(data->account, data->date);
664 
665  /* update the ending balance in the startRecnWindow if it has changed. */
666  if ( gnc_numeric_compare( before, after ) )
667  {
668  if (gnc_reverse_balance(data->account))
669  after = gnc_numeric_neg (after);
670 
671  gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT (data->end_value), after);
672  gtk_widget_grab_focus(GTK_WIDGET(entry));
673  gtk_editable_select_region (GTK_EDITABLE(entry), 0, -1);
674  data->original_value = after;
675  data->user_set_value = FALSE;
676  }
677 }
678 
679 
680 void
681 gnc_start_recn_interest_clicked_cb(GtkButton *button, startRecnWindowData *data)
682 {
683  /* make the button unclickable since we're popping up the window */
684  if ( data->xfer_button )
685  gtk_widget_set_sensitive(GTK_WIDGET(data->xfer_button), FALSE);
686 
687  /* run the account window */
688  gnc_reconcile_interest_xfer_run( data );
689 }
690 
691 
692 static void
693 gnc_save_reconcile_interval(Account *account, time64 statement_date)
694 {
695  time64 prev_statement_date;
696  int days = 0, months = 0;
697 
698  if (!xaccAccountGetReconcileLastDate (account, &prev_statement_date))
699  return;
700 
701  /*
702  * Compute the number of days difference.
703  */
704  auto seconds = statement_date - prev_statement_date;
705  days = seconds / 60 / 60 / 24;
706 
707  /*
708  * See if we need to remember days(weeks) or months. The only trick
709  * value is 28 days which could be either 4 weeks or 1 month.
710  */
711  if (days == 28)
712  {
713  int prev_days = 0, prev_months = 1;
714 
715  /* What was it last time? */
716  xaccAccountGetReconcileLastInterval (account, &prev_months, &prev_days);
717  if (prev_months == 1)
718  {
719  months = 1;
720  days = 0;
721  }
722  }
723  else if (days > 28)
724  {
725  struct tm current, prev;
726 
727  gnc_localtime_r (&statement_date, &current);
728  gnc_localtime_r (&prev_statement_date, &prev);
729  months = ((12 * current.tm_year + current.tm_mon) -
730  (12 * prev.tm_year + prev.tm_mon));
731  days = 0;
732  }
733 
734  /*
735  * Remember for next time unless it is negative.
736  */
737  if (months >= 0 && days >= 0)
738  xaccAccountSetReconcileLastInterval(account, months, days);
739 }
740 
741 
742 /********************************************************************\
743  * startRecnWindow *
744  * opens up the window to prompt the user to enter the ending *
745  * balance from bank statement *
746  * *
747  * NOTE: This function does not return until the user presses "Ok" *
748  * or "Cancel" *
749  * *
750  * Args: parent - the parent of this window *
751  * account - the account to reconcile *
752  * new_ending - returns the amount for ending balance *
753  * statement_date - returns date of the statement :) *
754  * Return: True, if the user presses "Ok", else False *
755 \********************************************************************/
756 static gboolean
757 startRecnWindow(GtkWidget *parent, Account *account,
758  gnc_numeric *new_ending, time64 *statement_date,
759  gboolean enable_subaccount)
760 {
761  GtkWidget *dialog, *end_value, *date_value, *include_children_button;
762  GtkBuilder *builder;
763  startRecnWindowData data = { NULL };
764  gboolean auto_interest_xfer_option;
765  GNCPrintAmountInfo print_info;
766  gnc_numeric ending;
767  GtkWidget *entry;
768  char *title;
769  int result = -6;
770  gulong fo_handler_id;
771 
772  /* Initialize the data structure that will be used for several callbacks
773  * throughout this file with the relevant info. Some initialization is
774  * done below as well. Note that local storage should be OK for this,
775  * since any callbacks using it will only work while the startRecnWindow
776  * is running.
777  */
778  data.account = account;
779  data.account_type = xaccAccountGetType(account);
780  data.date = *statement_date;
781 
782  /* whether to have an automatic interest xfer dialog or not */
783  auto_interest_xfer_option = xaccAccountGetAutoInterest (account);
784 
785  data.include_children = !has_account_different_commodities(account) &&
787 
788  ending = gnc_ui_account_get_reconciled_balance(account,
789  data.include_children);
790  print_info = gnc_account_print_info (account, TRUE);
791 
792  /*
793  * Do not reverse the balance here. It messes up the math in the
794  * reconciliation window. Also, the balance should show up as a
795  * positive number in the reconciliation window to match the positive
796  * number that shows in the register window.
797  */
798 
799  /* Create the dialog box */
800  builder = gtk_builder_new();
801  gnc_builder_add_from_file (builder, "window-reconcile.glade", "reconcile_start_dialog");
802 
803  dialog = GTK_WIDGET(gtk_builder_get_object (builder, "reconcile_start_dialog"));
804 
805  // Set the name for this dialog so it can be easily manipulated with css
806  gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-reconcile-start");
807 
808  title = gnc_recn_make_window_name (account);
809  gtk_window_set_title(GTK_WINDOW(dialog), title);
810  g_free (title);
811 
812  data.startRecnWindow = GTK_WIDGET(dialog);
813 
814  if (parent != NULL)
815  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent));
816 
817  {
818  GtkWidget *start_value, *box;
819  GtkWidget *label;
820  GtkWidget *interest = NULL;
821 
822  start_value = GTK_WIDGET(gtk_builder_get_object (builder, "start_value"));
823  gtk_label_set_text(GTK_LABEL(start_value), xaccPrintAmount (ending, print_info));
824 
825  include_children_button = GTK_WIDGET(gtk_builder_get_object (builder, "subaccount_check"));
826  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(include_children_button),
827  data.include_children);
828  gtk_widget_set_sensitive(include_children_button, enable_subaccount);
829 
830  date_value = gnc_date_edit_new(*statement_date, FALSE, FALSE);
831  data.date_value = date_value;
832  box = GTK_WIDGET(gtk_builder_get_object (builder, "date_value_box"));
833  gtk_box_pack_start(GTK_BOX(box), date_value, TRUE, TRUE, 0);
834  label = GTK_WIDGET(gtk_builder_get_object (builder, "date_label"));
835  gnc_date_make_mnemonic_target(GNC_DATE_EDIT(date_value), label);
836 
837  end_value = gnc_amount_edit_new ();
838  data.end_value = GNC_AMOUNT_EDIT(end_value);
839  data.original_value = *new_ending;
840  data.user_set_value = FALSE;
841 
842  data.future_icon = GTK_WIDGET(gtk_builder_get_object (builder, "future_icon"));
843  data.future_text = GTK_WIDGET(gtk_builder_get_object (builder, "future_text"));
844 
845  box = GTK_WIDGET(gtk_builder_get_object (builder, "ending_value_box"));
846  gtk_box_pack_start(GTK_BOX(box), end_value, TRUE, TRUE, 0);
847  label = GTK_WIDGET(gtk_builder_get_object (builder, "end_label"));
848  gnc_amount_edit_make_mnemonic_target (GNC_AMOUNT_EDIT(end_value), label);
849 
850  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, &data);
851 
852  gnc_date_activates_default(GNC_DATE_EDIT(date_value), TRUE);
853 
854  /* need to get a callback on date changes to update the recn balance */
855  g_signal_connect ( G_OBJECT (date_value), "date_changed",
856  G_CALLBACK (recn_date_changed_cb), (gpointer) &data );
857 
858  print_info.use_symbol = 0;
859  gnc_amount_edit_set_print_info (GNC_AMOUNT_EDIT (end_value), print_info);
860  gnc_amount_edit_set_fraction (GNC_AMOUNT_EDIT (end_value),
861  xaccAccountGetCommoditySCU (account));
862 
863  gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT (end_value), *new_ending);
864 
865  entry = gnc_amount_edit_gtk_entry (GNC_AMOUNT_EDIT (end_value));
866  gtk_editable_select_region (GTK_EDITABLE(entry), 0, -1);
867  fo_handler_id = g_signal_connect (G_OBJECT(entry), "focus-out-event",
868  G_CALLBACK(amount_edit_focus_out_cb),
869  (gpointer) &data);
870  g_signal_connect (G_OBJECT(entry), "activate",
871  G_CALLBACK(amount_edit_cb),
872  (gpointer) &data);
873  gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
874 
875  /* if it's possible to enter an interest payment or charge for this
876  * account, add a button so that the user can pop up the appropriate
877  * dialog if it isn't automatically popping up.
878  */
879  interest = GTK_WIDGET(gtk_builder_get_object (builder, "interest_button"));
880  if ( account_type_has_auto_interest_payment( data.account_type ) )
881  gtk_button_set_label(GTK_BUTTON(interest), _("Enter _Interest Payment…") );
882  else if ( account_type_has_auto_interest_charge( data.account_type ) )
883  gtk_button_set_label(GTK_BUTTON(interest), _("Enter _Interest Charge…") );
884  else
885  {
886  gtk_widget_destroy(interest);
887  interest = NULL;
888  }
889 
890  if ( interest )
891  {
892  data.xfer_button = interest;
893  if ( auto_interest_xfer_option )
894  gtk_widget_set_sensitive(GTK_WIDGET(interest), FALSE);
895  }
896 
897  gtk_widget_show_all(dialog);
898 
899  gtk_widget_hide (data.future_text);
900  gtk_widget_hide (data.future_icon);
901 
902  gtk_widget_grab_focus(gnc_amount_edit_gtk_entry
903  (GNC_AMOUNT_EDIT (end_value)));
904  }
905 
906  /* Allow the user to enter an interest payment
907  * or charge prior to reconciling */
908  if ( account_type_has_auto_interest_xfer( data.account_type )
909  && auto_interest_xfer_option )
910  {
911  gnc_reconcile_interest_xfer_run( &data );
912  }
913 
914  while (gtk_dialog_run (GTK_DIALOG(dialog)) == GTK_RESPONSE_OK)
915  {
916  if (gnc_date_edit_get_date_end(GNC_DATE_EDIT(date_value)) != *statement_date)
917  recn_date_changed_cb(date_value, &data);
918 
919  /* If response is OK but end_value not valid, try again */
920  if (gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT(end_value), NULL))
921  {
922  result = GTK_RESPONSE_OK;
923  break;
924  }
925  }
926 
927  if (result == GTK_RESPONSE_OK)
928  {
929  *new_ending = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT (end_value));
930  *statement_date = gnc_date_edit_get_date_end(GNC_DATE_EDIT(date_value));
931 
932  if (gnc_reverse_balance(account))
933  *new_ending = gnc_numeric_neg (*new_ending);
934 
935  xaccAccountSetReconcileChildrenStatus(account, data.include_children);
936 
937  gnc_save_reconcile_interval(account, *statement_date);
938  }
939  // must remove the focus-out handler
940  g_signal_handler_disconnect (G_OBJECT(entry), fo_handler_id);
941  gtk_widget_destroy (dialog);
942  g_object_unref(G_OBJECT(builder));
943 
944  return (result == GTK_RESPONSE_OK);
945 }
946 
947 
948 static void
949 gnc_reconcile_window_set_sensitivity(RecnWindow *recnData)
950 {
951  gboolean sensitive = FALSE;
952  GNCReconcileView *view;
953  GAction *action;
954 
955  view = GNC_RECONCILE_VIEW(recnData->debit);
956  if (gnc_reconcile_view_num_selected(view) == 1)
957  sensitive = TRUE;
958 
959  view = GNC_RECONCILE_VIEW(recnData->credit);
960  if (gnc_reconcile_view_num_selected(view) == 1)
961  sensitive = TRUE;
962 
963  action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group),
964  "TransEditAction");
965  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), sensitive);
966 
967  action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group),
968  "TransDeleteAction");
969  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), sensitive);
970 
971  sensitive = FALSE;
972 
973  view = GNC_RECONCILE_VIEW(recnData->debit);
974  if (gnc_reconcile_view_num_selected(view) > 0)
975  sensitive = TRUE;
976 
977  view = GNC_RECONCILE_VIEW(recnData->credit);
978  if (gnc_reconcile_view_num_selected(view) > 0)
979  sensitive = TRUE;
980 
981  action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group),
982  "TransRecAction");
983  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), sensitive);
984 
985  action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group),
986  "TransUnRecAction");
987  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), sensitive);
988 }
989 
990 
991 static void
992 gnc_reconcile_window_toggled_cb(GNCReconcileView *view, Split *split,
993  gpointer data)
994 {
995  auto recnData = static_cast<RecnWindow*>(data);
996  gnc_reconcile_window_set_sensitivity(recnData);
997  recnRecalculateBalance(recnData);
998 }
999 
1000 
1001 static void
1002 gnc_reconcile_window_row_cb(GNCReconcileView *view, gpointer item,
1003  gpointer data)
1004 {
1005  auto recnData = static_cast<RecnWindow*>(data);
1006  gnc_reconcile_window_set_sensitivity(recnData);
1007 }
1008 
1009 
1022 static void
1023 do_popup_menu(RecnWindow *recnData, GdkEventButton *event)
1024 {
1025  GMenuModel *menu_model = (GMenuModel *)gtk_builder_get_object (recnData->builder,
1026  "recwin-popup");
1027  GtkWidget *menu = gtk_menu_new_from_model (menu_model);
1028 
1029  if (!menu)
1030  return;
1031 
1032  gtk_menu_attach_to_widget (GTK_MENU(menu), GTK_WIDGET(recnData->window), NULL);
1033  gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *) event);
1034 }
1035 
1036 
1050 static gboolean
1051 gnc_reconcile_window_popup_menu_cb (GtkWidget *widget,
1052  RecnWindow *recnData)
1053 {
1054  do_popup_menu(recnData, NULL);
1055  return TRUE;
1056 }
1057 
1058 
1059 /* Callback function invoked when the user clicks in the content of
1060  * any Gnucash window. If this was a "right-click" then Gnucash will
1061  * popup the contextual menu.
1062  */
1063 static gboolean
1064 gnc_reconcile_window_button_press_cb (GtkWidget *widget,
1065  GdkEventButton *event,
1066  RecnWindow *recnData)
1067 {
1068  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1069  {
1070  GNCQueryView *qview = GNC_QUERY_VIEW(widget);
1071  GtkTreePath *path;
1072 
1073  /* Get tree path for row that was clicked */
1074  gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(qview),
1075  (gint) event->x,
1076  (gint) event->y,
1077  &path, NULL, NULL, NULL);
1078 
1079  if (path)
1080  {
1081  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
1082 
1083  if (!gtk_tree_selection_path_is_selected (selection, path))
1084  {
1085  gtk_tree_selection_unselect_all (selection);
1086  gtk_tree_selection_select_path (selection, path);
1087  }
1088  gtk_tree_path_free (path);
1089  }
1090  do_popup_menu (recnData, event);
1091  return TRUE;
1092  }
1093  return FALSE;
1094 }
1095 
1096 
1097 static GNCSplitReg *
1098 gnc_reconcile_window_open_register(RecnWindow *recnData)
1099 {
1100  Account *account = recn_get_account (recnData);
1101  GNCSplitReg *gsr;
1102  gboolean include_children;
1103 
1104  if (!account)
1105  return(NULL);
1106 
1107  include_children = xaccAccountGetReconcileChildrenStatus (account);
1108  recnData->page = gnc_plugin_page_register_new (account, include_children);
1109  gnc_main_window_open_page (NULL, recnData->page);
1110  gsr = gnc_plugin_page_register_get_gsr (recnData->page);
1111  gnc_split_reg_raise (gsr);
1112  return gsr;
1113 }
1114 
1115 
1116 static void
1117 gnc_reconcile_window_double_click_cb(GNCReconcileView *view, Split *split,
1118  gpointer data)
1119 {
1120  auto recnData = static_cast<RecnWindow*>(data);
1121  GNCSplitReg *gsr;
1122 
1123  /* This should never be true, but be paranoid */
1124  if (split == NULL)
1125  return;
1126 
1127  gsr = gnc_reconcile_window_open_register(recnData);
1128  if (gsr == NULL)
1129  return;
1130 
1131  /* Test for visibility of split */
1132  if (gnc_split_reg_clear_filter_for_split (gsr, split))
1133  gnc_plugin_page_register_clear_current_filter (GNC_PLUGIN_PAGE(recnData->page));
1134 
1135  gnc_split_reg_jump_to_split( gsr, split );
1136 }
1137 
1138 
1139 static void
1140 gnc_reconcile_window_focus_cb(GtkWidget *widget, GdkEventFocus *event,
1141  gpointer data)
1142 {
1143  auto recnData = static_cast<RecnWindow*>(data);
1144  GNCReconcileView *this_view, *other_view;
1145  GNCReconcileView *debit, *credit;
1146 
1147  this_view = GNC_RECONCILE_VIEW(widget);
1148 
1149  debit = GNC_RECONCILE_VIEW(recnData->debit);
1150  credit = GNC_RECONCILE_VIEW(recnData->credit);
1151 
1152  other_view = GNC_RECONCILE_VIEW(this_view == debit ? credit : debit);
1153 
1154  /* clear the *other* list so we always have no more than one selection */
1155  gnc_reconcile_view_unselect_all(other_view);
1156 }
1157 
1158 
1159 static gboolean
1160 gnc_reconcile_key_press_cb (GtkWidget *widget, GdkEventKey *event,
1161  gpointer data)
1162 {
1163  auto recnData = static_cast<RecnWindow*>(data);
1164  GtkWidget *this_view, *other_view;
1165  GtkWidget *debit, *credit;
1166 
1167  switch (event->keyval)
1168  {
1169  case GDK_KEY_Tab:
1170  case GDK_KEY_ISO_Left_Tab:
1171  break;
1172 
1173  default:
1174  return FALSE;
1175  }
1176 
1177  g_signal_stop_emission_by_name (widget, "key_press_event");
1178 
1179  this_view = widget;
1180 
1181  debit = recnData->debit;
1182  credit = recnData->credit;
1183 
1184  other_view = (this_view == debit ? credit : debit);
1185 
1186  gtk_widget_grab_focus (other_view);
1187 
1188  return TRUE;
1189 }
1190 
1191 
1192 static void
1193 gnc_reconcile_window_set_titles(RecnWindow *recnData)
1194 {
1195  const gchar *title;
1196 
1198  gtk_frame_set_label(GTK_FRAME(recnData->debit_frame), title);
1199 
1201  gtk_frame_set_label(GTK_FRAME(recnData->credit_frame), title);
1202 }
1203 
1204 
1205 static GtkWidget *
1206 gnc_reconcile_window_create_view_box(Account *account,
1207  GNCReconcileViewType type,
1208  RecnWindow *recnData,
1209  GtkWidget **list_save,
1210  GtkWidget **total_save)
1211 {
1212  GtkWidget *frame, *scrollWin, *view, *vbox, *label, *hbox;
1213  GtkWidget *vscroll;
1214  GtkRequisition nat_sb;
1215 
1216  frame = gtk_frame_new(NULL);
1217 
1218  if (type == RECLIST_DEBIT)
1219  recnData->debit_frame = frame;
1220  else
1221  recnData->credit_frame = frame;
1222 
1223  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
1224  gtk_box_set_homogeneous (GTK_BOX (vbox), FALSE);
1225 
1226  view = gnc_reconcile_view_new(account, type, recnData->statement_date);
1227  *list_save = view;
1228 
1229  g_signal_connect(view, "toggle_reconciled",
1230  G_CALLBACK(gnc_reconcile_window_toggled_cb),
1231  recnData);
1232  g_signal_connect(view, "line_selected",
1233  G_CALLBACK(gnc_reconcile_window_row_cb),
1234  recnData);
1235  g_signal_connect(view, "button_press_event",
1236  G_CALLBACK(gnc_reconcile_window_button_press_cb),
1237  recnData);
1238  g_signal_connect(view, "double_click_split",
1239  G_CALLBACK(gnc_reconcile_window_double_click_cb),
1240  recnData);
1241  g_signal_connect(view, "focus_in_event",
1242  G_CALLBACK(gnc_reconcile_window_focus_cb),
1243  recnData);
1244  g_signal_connect(view, "key_press_event",
1245  G_CALLBACK(gnc_reconcile_key_press_cb),
1246  recnData);
1247 
1248  scrollWin = gtk_scrolled_window_new (NULL, NULL);
1249  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrollWin),
1250  GTK_POLICY_AUTOMATIC,
1251  GTK_POLICY_AUTOMATIC);
1252  gtk_container_set_border_width(GTK_CONTAINER(scrollWin), 5);
1253 
1254  gtk_container_add(GTK_CONTAINER(frame), scrollWin);
1255  gtk_container_add(GTK_CONTAINER(scrollWin), view);
1256  gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
1257 
1258  // get the vertical scroll bar width
1259  vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (scrollWin));
1260  gtk_widget_get_preferred_size (vscroll, NULL, &nat_sb);
1261 
1262  // add xpadding to recn column so scrollbar does not cover
1263  gnc_reconcile_view_add_padding (GNC_RECONCILE_VIEW(view), REC_RECN, nat_sb.width);
1264 
1265  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
1266  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
1267  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1268 
1269  label = gtk_label_new(_("Total"));
1270  gnc_label_set_alignment(label, 1.0, 0.5);
1271  gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1272 
1273  label = gtk_label_new("");
1274  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1275  *total_save = label;
1276  gtk_widget_set_margin_end (GTK_WIDGET(label), 10 + nat_sb.width);
1277 
1278  return vbox;
1279 }
1280 
1281 
1282 static Split *
1283 gnc_reconcile_window_get_current_split(RecnWindow *recnData)
1284 {
1285  GNCReconcileView *view;
1286  Split *split;
1287 
1288  view = GNC_RECONCILE_VIEW(recnData->debit);
1289  split = gnc_reconcile_view_get_current_split(view);
1290  if (split != NULL)
1291  return split;
1292 
1293  view = GNC_RECONCILE_VIEW(recnData->credit);
1294  split = gnc_reconcile_view_get_current_split(view);
1295 
1296  return split;
1297 }
1298 
1299 
1300 static void
1301 gnc_ui_reconcile_window_help_cb (GSimpleAction *simple,
1302  GVariant *parameter,
1303  gpointer user_data)
1304 {
1305  auto recnData = static_cast<RecnWindow*>(user_data);
1306  gnc_gnome_help (GTK_WINDOW(recnData->window), DF_MANUAL, DL_RECNWIN);
1307 }
1308 
1309 
1310 static void
1311 gnc_ui_reconcile_window_change_cb (GSimpleAction *simple,
1312  GVariant *parameter,
1313  gpointer user_data)
1314 {
1315  auto recnData = static_cast<RecnWindow*>(user_data);
1316  Account *account = recn_get_account (recnData);
1317  gnc_numeric new_ending = recnData->new_ending;
1318  time64 statement_date = recnData->statement_date;
1319 
1320  if (gnc_reverse_balance (account))
1321  new_ending = gnc_numeric_neg (new_ending);
1322  if (startRecnWindow (recnData->window, account, &new_ending, &statement_date,
1323  FALSE))
1324  {
1325  recnData->new_ending = new_ending;
1326  recnData->statement_date = statement_date;
1327  recnRecalculateBalance (recnData);
1328  }
1329 }
1330 
1331 
1332 static void
1333 gnc_ui_reconcile_window_balance_cb (GSimpleAction *simple,
1334  GVariant *parameter,
1335  gpointer user_data)
1336 {
1337  auto recnData = static_cast<RecnWindow*>(user_data);
1338  GNCSplitReg *gsr;
1339  Account *account;
1340  gnc_numeric balancing_amount;
1341  time64 statement_date;
1342 
1343 
1344  gsr = gnc_reconcile_window_open_register(recnData);
1345  if (gsr == NULL)
1346  return;
1347 
1348  account = recn_get_account(recnData);
1349  if (account == NULL)
1350  return;
1351 
1352  balancing_amount = recnRecalculateBalance(recnData);
1353  if (gnc_numeric_zero_p(balancing_amount))
1354  return;
1355 
1356  statement_date = recnData->statement_date;
1357  if (statement_date == 0)
1358  statement_date = gnc_time (NULL); // default to 'now'
1359 
1360  gnc_split_reg_balancing_entry(gsr, account, statement_date, balancing_amount);
1361 }
1362 
1363 
1364 static void
1365 gnc_ui_reconcile_window_rec_cb (GSimpleAction *simple,
1366  GVariant *parameter,
1367  gpointer user_data)
1368 {
1369  auto recnData = static_cast<RecnWindow*>(user_data);
1370  GNCReconcileView *debit, *credit;
1371 
1372  debit = GNC_RECONCILE_VIEW(recnData->debit);
1373  credit = GNC_RECONCILE_VIEW(recnData->credit);
1374 
1375  gnc_reconcile_view_set_list (debit, TRUE);
1376  gnc_reconcile_view_set_list (credit, TRUE);
1377 }
1378 
1379 
1380 static void
1381 gnc_ui_reconcile_window_unrec_cb (GSimpleAction *simple,
1382  GVariant *parameter,
1383  gpointer user_data)
1384 {
1385  auto recnData = static_cast<RecnWindow*>(user_data);
1386  GNCReconcileView *debit, *credit;
1387 
1388  debit = GNC_RECONCILE_VIEW(recnData->debit);
1389  credit = GNC_RECONCILE_VIEW(recnData->credit);
1390 
1391  gnc_reconcile_view_set_list (debit, FALSE);
1392  gnc_reconcile_view_set_list (credit, FALSE);
1393 }
1394 
1395 
1401 static GNCReconcileView *
1402 gnc_reconcile_window_get_selection_view (RecnWindow *recnData)
1403 {
1404  if (gnc_reconcile_view_num_selected (GNC_RECONCILE_VIEW (recnData->debit)) > 0)
1405  return GNC_RECONCILE_VIEW (recnData->debit);
1406 
1407  if (gnc_reconcile_view_num_selected (GNC_RECONCILE_VIEW (recnData->credit)) > 0)
1408  return GNC_RECONCILE_VIEW (recnData->credit);
1409 
1410  return NULL;
1411 }
1412 
1413 
1420 static void
1421 gnc_reconcile_window_delete_set_next_selection (RecnWindow *recnData, Split *split)
1422 {
1423  GNCReconcileView *view = gnc_reconcile_window_get_selection_view (recnData);
1424  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1425  Split *this_split = NULL;
1426  GtkTreeIter iter;
1427  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1428  GList *path_list, *node;
1429  GtkTreePath *save_del_path;
1430  Transaction* trans = xaccSplitGetParent (split); // parent transaction of the split to delete
1431 
1432  if (!view)
1433  return; // no selected split
1434 
1435  path_list = gtk_tree_selection_get_selected_rows (selection, &model);
1436  // get path of the first split selected - there should be only 1 selected
1437  node = g_list_first (path_list);
1438  if (!node)
1439  return;
1440  auto path = static_cast<GtkTreePath*>(node->data);
1441  save_del_path = gtk_tree_path_copy (path);
1442 
1443  gtk_tree_path_next (path);
1444  if (gtk_tree_model_get_iter (model, &iter, path))
1445  {
1446  do
1447  {
1448  gtk_tree_model_get (model, &iter, REC_POINTER, &this_split, -1);
1449  }
1450  while (xaccSplitGetParent (this_split) == trans && gtk_tree_model_iter_next (model, &iter));
1451  }
1452 
1453  if ((!this_split) || xaccSplitGetParent (this_split) == trans)
1454  {
1455  // There aren't any splits for a different transaction after the split to be deleted,
1456  // so find the previous split having a different parent transaction
1457  path = save_del_path; // split to be deleted
1458  if (gtk_tree_path_prev (path) && gtk_tree_model_get_iter (model, &iter, path))
1459  {
1460  do
1461  {
1462  gtk_tree_model_get (model, &iter, REC_POINTER, &this_split, -1);
1463  }
1464  while (xaccSplitGetParent (this_split) == trans && gtk_tree_model_iter_previous (model, &iter));
1465  }
1466  }
1467 
1468  gtk_tree_path_free (save_del_path);
1469  g_list_free_full (path_list, (GDestroyNotify) gtk_tree_path_free);
1470  if ((!this_split) || xaccSplitGetParent (this_split) == trans)
1471  return;
1472 
1473  gtk_tree_selection_select_iter (selection, &iter);
1474 }
1475 
1476 
1477 static void
1478 gnc_ui_reconcile_window_delete_cb (GSimpleAction *simple,
1479  GVariant *parameter,
1480  gpointer user_data)
1481 {
1482  auto recnData = static_cast<RecnWindow*>(user_data);
1483  Transaction *trans;
1484  Split *split;
1485 
1486  split = gnc_reconcile_window_get_current_split(recnData);
1487  /* This should never be true, but be paranoid */
1488  if (split == NULL)
1489  return;
1490 
1491  {
1492  const char *message = _("Are you sure you want to delete the selected "
1493  "transaction?");
1494  gboolean result;
1495 
1496  result = gnc_verify_dialog (GTK_WINDOW (recnData->window), FALSE, "%s", message);
1497 
1498  if (!result)
1499  return;
1500  }
1501 
1502  /* select the split that should be visible after the deletion */
1503  gnc_reconcile_window_delete_set_next_selection(recnData, split);
1504 
1505  gnc_suspend_gui_refresh ();
1506 
1507  trans = xaccSplitGetParent(split);
1508  xaccTransDestroy(trans);
1509 
1510  gnc_resume_gui_refresh ();
1511 }
1512 
1513 
1514 static void
1515 gnc_ui_reconcile_window_edit_cb (GSimpleAction *simple,
1516  GVariant *parameter,
1517  gpointer user_data)
1518 {
1519  auto recnData = static_cast<RecnWindow*>(user_data);
1520  GNCSplitReg *gsr;
1521  Split *split;
1522 
1523  split = gnc_reconcile_window_get_current_split (recnData);
1524  /* This should never be true, but be paranoid */
1525  if (split == NULL)
1526  return;
1527 
1528  gsr = gnc_reconcile_window_open_register(recnData);
1529  if (gsr == NULL)
1530  return;
1531 
1532  /* Test for visibility of split */
1533  if (gnc_split_reg_clear_filter_for_split (gsr, split))
1534  gnc_plugin_page_register_clear_current_filter (GNC_PLUGIN_PAGE(recnData->page));
1535 
1536  gnc_split_reg_jump_to_split_amount( gsr, split );
1537 }
1538 
1539 
1540 static char *
1541 gnc_recn_make_window_name(Account *account)
1542 {
1543  char *fullname;
1544  char *title;
1545 
1546  fullname = gnc_account_get_full_name(account);
1547  title = g_strconcat(fullname, " - ", _("Reconcile"), NULL);
1548 
1549  g_free(fullname);
1550 
1551  return title;
1552 }
1553 
1554 
1555 static void
1556 gnc_recn_set_window_name(RecnWindow *recnData)
1557 {
1558  char *title;
1559 
1560  title = gnc_recn_make_window_name (recn_get_account (recnData));
1561 
1562  gtk_window_set_title (GTK_WINDOW (recnData->window), title);
1563 
1564  g_free (title);
1565 }
1566 
1567 
1568 static void
1569 gnc_recn_edit_account_cb (GSimpleAction *simple,
1570  GVariant *parameter,
1571  gpointer user_data)
1572 {
1573  auto recnData = static_cast<RecnWindow*>(user_data);
1574  Account *account = recn_get_account (recnData);
1575 
1576  if (account == NULL)
1577  return;
1578 
1579  gnc_ui_edit_account_window (GTK_WINDOW (recnData->window), account);
1580 }
1581 
1582 
1583 static void
1584 gnc_recn_xfer_cb (GSimpleAction *simple,
1585  GVariant *parameter,
1586  gpointer user_data)
1587 {
1588  auto recnData = static_cast<RecnWindow*>(user_data);
1589  Account *account = recn_get_account (recnData);
1590 
1591  if (account == NULL)
1592  return;
1593 
1594  gnc_xfer_dialog (recnData->window, account);
1595 }
1596 
1597 
1598 static void
1599 gnc_recn_scrub_cb (GSimpleAction *simple,
1600  GVariant *parameter,
1601  gpointer user_data)
1602 {
1603  auto recnData = static_cast<RecnWindow*>(user_data);
1604  Account *account = recn_get_account (recnData);
1605 
1606  if (account == NULL)
1607  return;
1608 
1609  gnc_suspend_gui_refresh ();
1610 
1611  xaccAccountTreeScrubOrphans (account, gnc_window_show_progress);
1612  xaccAccountTreeScrubImbalance (account, gnc_window_show_progress);
1613 
1614  // XXX: Lots are disabled.
1615  if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL)
1616  xaccAccountTreeScrubLots(account);
1617 
1618  gnc_resume_gui_refresh ();
1619 }
1620 
1621 
1622 static void
1623 gnc_recn_open_cb (GSimpleAction *simple,
1624  GVariant *parameter,
1625  gpointer user_data)
1626 {
1627  auto recnData = static_cast<RecnWindow*>(user_data);
1628 
1629  gnc_reconcile_window_open_register(recnData);
1630 }
1631 
1632 
1633 static void
1634 gnc_get_reconcile_info (Account *account,
1635  gnc_numeric *new_ending,
1636  time64 *statement_date)
1637 {
1638  gboolean always_today;
1639  GDate date;
1640  time64 today;
1641 
1642  g_date_clear(&date, 1);
1643 
1644  always_today = gnc_prefs_get_bool(GNC_PREFS_GROUP_RECONCILE, GNC_PREF_ALWAYS_REC_TO_TODAY);
1645 
1646  if (!always_today &&
1647  xaccAccountGetReconcileLastDate (account, statement_date))
1648  {
1649  int months = 1, days = 0;
1650 
1651  gnc_gdate_set_time64(&date, *statement_date);
1652 
1653  xaccAccountGetReconcileLastInterval (account, &months, &days);
1654 
1655  if (months)
1656  {
1657  gboolean was_last_day_of_month = g_date_is_last_of_month(&date);
1658 
1659  g_date_add_months(&date, months);
1660 
1661  /* Track last day of the month, i.e. 1/31 -> 2/28 -> 3/31 */
1662  if (was_last_day_of_month)
1663  {
1664  g_date_set_day (&date, g_date_get_days_in_month(g_date_get_month(&date),
1665  g_date_get_year( &date)));
1666  }
1667  }
1668  else
1669  {
1670  g_date_add_days (&date, days);
1671  }
1672 
1673  *statement_date = gnc_time64_get_day_end_gdate (&date);
1674 
1675  today = gnc_time64_get_day_end (gnc_time (NULL));
1676  if (*statement_date > today)
1677  *statement_date = today;
1678  }
1679 
1680  xaccAccountGetReconcilePostponeDate (account, statement_date);
1681 
1682  if (xaccAccountGetReconcilePostponeBalance(account, new_ending))
1683  {
1684  if (gnc_reverse_balance(account))
1685  *new_ending = gnc_numeric_neg(*new_ending);
1686  }
1687  else
1688  {
1689  /* if the account wasn't previously postponed, try to predict
1690  * the statement balance based on the statement date.
1691  */
1692  *new_ending =
1693  gnc_ui_account_get_balance_as_of_date
1694  (account, *statement_date,
1696  }
1697 }
1698 
1699 
1700 static gboolean
1701 find_by_account (gpointer find_data, gpointer user_data)
1702 {
1703  auto account = GNC_ACCOUNT(find_data);
1704  auto recnData = static_cast<RecnWindow*>(user_data);
1705 
1706  if (!recnData)
1707  return FALSE;
1708 
1709  return guid_equal (&recnData->account, xaccAccountGetGUID (account));
1710 }
1711 
1712 
1713 static void
1714 recn_set_watches_one_account (gpointer data, gpointer user_data)
1715 {
1716  Account *account = (Account *)data;
1717  RecnWindow *recnData = (RecnWindow *)user_data;
1718 
1719  /* add a watch on the account */
1720  gnc_gui_component_watch_entity (recnData->component_id,
1721  xaccAccountGetGUID (account),
1722  QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
1723 
1724  /* add a watch on each split for the account */
1725  for (auto split : xaccAccountGetSplits (account))
1726  {
1727  auto trans = xaccSplitGetParent (split);
1728  gnc_gui_component_watch_entity (recnData->component_id,
1729  xaccTransGetGUID (trans),
1730  QOF_EVENT_MODIFY
1731  | QOF_EVENT_DESTROY
1732  | GNC_EVENT_ITEM_CHANGED);
1733  }
1734 }
1735 
1736 
1737 static void
1738 recn_set_watches (RecnWindow *recnData)
1739 {
1740  gboolean include_children;
1741  Account *account;
1742  GList *accounts = NULL;
1743 
1744  gnc_gui_component_clear_watches (recnData->component_id);
1745 
1746  account = recn_get_account (recnData);
1747 
1748  include_children = xaccAccountGetReconcileChildrenStatus(account);
1749  if (include_children)
1750  accounts = gnc_account_get_descendants(account);
1751 
1752  /* match the account */
1753  accounts = g_list_prepend (accounts, account);
1754 
1755  g_list_foreach(accounts, recn_set_watches_one_account, recnData);
1756 
1757  g_list_free (accounts);
1758 }
1759 
1760 
1761 static void
1762 refresh_handler (GHashTable *changes, gpointer user_data)
1763 {
1764  auto recnData = static_cast<RecnWindow*>(user_data);
1765  const EventInfo *info;
1766  Account *account;
1767 
1768  account = recn_get_account (recnData);
1769  if (!account)
1770  {
1771  gnc_close_gui_component_by_data (WINDOW_RECONCILE_CM_CLASS, recnData);
1772  return;
1773  }
1774 
1775  if (changes)
1776  {
1777  info = gnc_gui_get_entity_events (changes, &recnData->account);
1778  if (info && (info->event_mask & QOF_EVENT_DESTROY))
1779  {
1780  gnc_close_gui_component_by_data (WINDOW_RECONCILE_CM_CLASS, recnData);
1781  return;
1782  }
1783  }
1784 
1785  gnc_reconcile_window_set_titles(recnData);
1786  recn_set_watches (recnData);
1787 
1788  recnRefresh (recnData);
1789 }
1790 
1791 
1792 static void
1793 close_handler (gpointer user_data)
1794 {
1795  auto recnData = static_cast<RecnWindow*>(user_data);
1796 
1797  gnc_save_window_size(GNC_PREFS_GROUP_RECONCILE, GTK_WINDOW(recnData->window));
1798  gtk_widget_destroy (recnData->window);
1799 }
1800 
1801 
1802 /********************************************************************\
1803  * recnWindow *
1804  * opens up the window to reconcile an account *
1805  * *
1806  * Args: parent - the parent of this window *
1807  * account - the account to reconcile *
1808  * Return: recnData - the instance of this RecnWindow *
1809 \********************************************************************/
1810 RecnWindow *
1811 recnWindow (GtkWidget *parent, Account *account)
1812 {
1813  gnc_numeric new_ending;
1814  gboolean enable_subaccounts;
1815  time64 statement_date;
1816 
1817  if (account == NULL)
1818  return NULL;
1819 
1820  /* The last time reconciliation was attempted during the current execution
1821  * of gnucash, the date was stored. Use that date if possible. This helps
1822  * with balancing multiple accounts for which statements are issued at the
1823  * same time, like multiple bank accounts on a single statement. Otherwise
1824  * use the end of today to ensure we include any transactions posted
1825  * today.
1826  */
1827  if (!gnc_reconcile_last_statement_date)
1828  statement_date = gnc_time64_get_day_end(gnc_time (NULL));
1829  else
1830  statement_date = gnc_reconcile_last_statement_date;
1831 
1832  gnc_get_reconcile_info (account, &new_ending, &statement_date);
1833 
1834  enable_subaccounts = !has_account_different_commodities(account);
1835  /* Popup a little window to prompt the user to enter the
1836  * ending balance for his/her bank statement */
1837  if (!startRecnWindow (parent, account, &new_ending, &statement_date,
1838  enable_subaccounts))
1839  return NULL;
1840 
1841  return recnWindowWithBalance (parent, account, new_ending, statement_date);
1842 }
1843 
1844 
1845 static GActionEntry recWindow_actions_entries [] =
1846 {
1847  { "RecnChangeInfoAction", gnc_ui_reconcile_window_change_cb, NULL, NULL, NULL },
1848  { "RecnFinishAction", recnFinishCB, NULL, NULL, NULL },
1849  { "RecnPostponeAction", recnPostponeCB, NULL, NULL, NULL },
1850  { "RecnCancelAction", recnCancelCB, NULL, NULL, NULL },
1851  { "RecnAutoClearAction", recnAutoClearCB, NULL, NULL, NULL },
1852 
1853  { "AccountOpenAccountAction", gnc_recn_open_cb, NULL, NULL, NULL },
1854  { "AccountEditAccountAction", gnc_recn_edit_account_cb, NULL, NULL, NULL },
1855  { "AccountTransferAction", gnc_recn_xfer_cb, NULL, NULL, NULL },
1856  { "AccountCheckRepairAction", gnc_recn_scrub_cb, NULL, NULL, NULL },
1857 
1858  { "TransBalanceAction", gnc_ui_reconcile_window_balance_cb, NULL, NULL, NULL },
1859  { "TransEditAction", gnc_ui_reconcile_window_edit_cb, NULL, NULL, NULL },
1860  { "TransDeleteAction", gnc_ui_reconcile_window_delete_cb, NULL, NULL, NULL },
1861  { "TransRecAction", gnc_ui_reconcile_window_rec_cb, NULL, NULL, NULL },
1862  { "TransUnRecAction", gnc_ui_reconcile_window_unrec_cb, NULL, NULL, NULL },
1863 
1864  { "HelpHelpAction", gnc_ui_reconcile_window_help_cb, NULL, NULL, NULL },
1865 };
1867 static guint recnWindow_n_actions_entries = G_N_ELEMENTS(recWindow_actions_entries);
1868 
1869 #ifdef MAC_INTEGRATION
1870 /* Enable GtkMenuItem accelerators */
1871 static gboolean
1872 can_activate_cb(GtkWidget *widget, guint signal_id, gpointer data)
1873 {
1874  //return gtk_widget_is_sensitive (widget);
1875  return TRUE;
1876 }
1877 #endif
1878 
1879 /********************************************************************\
1880  * recnWindowWithBalance
1881  *
1882  * Opens up the window to reconcile an account, but with ending
1883  * balance and statement date already given.
1884  *
1885  * Args: parent - The parent widget of the new window
1886  * account - The account to reconcile
1887  * new_ending - The amount for ending balance
1888  * statement_date - The date of the statement
1889  * Return: recnData - the instance of this RecnWindow
1890 \********************************************************************/
1891 RecnWindow *
1892 recnWindowWithBalance (GtkWidget *parent, Account *account, gnc_numeric new_ending,
1893  time64 statement_date)
1894 {
1895  RecnWindow *recnData;
1896  GtkWidget *statusbar;
1897  GtkWidget *vbox;
1898  GtkWidget *dock;
1899 
1900  if (account == NULL)
1901  return NULL;
1902 
1903  recnData = static_cast<RecnWindow*>(gnc_find_first_gui_component (WINDOW_RECONCILE_CM_CLASS,
1904  find_by_account, account));
1905  if (recnData)
1906  return recnData;
1907 
1908  recnData = g_new0 (RecnWindow, 1);
1909 
1910  recnData->account = *xaccAccountGetGUID (account);
1911 
1912 
1913  recnData->component_id =
1914  gnc_register_gui_component (WINDOW_RECONCILE_CM_CLASS,
1915  refresh_handler, close_handler,
1916  recnData);
1917  gnc_gui_component_set_session (recnData->component_id, gnc_get_current_session());
1918 
1919  recn_set_watches (recnData);
1920 
1921  gnc_reconcile_last_statement_date = statement_date;
1922 
1923  recnData->new_ending = new_ending;
1924  recnData->statement_date = statement_date;
1925  recnData->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1926  recnData->delete_refresh = FALSE;
1927  new (&recnData->autoclear_splits) SplitsVec();
1928  new (&recnData->initially_cleared_splits) SplitsVec();
1929 
1930  gnc_recn_set_window_name(recnData);
1931 
1932  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1933  gtk_box_set_homogeneous (GTK_BOX (vbox), FALSE);
1934  gtk_container_add(GTK_CONTAINER(recnData->window), vbox);
1935 
1936  // Set the name for this dialog so it can be easily manipulated with css
1937  gtk_widget_set_name (GTK_WIDGET(recnData->window), "gnc-id-reconcile");
1938 
1939  dock = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1940  gtk_box_set_homogeneous (GTK_BOX (dock), FALSE);
1941  gtk_widget_show(dock);
1942  gtk_box_pack_start(GTK_BOX (vbox), dock, FALSE, TRUE, 0);
1943 
1944  auto init_cleared = [&recnData](Split* s)
1945  {
1946  if (xaccSplitGetReconcile (s) == CREC)
1947  recnData->initially_cleared_splits.push_back (s);
1948  };
1949  gnc_account_foreach_split_until_date (account, statement_date, init_cleared);
1950 
1951  {
1952  GtkToolbar *tool_bar;
1953  GMenuModel *menu_model;
1954  GtkWidget *menu_bar;
1955  const gchar *ui = GNUCASH_RESOURCE_PREFIX "/gnc-reconcile-window.ui";
1956  GError *error = NULL;
1957 
1958  recnData->accel_group = gtk_accel_group_new ();
1959  recnData->builder = gtk_builder_new ();
1960 
1961  gtk_builder_add_from_resource (recnData->builder, ui, &error);
1962 
1963  gtk_builder_set_translation_domain (recnData->builder, PROJECT_NAME);
1964 
1965  if (error)
1966  {
1967  g_critical ("Failed to load ui resource %s, Error %s", ui, error->message);
1968  g_error_free (error);
1969  gnc_unregister_gui_component_by_data (WINDOW_RECONCILE_CM_CLASS, recnData);
1970  g_free (recnData);
1971  return NULL;
1972  }
1973 
1974  recnData->autoclear_button = GTK_WIDGET(gtk_builder_get_object(recnData->builder, "autoclear_button"));
1975 
1976  menu_model = (GMenuModel *)gtk_builder_get_object (recnData->builder, "recwin-menu");
1977  menu_bar = gtk_menu_bar_new_from_model (menu_model);
1978  gtk_container_add (GTK_CONTAINER(vbox), menu_bar);
1979 #ifdef MAC_INTEGRATION
1980  auto theApp = static_cast<GtkosxApplication*>(g_object_new (GTKOSX_TYPE_APPLICATION, NULL));
1981  gtk_widget_hide (menu_bar);
1982  gtk_widget_set_no_show_all (menu_bar, TRUE);
1983  if (GTK_IS_MENU_ITEM (menu_bar))
1984  menu_bar = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu_bar));
1985 
1986  gtkosx_application_set_menu_bar (theApp, GTK_MENU_SHELL (menu_bar));
1987 #endif
1988  tool_bar = (GtkToolbar *)gtk_builder_get_object (recnData->builder, "recwin-toolbar");
1989 
1990  gtk_toolbar_set_style (GTK_TOOLBAR(tool_bar), GTK_TOOLBAR_BOTH);
1991  gtk_toolbar_set_icon_size (GTK_TOOLBAR(tool_bar),
1992  GTK_ICON_SIZE_SMALL_TOOLBAR);
1993 
1994  gtk_container_add (GTK_CONTAINER(vbox), GTK_WIDGET(tool_bar));
1995 
1996  gtk_window_add_accel_group (GTK_WINDOW(recnData->window), recnData->accel_group);
1997 
1998  // need to add the accelerator keys
1999  gnc_add_accelerator_keys_for_menu (menu_bar, menu_model, recnData->accel_group);
2000 
2001 #ifdef MAC_INTEGRATION
2002  gtkosx_application_sync_menubar (theApp);
2003  g_signal_connect (menu_bar, "can-activate-accel",
2004  G_CALLBACK(can_activate_cb), NULL);
2005  g_object_unref (theApp);
2006  theApp = NULL;
2007 #endif
2008 
2009  recnData->simple_action_group = g_simple_action_group_new ();
2010 
2011  g_action_map_add_action_entries (G_ACTION_MAP(recnData->simple_action_group),
2012  recWindow_actions_entries,
2013  recnWindow_n_actions_entries,
2014  recnData);
2015 
2016  gtk_widget_insert_action_group (GTK_WIDGET(recnData->window), "recwin",
2017  G_ACTION_GROUP(recnData->simple_action_group));
2018  }
2019 
2020  g_signal_connect(recnData->window, "popup-menu",
2021  G_CALLBACK(gnc_reconcile_window_popup_menu_cb), recnData);
2022 
2023  statusbar = gtk_statusbar_new();
2024  gtk_box_pack_end(GTK_BOX(vbox), statusbar, FALSE, FALSE, 0);
2025 
2026  g_signal_connect (recnData->window, "destroy",
2027  G_CALLBACK(recn_destroy_cb), recnData);
2028  g_signal_connect (recnData->window, "delete_event",
2029  G_CALLBACK(recn_delete_cb), recnData);
2030  g_signal_connect (recnData->window, "key_press_event",
2031  G_CALLBACK(recn_key_press_cb), recnData);
2032 
2033 
2034  /* if account has a reconciled split where reconciled_date is
2035  later than statement_date, emit a warning into statusbar */
2036  {
2037  GtkStatusbar *bar = GTK_STATUSBAR (statusbar);
2038  guint context = gtk_statusbar_get_context_id (bar, "future_dates");
2039  GtkWidget *box = gtk_statusbar_get_message_area (bar);
2040  GtkWidget *image = gtk_image_new_from_icon_name
2041  ("dialog-warning", GTK_ICON_SIZE_SMALL_TOOLBAR);
2042 
2043  // find an already reconciled split whose statement date
2044  // is after *this* reconciliation statement date.
2045  auto has_later_recn_statement_date = [statement_date](const Split *split)
2046  { return (xaccSplitGetReconcile (split) == YREC &&
2047  xaccSplitGetDateReconciled (split) > statement_date); };
2048 
2049  if (auto split = gnc_account_find_split (account, has_later_recn_statement_date, true))
2050  {
2051  auto datestr = qof_print_date (xaccTransGetDate (xaccSplitGetParent (split)));
2052  auto recnstr = qof_print_date (xaccSplitGetDateReconciled (split));
2053  PWARN ("split posting_date=%s, recn_date=%s", datestr, recnstr);
2054 
2055  gtk_statusbar_push (bar, context, _("WARNING! Account contains \
2056 splits whose reconcile date is after statement date. Reconciliation may be \
2057 difficult."));
2058 
2059  gtk_widget_set_tooltip_text (GTK_WIDGET (bar), _("This account \
2060 has splits whose Reconciled Date is after this reconciliation statement date. \
2061 These splits may make reconciliation difficult. If this is the case, you may \
2062 use Find Transactions to find them, unreconcile, and re-reconcile."));
2063 
2064  gtk_box_pack_start (GTK_BOX(box), image, FALSE, FALSE, 0);
2065  gtk_box_reorder_child (GTK_BOX(box), image, 0);
2066 
2067  g_free (datestr);
2068  g_free (recnstr);
2069  }
2070  }
2071 
2072  /* The main area */
2073  {
2074  GtkWidget *frame = gtk_frame_new(NULL);
2075  GtkWidget *main_area = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
2076  GtkWidget *debcred_area = gtk_grid_new ();
2077  GtkWidget *debits_box;
2078  GtkWidget *credits_box;
2079 
2080  gtk_box_set_homogeneous (GTK_BOX (main_area), FALSE);
2081  gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 10);
2082 
2083  /* Force a reasonable starting size */
2084  gtk_window_set_default_size(GTK_WINDOW(recnData->window), 800, 600);
2085  gnc_restore_window_size (GNC_PREFS_GROUP_RECONCILE,
2086  GTK_WINDOW(recnData->window), GTK_WINDOW(parent));
2087 
2088  gtk_container_add(GTK_CONTAINER(frame), main_area);
2089  gtk_container_set_border_width(GTK_CONTAINER(main_area), 10);
2090 
2091  debits_box = gnc_reconcile_window_create_view_box
2092  (account, RECLIST_DEBIT, recnData,
2093  &recnData->debit, &recnData->total_debit);
2094 
2095  // Add a style context for this widget so it can be easily manipulated with css
2096  gnc_widget_style_context_add_class (GTK_WIDGET(debits_box), "gnc-class-debits");
2097 
2098  credits_box = gnc_reconcile_window_create_view_box
2099  (account, RECLIST_CREDIT, recnData,
2100  &recnData->credit, &recnData->total_credit);
2101 
2102  // Add a style context for this widget so it can be easily manipulated with css
2103  gnc_widget_style_context_add_class (GTK_WIDGET(credits_box), "gnc-class-credits");
2104 
2105  GNC_RECONCILE_VIEW(recnData->debit)->sibling = GNC_RECONCILE_VIEW(recnData->credit);
2106  GNC_RECONCILE_VIEW(recnData->credit)->sibling = GNC_RECONCILE_VIEW(recnData->debit);
2107 
2108  gtk_box_pack_start(GTK_BOX(main_area), debcred_area, TRUE, TRUE, 0);
2109 
2110  gtk_grid_set_column_homogeneous (GTK_GRID(debcred_area), TRUE);
2111  gtk_grid_set_column_spacing (GTK_GRID(debcred_area), 15);
2112  gtk_grid_attach (GTK_GRID(debcred_area), debits_box, 0, 0, 1, 1);
2113  gtk_widget_set_hexpand (debits_box, TRUE);
2114  gtk_widget_set_vexpand (debits_box, TRUE);
2115  gtk_widget_set_halign (debits_box, GTK_ALIGN_FILL);
2116  gtk_widget_set_valign (debits_box, GTK_ALIGN_FILL);
2117 
2118  gtk_grid_attach (GTK_GRID(debcred_area), credits_box, 1, 0, 1, 1);
2119  gtk_widget_set_hexpand (credits_box, TRUE);
2120  gtk_widget_set_vexpand (credits_box, TRUE);
2121  gtk_widget_set_halign (credits_box, GTK_ALIGN_FILL);
2122  gtk_widget_set_valign (credits_box, GTK_ALIGN_FILL);
2123 
2124  {
2125  GtkWidget *hbox, *title_vbox, *value_vbox;
2126  GtkWidget *totals_hbox, *frame, *title, *value;
2127 
2128  /* lower horizontal bar below reconcile lists */
2129  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
2130  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
2131  gtk_box_pack_start(GTK_BOX(main_area), hbox, FALSE, FALSE, 0);
2132 
2133  /* frame to hold totals */
2134  frame = gtk_frame_new(NULL);
2135  gtk_box_pack_end(GTK_BOX(hbox), frame, FALSE, FALSE, 0);
2136 
2137  // Set the name for this widget so it can be easily manipulated with css
2138  gtk_widget_set_name (GTK_WIDGET(frame), "gnc-id-reconcile-totals");
2139 
2140  /* hbox to hold title/value vboxes */
2141  totals_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
2142  gtk_box_set_homogeneous (GTK_BOX (totals_hbox), FALSE);
2143  gtk_container_add(GTK_CONTAINER(frame), totals_hbox);
2144  gtk_container_set_border_width(GTK_CONTAINER(totals_hbox), 5);
2145 
2146  /* vbox to hold titles */
2147  title_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
2148  gtk_box_set_homogeneous (GTK_BOX (title_vbox), FALSE);
2149  gtk_box_pack_start(GTK_BOX(totals_hbox), title_vbox, FALSE, FALSE, 0);
2150 
2151  /* vbox to hold values */
2152  value_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
2153  gtk_box_set_homogeneous (GTK_BOX (value_vbox), FALSE);
2154  gtk_box_pack_start(GTK_BOX(totals_hbox), value_vbox, TRUE, TRUE, 0);
2155 
2156  /* statement date title/value */
2157  title = gtk_label_new(_("Statement Date"));
2158  gnc_label_set_alignment(title, 1.0, 0.5);
2159  gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 0);
2160 
2161  value = gtk_label_new("");
2162  recnData->recn_date = value;
2163  gnc_label_set_alignment(value, 1.0, 0.5);
2164  gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 0);
2165 
2166  /* starting balance title/value */
2167  title = gtk_label_new(_("Starting Balance"));
2168  gnc_label_set_alignment(title, 1.0, 0.5);
2169  gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 3);
2170 
2171  value = gtk_label_new("");
2172  recnData->starting = value;
2173  gnc_label_set_alignment(value, 1.0, 0.5);
2174  gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 3);
2175 
2176  /* ending balance title/value */
2177  title = gtk_label_new(_("Ending Balance"));
2178  gnc_label_set_alignment(title, 1.0, 0.5);
2179  gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 0);
2180 
2181  value = gtk_label_new("");
2182  recnData->ending = value;
2183  gnc_label_set_alignment(value, 1.0, 0.5);
2184  gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 0);
2185 
2186  /* reconciled balance title/value */
2187  title = gtk_label_new(_("Reconciled Balance"));
2188  gnc_label_set_alignment(title, 1.0, 0.5);
2189  gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 0);
2190 
2191  value = gtk_label_new("");
2192  recnData->reconciled = value;
2193  gnc_label_set_alignment(value, 1.0, 0.5);
2194  gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 0);
2195 
2196  /* difference title/value */
2197  title = gtk_label_new(_("Difference"));
2198  gnc_label_set_alignment(title, 1.0, 0.5);
2199  gtk_box_pack_start(GTK_BOX(title_vbox), title, FALSE, FALSE, 0);
2200 
2201  value = gtk_label_new("");
2202  recnData->difference = value;
2203  gnc_label_set_alignment(value, 1.0, 0.5);
2204  gtk_box_pack_start(GTK_BOX(value_vbox), value, FALSE, FALSE, 0);
2205  }
2206 
2207  /* Set up the data */
2208  recnRefresh (recnData);
2209  }
2210 
2211  /* Allow resize */
2212  gtk_window_set_resizable(GTK_WINDOW(recnData->window), TRUE);
2213  gtk_widget_show_all(recnData->window);
2214 
2215  gnc_reconcile_window_set_titles(recnData);
2216 
2217  recnRecalculateBalance(recnData);
2218 
2219  gnc_window_adjust_for_screen(GTK_WINDOW(recnData->window));
2220 
2221  /* Set the sort orders of the debit and credit tree views */
2222  gnc_query_sort_order(GNC_QUERY_VIEW(recnData->debit), REC_DATE, GTK_SORT_ASCENDING);
2223  gnc_query_sort_order(GNC_QUERY_VIEW(recnData->credit), REC_DATE, GTK_SORT_ASCENDING);
2224 
2225  gtk_widget_grab_focus (recnData->debit);
2226 
2227  { // align the Totals value with that of the amount column
2228  gint recn_widthc = gnc_reconcile_view_get_column_width (GNC_RECONCILE_VIEW(recnData->credit), REC_RECN);
2229  gint recn_widthd = gnc_reconcile_view_get_column_width (GNC_RECONCILE_VIEW(recnData->debit), REC_RECN);
2230 
2231  gtk_widget_set_margin_end (GTK_WIDGET(recnData->total_credit), 10 + recn_widthc);
2232  gtk_widget_set_margin_end (GTK_WIDGET(recnData->total_debit), 10 + recn_widthd);
2233  }
2234  return recnData;
2235 }
2236 
2237 
2238 /********************************************************************\
2239  * gnc_ui_reconcile_window_raise *
2240  * shows and raises an account editing window *
2241  * *
2242  * Args: editAccData - the edit window structure *
2243 \********************************************************************/
2244 void
2245 gnc_ui_reconcile_window_raise(RecnWindow * recnData)
2246 {
2247  if (recnData == NULL)
2248  return;
2249 
2250  if (recnData->window == NULL)
2251  return;
2252 
2253  gtk_window_present(GTK_WINDOW(recnData->window));
2254 }
2255 
2256 GtkWindow *
2257 gnc_ui_reconcile_window_get_window (RecnWindow * recnData)
2258 {
2259  if (recnData == NULL || recnData->window == NULL)
2260  return NULL;
2261  return GTK_WINDOW(recnData->window);
2262 }
2263 
2264 
2265 
2266 /********************************************************************\
2267  * recn_destroy_cb *
2268  * frees memory allocated for an recnWindow, and other cleanup *
2269  * stuff *
2270  * *
2271  * Args: w - the widget that called us *
2272  * data - the data struct for this window *
2273  * Return: none *
2274 \********************************************************************/
2275 static void
2276 recn_destroy_cb (GtkWidget *w, gpointer data)
2277 {
2278  auto recnData = static_cast<RecnWindow*>(data);
2279  gchar **actions = g_action_group_list_actions (G_ACTION_GROUP(recnData->simple_action_group));
2280  gint num_actions = g_strv_length (actions);
2281 
2282  gnc_unregister_gui_component_by_data (WINDOW_RECONCILE_CM_CLASS, recnData);
2283 
2284  if (recnData->delete_refresh)
2285  gnc_resume_gui_refresh ();
2286 
2287  if (recnData->builder)
2288  g_object_unref(recnData->builder);
2289 
2290  if (recnData->accel_group)
2291  g_object_unref(recnData->accel_group);
2292 
2293  recnData->autoclear_splits.~SplitsVec();
2294  recnData->initially_cleared_splits.~SplitsVec();
2295 
2296  //Disable the actions, the handlers try to access recnData
2297  for (gint i = 0; i < num_actions; i++)
2298  {
2299  GAction *action = g_action_map_lookup_action (G_ACTION_MAP(recnData->simple_action_group), actions[i]);
2300  g_simple_action_set_enabled (G_SIMPLE_ACTION(action), FALSE);
2301  }
2302  g_strfreev (actions);
2303  g_free (recnData);
2304 }
2305 
2306 
2307 static void
2308 recn_cancel(RecnWindow *recnData)
2309 {
2310  gboolean changed = FALSE;
2311 
2312  if (gnc_reconcile_view_changed(GNC_RECONCILE_VIEW(recnData->credit)))
2313  changed = TRUE;
2314  if (gnc_reconcile_view_changed(GNC_RECONCILE_VIEW(recnData->debit)))
2315  changed = TRUE;
2316 
2317  if (changed)
2318  {
2319  const char *message = _("You have made changes to this reconcile "
2320  "window. Are you sure you want to cancel?");
2321  if (!gnc_verify_dialog (GTK_WINDOW (recnData->window), FALSE, "%s", message))
2322  return;
2323  }
2324 
2325  gnc_close_gui_component_by_data (WINDOW_RECONCILE_CM_CLASS, recnData);
2326 }
2327 
2328 
2329 static gboolean
2330 recn_delete_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
2331 {
2332  auto recnData = static_cast<RecnWindow*>(data);
2333 
2334  recn_cancel(recnData);
2335  return TRUE;
2336 }
2337 
2338 
2339 static gboolean
2340 recn_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
2341 {
2342  auto recnData = static_cast<RecnWindow*>(data);
2343 
2344  if (event->keyval == GDK_KEY_Escape)
2345  {
2346  recn_cancel(recnData);
2347  return TRUE;
2348  }
2349  else
2350  {
2351  return FALSE;
2352  }
2353 }
2354 
2355 
2356 /********************************************************************\
2357  * find_payment_account *
2358  * find an account that 'looks like' a payment account for the *
2359  * given account. This really only makes sense for credit card *
2360  * accounts. *
2361  * *
2362  * Args: account - the account to look in *
2363  * Return: a candidate payment account or NULL if none was found *
2364 \********************************************************************/
2365 static Account *
2366 find_payment_account(Account *account)
2367 {
2368  if (account == nullptr)
2369  return nullptr;
2370 
2371  const auto& splits = xaccAccountGetSplits (account);
2372 
2373  /* Search backwards to find the latest payment */
2374  for (auto it = splits.rbegin(); it != splits.rend(); it++)
2375  {
2376  auto split = *it;
2377 
2378  /* ignore 'purchases' */
2380  continue;
2381 
2382  for (auto n = xaccTransGetSplitList (xaccSplitGetParent(split)); n; n = n->next)
2383  {
2384  auto s = GNC_SPLIT(n->data);
2385  if (s == split)
2386  continue;
2387 
2388  auto a = xaccSplitGetAccount(s);
2389  if (a == account)
2390  continue;
2391 
2392  auto type = xaccAccountGetType(a);
2393  if (type == ACCT_TYPE_BANK || type == ACCT_TYPE_CASH || type == ACCT_TYPE_ASSET)
2394  return a;
2395  }
2396  }
2397 
2398  return nullptr;
2399 }
2400 
2401 static void
2402 acct_traverse_descendants (Account *acct, std::function<void(Account*)> fn)
2403 {
2404  fn (acct);
2406  gnc_account_foreach_descendant (acct, fn);
2407 }
2408 
2409 /********************************************************************\
2410  * recnFinishCB *
2411  * saves reconcile information *
2412  * *
2413  * Args: w - the widget that called us *
2414  * data - the data struct for this window *
2415  * Return: none *
2416 \********************************************************************/
2417 static void
2418 recnFinishCB (GSimpleAction *simple,
2419  GVariant *parameter,
2420  gpointer user_data)
2421 {
2422  auto recnData = static_cast<RecnWindow*>(user_data);
2423  gboolean auto_payment;
2424  Account *account;
2425  time64 date;
2426 
2427  if (!gnc_numeric_zero_p (recnRecalculateBalance(recnData)))
2428  {
2429  const char *message = _("The account is not balanced. "
2430  "Are you sure you want to finish?");
2431  if (!gnc_verify_dialog (GTK_WINDOW (recnData->window), FALSE, "%s", message))
2432  return;
2433  }
2434 
2435  date = recnData->statement_date;
2436 
2437  gnc_suspend_gui_refresh ();
2438 
2439  recnData->delete_refresh = TRUE;
2440  account = recn_get_account (recnData);
2441 
2442  acct_traverse_descendants (account, xaccAccountBeginEdit);
2443  gnc_reconcile_view_commit(GNC_RECONCILE_VIEW(recnData->credit), date);
2444  gnc_reconcile_view_commit(GNC_RECONCILE_VIEW(recnData->debit), date);
2445  acct_traverse_descendants (account, xaccAccountCommitEdit);
2446 
2447  auto_payment = gnc_prefs_get_bool(GNC_PREFS_GROUP_RECONCILE, GNC_PREF_AUTO_CC_PAYMENT);
2448 
2450  xaccAccountSetReconcileLastDate (account, date);
2451 
2452  if (auto_payment &&
2453  (xaccAccountGetType (account) == ACCT_TYPE_CREDIT) &&
2454  (gnc_numeric_negative_p (recnData->new_ending)))
2455  {
2456  Account *payment_account;
2457  XferDialog *xfer;
2458 
2459  xfer = gnc_xfer_dialog (GTK_WIDGET (gnc_ui_get_main_window (recnData->window)), account);
2460 
2461  gnc_xfer_dialog_set_amount(xfer, gnc_numeric_neg (recnData->new_ending));
2462 
2463  payment_account = find_payment_account (account);
2464  if (payment_account != NULL)
2465  gnc_xfer_dialog_select_from_account (xfer, payment_account);
2466  }
2467 
2468  gnc_close_gui_component_by_data (WINDOW_RECONCILE_CM_CLASS, recnData);
2469 }
2470 
2471 
2472 /********************************************************************\
2473  * recnPostponeCB *
2474  * saves reconcile information for later use *
2475  * *
2476  * Args: w - the widget that called us *
2477  * data - the data struct for this window *
2478  * Return: none *
2479 \********************************************************************/
2480 static void
2481 recnPostponeCB (GSimpleAction *simple,
2482  GVariant *parameter,
2483  gpointer user_data)
2484 {
2485  auto recnData = static_cast<RecnWindow*>(user_data);
2486  Account *account;
2487 
2488  {
2489  const char *message = _("Do you want to postpone this reconciliation "
2490  "and finish it later?");
2491  if (!gnc_verify_dialog (GTK_WINDOW (recnData->window), FALSE, "%s", message))
2492  return;
2493  }
2494 
2495  gnc_suspend_gui_refresh ();
2496 
2497  recnData->delete_refresh = TRUE;
2498  account = recn_get_account (recnData);
2499 
2500  acct_traverse_descendants (account, xaccAccountBeginEdit);
2501  gnc_reconcile_view_postpone (GNC_RECONCILE_VIEW(recnData->credit));
2502  gnc_reconcile_view_postpone (GNC_RECONCILE_VIEW(recnData->debit));
2503  acct_traverse_descendants (account, xaccAccountCommitEdit);
2504 
2505  xaccAccountSetReconcilePostponeDate (account, recnData->statement_date);
2506  xaccAccountSetReconcilePostponeBalance (account, recnData->new_ending);
2507 
2508  gnc_close_gui_component_by_data (WINDOW_RECONCILE_CM_CLASS, recnData);
2509 }
2510 
2511 
2512 static void
2513 recnCancelCB (GSimpleAction *simple,
2514  GVariant *parameter,
2515  gpointer user_data)
2516 {
2517  auto recnData = static_cast<RecnWindow*>(user_data);
2518  recn_cancel(recnData);
2519 }
2520 
2521 /********************************************************************\
2522  * recnAutoClearCB *
2523  * handles the auto-clear button click *
2524  * *
2525  * Args: simple - the action *
2526  * parameter - unused *
2527  * user_data - the reconcile window data *
2528  * Return: none *
2529 \********************************************************************/
2530 static void
2531 recnAutoClearCB (GSimpleAction *simple,
2532  GVariant *parameter,
2533  gpointer user_data)
2534 {
2535  auto recnData = static_cast<RecnWindow*>(user_data);
2536 
2537  if (recnData->autoclear_splits.empty())
2538  return;
2539 
2540  gnc_suspend_gui_refresh ();
2541  gnc_reconcile_view_unclear_all (GNC_RECONCILE_VIEW(recnData->debit));
2542  gnc_reconcile_view_unclear_all (GNC_RECONCILE_VIEW(recnData->credit));
2543  std::for_each (recnData->autoclear_splits.begin(),
2544  recnData->autoclear_splits.end(),
2545  [recnData](Split* split)
2546  {
2547  auto view = gnc_numeric_positive_p (xaccSplitGetAmount (split))
2548  ? recnData->debit : recnData->credit;
2549  gnc_reconcile_view_set_cleared (GNC_RECONCILE_VIEW(view), split);
2550  });
2551  recnRefresh (recnData);
2552  gnc_resume_gui_refresh ();
2553 }
GncPluginPage * gnc_plugin_page_register_new(Account *account, gboolean subaccounts)
Create a new "register" plugin page, given a pointer to an account.
High-Level API for imposing Lot constraints.
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
Equivalence predicate: Returns TRUE (1) if a and b represent the same number.
gboolean xaccAccountGetAutoInterest(const Account *acc)
Get the "auto interest" flag for an account.
Definition: Account.cpp:4134
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
The instance data structure for a content plugin.
Date and Time handling routines.
This file contains the functions to present a gui to the user for creating a new account or editing a...
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3241
gtk helper routines.
int xaccAccountGetCommoditySCU(const Account *acc)
Return the SCU for the account.
Definition: Account.cpp:2719
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value...
void xaccAccountSetReconcileLastDate(Account *acc, time64 last_date)
DOCUMENT ME!
Definition: Account.cpp:4543
STRUCTS.
Functions that are supported by all types of windows.
char xaccSplitGetReconcile(const Split *split)
Returns the value of the reconcile flag.
gpointer gnc_account_foreach_descendant_until(const Account *acc, AccountCb2 thunk, gpointer user_data)
This method will traverse all children of this accounts and their descendants, calling &#39;func&#39; on each...
Definition: Account.cpp:3218
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
void xaccAccountSetReconcileLastInterval(Account *acc, int months, int days)
DOCUMENT ME!
Definition: Account.cpp:4573
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
gint gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
Returns 1 if a>b, -1 if b>a, 0 if a == b.
The cash account type is used to denote a shoe-box or pillowcase stuffed with * cash.
Definition: Account.h:110
const char * gnc_account_get_debit_string(GNCAccountType acct_type)
Get the debit string associated with this account type.
Definition: Account.cpp:4050
void gnc_ui_edit_account_window(GtkWindow *parent, Account *account)
Display a window for editing the attributes of an existing account.
struct tm * gnc_localtime_r(const time64 *secs, struct tm *time)
fill out a time struct from a 64-bit time value adjusted for the current time zone.
Definition: gnc-date.cpp:115
void gnc_main_window_open_page(GncMainWindow *window, GncPluginPage *page)
Display a data plugin page in a window.
gboolean gnc_numeric_negative_p(gnc_numeric a)
Returns 1 if a < 0, otherwise returns 0.
gboolean xaccAccountGetReconcilePostponeDate(const Account *acc, time64 *postpone_date)
DOCUMENT ME!
Definition: Account.cpp:4583
void xaccTransDestroy(Transaction *trans)
Destroys a transaction.
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
#define xaccAccountGetGUID(X)
Definition: Account.h:252
convert single-entry accounts to clean double-entry
char * qof_print_date(time64 secs)
Convenience; calls through to qof_print_date_dmy_buff().
Definition: gnc-date.cpp:610
gchar * gnc_account_get_full_name(const Account *account)
The gnc_account_get_full_name routine returns the fully qualified name of the account using the given...
Definition: Account.cpp:3279
Functions providing a register page for the GnuCash UI.
Account public routines (C++ api)
void gnc_gnome_help(GtkWindow *parent, const char *file_name, const char *anchor)
Launch the systems default help browser, gnome&#39;s yelp for linux, and open to a given link within a gi...
#define YREC
The Split has been reconciled.
Definition: Split.h:74
gboolean guid_equal(const GncGUID *guid_1, const GncGUID *guid_2)
Given two GUIDs, return TRUE if they are non-NULL and equal.
Definition: guid.cpp:237
void xaccAccountTreeScrubOrphans(Account *acc, QofPercentageFunc percentagefunc)
The xaccAccountTreeScrubOrphans() method performs this scrub for the indicated account and its childr...
Definition: Scrub.cpp:173
void xaccAccountClearReconcilePostpone(Account *acc)
DOCUMENT ME!
Definition: Account.cpp:4633
The bank account type denotes a savings or checking account held at a bank.
Definition: Account.h:107
void xaccAccountSetReconcilePostponeDate(Account *acc, time64 postpone_date)
DOCUMENT ME!
Definition: Account.cpp:4598
Gnome specific utility functions.
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition: gnc-date.h:108
Additional event handling code.
void xaccAccountSetReconcilePostponeBalance(Account *acc, gnc_numeric balance)
DOCUMENT ME!
Definition: Account.cpp:4623
asset (and liability) accounts indicate generic, generalized accounts that are none of the above...
Definition: Account.h:116
gnc_numeric xaccAccountGetBalanceAsOfDate(Account *acc, time64 date)
Get the balance of the account at the end of the day before the date specified.
Definition: Account.cpp:3493
gboolean xaccAccountGetReconcileLastDate(const Account *acc, time64 *last_date)
DOCUMENT ME!
Definition: Account.cpp:4525
#define CREC
The Split has been cleared.
Definition: Split.h:73
GNCAccountType
The account types are used to determine how the transaction data in the account is displayed...
Definition: Account.h:101
gboolean gnc_numeric_positive_p(gnc_numeric a)
Returns 1 if a > 0, otherwise returns 0.
#define xaccTransGetGUID(X)
Definition: Transaction.h:788
Generic api to store and retrieve preferences.
void gnc_add_accelerator_keys_for_menu(GtkWidget *menu, GMenuModel *model, GtkAccelGroup *accel_group)
Add accelerator keys for menu item widgets.
GList * gnc_account_get_descendants(const Account *account)
This routine returns a flat list of all of the accounts that are descendants of the specified account...
Definition: Account.cpp:3018
gboolean xaccAccountGetReconcileChildrenStatus(const Account *acc)
DOCUMENT ME!
Definition: Account.cpp:4854
gboolean xaccAccountGetReconcileLastInterval(const Account *acc, int *months, int *days)
DOCUMENT ME!
Definition: Account.cpp:4552
time64 xaccSplitGetDateReconciled(const Split *split)
Retrieve the date when the Split was reconciled.
Definition: Split.cpp:1823
const char * gnc_account_get_credit_string(GNCAccountType acct_type)
Get the credit string associated with this account type.
Definition: Account.cpp:4062
Split * gnc_account_find_split(const Account *acc, std::function< bool(const Split *)> predicate, bool reverse)
scans account split list (in forward or reverse order) until predicate split->bool returns true...
Definition: Account.cpp:1166
void gnc_gdate_set_time64(GDate *gd, time64 time)
Set a GDate to a time64.
Definition: gnc-date.cpp:1314
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition: Account.cpp:1473
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
Definition: gmock-Split.cpp:53
time64 gnc_time64_get_day_end_gdate(const GDate *date)
The gnc_time64_get_day_end() routine will take the given time in GLib GDate format and adjust it to t...
Definition: gnc-date.cpp:1502
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3375
time64 gnc_time64_get_today_end(void)
The gnc_time64_get_today_end() routine returns a time64 value corresponding to the last second of tod...
Definition: gnc-date.cpp:1426
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:262
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
void gnc_plugin_page_register_clear_current_filter(GncPluginPage *plugin_page)
This function clears the registers current filter.
time64 gnc_time64_get_day_end(time64 time_val)
The gnc_time64_get_day_end() routine will take the given time in seconds and adjust it to the last se...
Definition: gnc-date.cpp:1386
File path resolution utility functions.
Not a type.
Definition: Account.h:105
The type used to store guids in C.
Definition: guid.h:75
GNCSplitReg * gnc_plugin_page_register_get_gsr(GncPluginPage *plugin_page)
Get the GNCSplitReg data structure associated with this register page.
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition: Account.cpp:1514
size_t qof_print_date_buff(char *buff, size_t buflen, time64 secs)
Convenience: calls through to qof_print_date_dmy_buff().
Definition: gnc-date.cpp:574
SplitList * xaccTransGetSplitList(const Transaction *trans)
The xaccTransGetSplitList() method returns a GList of the splits in a transaction.
gboolean gnc_commodity_equiv(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equivalent.
void xaccAccountSetReconcileChildrenStatus(Account *acc, gboolean status)
DOCUMENT ME!
Definition: Account.cpp:4841
The Credit card account is used to denote credit (e.g.
Definition: Account.h:113
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account&#39;s commodity.
Definition: gmock-Split.cpp:69
gboolean xaccAccountGetReconcilePostponeBalance(const Account *acc, gnc_numeric *balance)
DOCUMENT ME!
Definition: Account.cpp:4607
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id...
Definition: Account.cpp:2048