GnuCash  5.6-150-g038405b370+
import-main-matcher.cpp
1 /********************************************************************\
2  * import-main-matcher.c - Transaction matcher main window *
3  * *
4  * Copyright (C) 2002 Benoit Grégoire <bock@step.polymtl.ca> *
5  * Copyright (C) 2002 Christian Stimming *
6  * Copyright (c) 2006 David Hampton <hampton@employees.org> *
7  * Copyright (C) 2012 Robert Fewell *
8  * *
9  * This program is free software; you can redistribute it and/or *
10  * modify it under the terms of the GNU General Public License as *
11  * published by the Free Software Foundation; either version 2 of *
12  * the License, or (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License*
20  * along with this program; if not, contact: *
21  * *
22  * Free Software Foundation Voice: +1-617-542-5942 *
23  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
24  * Boston, MA 02110-1301, USA gnu@gnu.org *
25 \********************************************************************/
35 #include <config.h>
36 
37 #include <gtk/gtk.h>
38 #include <glib/gi18n.h>
39 #include <stdbool.h>
40 
41 #include <memory>
42 #include <algorithm>
43 #include <vector>
44 
45 #include "import-main-matcher.h"
46 
47 #include "Account.hpp"
48 #include "dialog-transfer.h"
49 #include "dialog-utils.h"
50 #include "gnc-glib-utils.h"
51 #include "gnc-ui.h"
52 #include "gnc-ui-util.h"
53 #include "gnc-engine.h"
54 #include "gnc-gtk-utils.h"
55 #include "import-settings.h"
56 #include "import-backend.h"
57 #include "import-account-matcher.h"
58 #include "import-pending-matches.h"
59 #include "gnc-component-manager.h"
60 #include "guid.h"
61 #include "gnc-session.h"
62 #include "Query.h"
63 
64 #define GNC_PREFS_GROUP "dialogs.import.generic.transaction-list"
65 #define IMPORT_MAIN_MATCHER_CM_CLASS "transaction-matcher-dialog"
66 
68 {
69  GtkWidget *main_widget;
70  GtkTreeView *view;
71  GNCImportSettings *user_settings;
72  int selected_row;
73  bool dark_theme;
74  GNCTransactionProcessedCB transaction_processed_cb;
75  gpointer user_data;
76  GNCImportPendingMatches *pending_matches;
77  GtkTreeViewColumn *account_column;
78  GtkTreeViewColumn *memo_column;
79  GtkWidget *show_account_column;
80  GtkWidget *show_matched_info;
81  GtkWidget *append_text; // Update+Clear: Append import Desc/Notes to matched Desc/Notes
82  GtkWidget *reconcile_after_close;
83  bool add_toggled; // flag to indicate that add has been toggled to stop selection
84  gint id;
85  GSList* temp_trans_list; // Temporary list of imported transactions
86  GHashTable* acct_id_hash; // Hash table, per account, of list of transaction IDs.
87  GSList* edited_accounts; // List of accounts currently edited.
88 
89  /* only when editing fields */
90  bool can_edit_desc;
91  bool can_edit_notes;
92  bool can_edit_memo;
93 
94  GHashTable *desc_hash;
95  GHashTable *notes_hash;
96  GHashTable *memo_hash;
97 
98  GList *new_strings;
99 };
100 
101 enum downloaded_cols
102 {
103  DOWNLOADED_COL_DATE_TXT = 0,
104  DOWNLOADED_COL_DATE_INT64, // used only for sorting
105  DOWNLOADED_COL_ACCOUNT,
106  DOWNLOADED_COL_AMOUNT,
107  DOWNLOADED_COL_AMOUNT_DOUBLE, // used only for sorting
108  DOWNLOADED_COL_DESCRIPTION,
109  DOWNLOADED_COL_DESCRIPTION_ORIGINAL,
110  DOWNLOADED_COL_DESCRIPTION_STYLE,
111  DOWNLOADED_COL_MEMO,
112  DOWNLOADED_COL_MEMO_ORIGINAL,
113  DOWNLOADED_COL_MEMO_STYLE,
114  DOWNLOADED_COL_NOTES_ORIGINAL,
115  DOWNLOADED_COL_ACTION_ADD,
116  DOWNLOADED_COL_ACTION_CLEAR,
117  DOWNLOADED_COL_ACTION_UPDATE,
118  DOWNLOADED_COL_ACTION_INFO,
119  DOWNLOADED_COL_ACTION_PIXBUF,
120  DOWNLOADED_COL_DATA,
121  DOWNLOADED_COL_COLOR,
122  DOWNLOADED_COL_ENABLE,
123  NUM_DOWNLOADED_COLS
124 };
125 
126 #define CSS_INT_REQUIRED_CLASS "gnc-class-intervention-required"
127 #define CSS_INT_PROB_REQUIRED_CLASS "gnc-class-intervention-probably-required"
128 #define CSS_INT_NOT_REQUIRED_CLASS "gnc-class-intervention-not-required"
129 
130 /* Define log domain for extended debugging of matcher */
131 #define G_MOD_IMPORT_MATCHER "gnc.import.main-matcher"
132 /*static QofLogModule log_module = GNC_MOD_IMPORT;*/
133 static QofLogModule log_module = G_MOD_IMPORT_MATCHER;
134 
135 static const gpointer one = GINT_TO_POINTER (1);
136 
137 extern "C" {
138 void on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info);
139 void on_matcher_cancel_clicked (GtkButton *button, gpointer user_data);
140 bool on_matcher_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data);
141 void on_matcher_help_clicked (GtkButton *button, gpointer user_data);
142 void on_matcher_help_close_clicked (GtkButton *button, gpointer user_data);
143 }
144 
145 static void gnc_gen_trans_list_create_matches (GNCImportMainMatcher *gui);
146 
147 /* Local prototypes */
148 static void gnc_gen_trans_assign_transfer_account (GtkTreeView *treeview,
149  bool *first,
150  bool is_selection,
151  GtkTreePath *path,
152  Account **new_acc,
153  GNCImportMainMatcher *info);
154 static void gnc_gen_trans_assign_transfer_account_to_selection_cb (GtkMenuItem *menuitem,
155  GNCImportMainMatcher *info);
156 static void gnc_gen_trans_view_popup_menu (GtkTreeView *treeview,
157  GdkEvent *event,
158  GNCImportMainMatcher *info);
159 static bool gnc_gen_trans_onButtonPressed_cb (GtkTreeView *treeview,
160  GdkEvent *event,
161  GNCImportMainMatcher *info);
162 static bool gnc_gen_trans_onPopupMenu_cb (GtkTreeView *treeview,
163  GNCImportMainMatcher *info);
164 static void refresh_model_row (GNCImportMainMatcher *gui,
165  GtkTreeModel *model,
166  GtkTreeIter *iter,
167  GNCImportTransInfo *info);
168 static bool query_tooltip_tree_view_cb (GtkWidget *widget, gint x, gint y,
169  bool keyboard_tip,
170  GtkTooltip *tooltip,
171  gpointer user_data);
172 /* end local prototypes */
173 
174 static void
175 update_all_balances (GNCImportMainMatcher *info)
176 {
177  for (GSList* iter = info->edited_accounts; iter; iter=iter->next)
178  {
179  auto acct = static_cast<Account*>(iter->data);
182  }
183  g_slist_free (info->edited_accounts);
184  info->edited_accounts = NULL;
185 }
186 
187 static void
188 defer_bal_computation (GNCImportMainMatcher *info, Account* acc)
189 {
191  {
193  info->edited_accounts = g_slist_prepend (info->edited_accounts, acc);
194  }
195 }
196 
197 void
198 gnc_gen_trans_list_delete (GNCImportMainMatcher *info)
199 {
200 
201  if (info == NULL)
202  return;
203 
204  GtkTreeModel *model = gtk_tree_view_get_model (info->view);
205  GtkTreeIter iter;
206  if (gtk_tree_model_get_iter_first (model, &iter))
207  {
208  do
209  {
210  GNCImportTransInfo *trans_info;
211  gtk_tree_model_get (model, &iter,
212  DOWNLOADED_COL_DATA, &trans_info,
213  -1);
214 
215  if (info->transaction_processed_cb)
216  {
217  info->transaction_processed_cb (trans_info, false,
218  info->user_data);
219  }
220  }
221  while (gtk_tree_model_iter_next (model, &iter));
222  }
223 
224  if (GTK_IS_DIALOG(info->main_widget))
225  {
226  gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(info->main_widget));
227  gnc_import_Settings_delete (info->user_settings);
228  gnc_unregister_gui_component (info->id);
229  gtk_widget_destroy (GTK_WIDGET(info->main_widget));
230  }
231  else
232  gnc_import_Settings_delete (info->user_settings);
233 
234  g_slist_free_full (info->temp_trans_list, (GDestroyNotify) gnc_import_TransInfo_delete);
235  info->temp_trans_list = NULL;
236 
237  // We've deferred balance computations on many accounts. Let's do it now that we're done.
238  update_all_balances (info);
239 
240  gnc_import_PendingMatches_delete (info->pending_matches);
241  g_hash_table_destroy (info->acct_id_hash);
242  g_hash_table_destroy (info->desc_hash);
243  g_hash_table_destroy (info->notes_hash);
244  g_hash_table_destroy (info->memo_hash);
245 
246  g_list_free_full (info->new_strings, (GDestroyNotify)g_free);
247 
248  g_free (info);
249 
250  if (!gnc_gui_refresh_suspended ())
251  gnc_gui_refresh_all ();
252 }
253 
254 bool
255 gnc_gen_trans_list_empty (GNCImportMainMatcher *info)
256 {
257  g_assert (info);
258 
259  GtkTreeIter iter;
260  GtkTreeModel *model = gtk_tree_view_get_model (info->view);
261  // Check that both the tree model and the temporary list are empty.
262  return !gtk_tree_model_get_iter_first (model, &iter) && !info->temp_trans_list;
263 }
264 
265 static void
266 gnc_gen_trans_list_show_accounts_column (GNCImportMainMatcher *info)
267 {
268  g_assert (info);
269 
270  GtkTreeModel *model = gtk_tree_view_get_model (info->view);
271  if (gtk_tree_model_iter_n_children (model, NULL) > 1)
272  {
273  bool multiple_accounts = false;
274  GtkTreeIter iter;
275 
276  /* Get first row in list store */
277  bool valid = gtk_tree_model_get_iter_first (model, &iter);
278  if (valid)
279  {
280  gchar *account_name;
281  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_ACCOUNT, &account_name, -1);
282 
283  valid = gtk_tree_model_iter_next (model, &iter);
284 
285  while (valid)
286  {
287  gchar *test_account_name;
288  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_ACCOUNT, &test_account_name, -1);
289  if (g_strcmp0 (account_name, test_account_name) != 0)
290  {
291  multiple_accounts = true;
292  g_free (test_account_name);
293  break;
294  }
295  valid = gtk_tree_model_iter_next (model, &iter);
296  g_free (test_account_name);
297  }
298  g_free (account_name);
299  }
300  // now toggle the column
301  if (multiple_accounts)
302  {
303  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), true);
304  gtk_tree_view_expand_all (info->view);
305  }
306  else
307  {
308  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), false);
309  gtk_tree_view_collapse_all (info->view);
310  }
311  }
312 }
313 
314 // This returns the transaction ID of the first match candidate in match_list
315 static const GncGUID*
316 get_top_trans_match_id (GList* match_list)
317 {
318  if (!match_list || !match_list->data) return NULL;
319  auto match_info = static_cast<GNCImportMatchInfo *>(match_list->data);
320  Transaction *trans = match_info->trans;
321  return xaccTransGetGUID (trans);
322 }
323 
324 // This returns the transaction score of the first match candidate in match_list
325 static gint
326 get_top_trans_match_score (GList* match_list)
327 {
328  if (!match_list || !match_list->data) return 0;
329  auto match_info = static_cast<GNCImportMatchInfo *>(match_list->data);
330  return match_info->probability;
331 }
332 
333 static GList*
334 get_trans_match_list (GtkTreeModel* model, GtkTreeIter* iter)
335 {
336  GNCImportTransInfo *transaction_info;
337  gtk_tree_model_get (model, iter,
338  DOWNLOADED_COL_DATA, &transaction_info,
339  -1);
340  return gnc_import_TransInfo_get_match_list (transaction_info);
341 }
342 
343 static GNCImportTransInfo*
344 get_trans_info (GtkTreeModel* model, GtkTreeIter* iter)
345 {
346  GNCImportTransInfo *transaction_info;
347  gtk_tree_model_get (model, iter,
348  DOWNLOADED_COL_DATA, &transaction_info,
349  -1);
350  return transaction_info;
351 }
352 /* This function finds the top matching register transaction for the imported transaction pointed to by iter
353  * It then goes through the list of all other imported transactions and creates a list of the ones that
354  * have the same register transaction as their top match (i.e., are in conflict). It finds the best of them
355  * (match-score-wise) and returns the rest as a list. The imported transactions in that list will get their
356  * top match modified. */
357 static GList*
358 get_conflict_list (GtkTreeModel* model, GtkTreeIter import_iter, GncGUID* id, gint best_match)
359 {
360  GtkTreeIter iter = import_iter;
361  GNCImportTransInfo* best_import = get_trans_info (model, &import_iter);
362  GList* conflicts = g_list_prepend (NULL, best_import);
363 
364  while (gtk_tree_model_iter_next (model, &iter))
365  {
366  gint match_score = 0;
367  GNCImportTransInfo* trans_info;
368  GncGUID id2;
369  // Get the ID of the top matching trans for this imported trans.
370  GList* register_iter = get_trans_match_list (model, &iter);
371  if (!register_iter || !register_iter->data)
372  continue;
373 
374  id2 = *get_top_trans_match_id (register_iter);
375  if (!guid_equal (id, &id2))
376  continue;
377 
378  // Conflict. Get the match score, add this transaction to our list.
379  match_score = get_top_trans_match_score (register_iter);
380  trans_info = get_trans_info (model, &iter);
381  conflicts = g_list_prepend (conflicts, trans_info);
382 
383  if (match_score > best_match)
384  {
385  // Keep track of the imported transaction with the best score.
386  best_match = match_score;
387  best_import = trans_info;
388  }
389  }
390 
391  // Remove the best match from the list of conflicts, as it will keep its match
392  conflicts = g_list_remove (conflicts, best_import);
393  return conflicts;
394 }
395 
396 static void
397 remove_top_matches (GList* conflicts)
398 {
399  for (GList* iter = conflicts; iter && iter->data; iter=iter->next)
400  gnc_import_TransInfo_remove_top_match (static_cast<GNCImportTransInfo*>(iter->data));
401 }
402 
403 static void
404 resolve_conflicts (GNCImportMainMatcher *info)
405 {
406  GtkTreeModel* model = gtk_tree_view_get_model (info->view);
407  GtkTreeIter import_iter;
408  gint best_match = 0;
409 
410  /* A greedy conflict resolution. Find all imported trans that vie for the same
411  * register trans. Assign the reg trans to the imported trans with the best match.
412  * Loop over the imported transactions */
413  bool valid = gtk_tree_model_get_iter_first (model, &import_iter);
414  while (valid)
415  {
416  GList *match_list = get_trans_match_list (model, &import_iter);
417  if (!match_list || !match_list->data)
418  {
419  valid = gtk_tree_model_iter_next (model, &import_iter);
420  continue;
421  }
422 
423  // The ID of the best current match for this imported trans
424  GncGUID id = *get_top_trans_match_id (match_list);
425  best_match = get_top_trans_match_score (match_list);
426  /* Get a list of all imported transactions that have a conflict with this one.
427  * The returned list excludes the best transaction. */
428  GList *conflicts = get_conflict_list (model, import_iter, &id, best_match);
429 
430  if (conflicts)
431  {
432  remove_top_matches (conflicts);
433  /* Go back to the beginning here, because a nth choice
434  * could now conflict with a previously assigned first choice. */
435  valid = gtk_tree_model_get_iter_first (model, &import_iter);
436  }
437  else
438  valid = gtk_tree_model_iter_next (model, &import_iter);
439  /* NOTE: The loop is guaranteed to terminate because whenever we go back to the top
440  * we remove at least 1 match, and there's a finite number of them. */
441 
442  g_list_free (conflicts);
443  }
444 
445  // Refresh all
446  valid = gtk_tree_model_get_iter_first (model, &import_iter);
447  while (valid)
448  {
449  refresh_model_row (info, model, &import_iter, get_trans_info (model, &import_iter));
450  valid = gtk_tree_model_iter_next (model, &import_iter);
451  }
452 }
453 
454 
455 static void
456 load_hash_tables (GNCImportMainMatcher *info)
457 {
458  GtkTreeModel *model = gtk_tree_view_get_model (info->view);
459  GtkTreeIter import_iter;
460  GList *accounts_list = NULL;
461  bool valid = gtk_tree_model_get_iter_first (model, &import_iter);
462  while (valid)
463  {
464  GNCImportTransInfo *trans_info = get_trans_info (model, &import_iter);
465  Split *s = gnc_import_TransInfo_get_fsplit (trans_info);
466  Account *acc = xaccSplitGetAccount (s);
467  if (!g_list_find (accounts_list, acc))
468  accounts_list = g_list_prepend (accounts_list, acc);
469  valid = gtk_tree_model_iter_next (model, &import_iter);
470  }
471  for (GList *m = accounts_list; m; m = m->next)
472  {
473  for (auto s : xaccAccountGetSplits (static_cast<Account*>(m->data)))
474  {
475  const Transaction *t = xaccSplitGetParent (s);
476 
477  const gchar *key = xaccTransGetDescription (t);
478  if (key && *key)
479  g_hash_table_insert (info->desc_hash, (gpointer)key, one);
480 
481  key = xaccTransGetNotes (t);
482  if (key && *key)
483  g_hash_table_insert (info->notes_hash, (gpointer)key, one);
484 
485  key = xaccSplitGetMemo (s);
486  if (key && *key)
487  g_hash_table_insert (info->memo_hash, (gpointer)key, one);
488  }
489  }
490  g_list_free (accounts_list);
491 }
492 
493 void
494 gnc_gen_trans_list_show_all (GNCImportMainMatcher *info)
495 {
496  g_assert (info);
497 
498  // Set initial state of Append checkbox to same as last import for this account.
499  // Get the import account from the first split in first transaction.
500  GSList *temp_trans_list = info->temp_trans_list;
501  if (!temp_trans_list)
502  {
503  gnc_info_dialog (GTK_WINDOW (info->main_widget), "%s", _("No new transactions were found in this import."));
504  return;
505  }
506  auto trans_info = static_cast<GNCImportTransInfo *>(temp_trans_list->data);
507  Split *first_split = gnc_import_TransInfo_get_fsplit (trans_info);
508  Account *account = xaccSplitGetAccount(first_split);
509  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (info->append_text),
510  xaccAccountGetAppendText(account));
511 
512  gnc_gen_trans_list_create_matches (info);
513  load_hash_tables (info);
514  resolve_conflicts (info);
515  gtk_widget_show_all (GTK_WIDGET(info->main_widget));
516  gnc_gen_trans_list_show_accounts_column (info);
517 }
518 
519 static void acc_begin_edit (GList **accounts_modified, Account *acc)
520 {
521  if (!acc || !accounts_modified || g_list_find (*accounts_modified, acc))
522  return;
523 
524  DEBUG ("xaccAccountBeginEdit for acc %s", xaccAccountGetName (acc));
525  xaccAccountBeginEdit (acc);
526  *accounts_modified = g_list_prepend (*accounts_modified, acc);
527 }
528 void
529 on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info)
530 {
531  g_assert (info);
532 
533  /* DEBUG ("Begin") */
534 
535  GtkTreeModel *model = gtk_tree_view_get_model (info->view);
536  GtkTreeIter iter;
537  if (!gtk_tree_model_get_iter_first (model, &iter))
538  {
539  // No transaction, we can just close the dialog.
541  return;
542  }
543 
544  /* Don't run any queries and/or split sorts while processing the matcher
545  results. */
546  gnc_suspend_gui_refresh ();
547  bool first_tran = true;
548  bool append_text = gtk_toggle_button_get_active ((GtkToggleButton*) info->append_text);
549  GList *accounts_modified = NULL;
550  do
551  {
552  GNCImportTransInfo *trans_info;
553  gtk_tree_model_get (model, &iter,
554  DOWNLOADED_COL_DATA, &trans_info,
555  -1);
556 
557  Split* first_split = gnc_import_TransInfo_get_fsplit (trans_info);
558  Transaction *trans = xaccSplitGetParent (first_split);
559 
560  for (GList *n = xaccTransGetSplitList (trans); n; n = g_list_next (n))
561  acc_begin_edit (&accounts_modified, xaccSplitGetAccount (static_cast<Split*>(n->data)));
562 
563  // Allow the backend to know if the Append checkbox is ticked or unticked
564  // by propagating info->append_text to every trans_info->append_text
565  gnc_import_TransInfo_set_append_text( trans_info, append_text);
566 
567  // When processing the first transaction,
568  // save the state of the Append checkbox to an account kvp so the same state can be
569  // defaulted next time this account is imported.
570  // Get the import account from the first split.
571  if (first_tran)
572  {
573  xaccAccountSetAppendText (xaccSplitGetAccount(first_split), append_text);
574  first_tran = false;
575  }
576 
577  Account *dest_acc = gnc_import_TransInfo_get_destacc (trans_info);
578  acc_begin_edit (&accounts_modified, dest_acc);
579 
580  if (gnc_import_process_trans_item (NULL, trans_info))
581  {
582  if (info->transaction_processed_cb)
583  {
584  info->transaction_processed_cb (trans_info, true,
585  info->user_data);
586  }
587  }
588  }
589  while (gtk_tree_model_iter_next (model, &iter));
590 
592 
593  /* Allow GUI refresh again. */
594  gnc_resume_gui_refresh ();
595 
596  /* DEBUG ("End") */
597  g_list_free_full (accounts_modified, (GDestroyNotify)xaccAccountCommitEdit);
598 }
599 
600 void
601 on_matcher_cancel_clicked (GtkButton *button, gpointer user_data)
602 {
603  auto info = static_cast<GNCImportMainMatcher *>(user_data);
605 }
606 
607 bool
608 on_matcher_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
609 {
610  auto info = static_cast<GNCImportMainMatcher *>(data);
612  return false;
613 }
614 
615 void
616 on_matcher_help_close_clicked (GtkButton *button, gpointer user_data)
617 {
618  auto help_dialog = static_cast<GtkWidget *>(user_data);
619 
620  gtk_widget_destroy (help_dialog);
621 }
622 
623 void
624 on_matcher_help_clicked (GtkButton *button, gpointer user_data)
625 {
626  auto info = static_cast<GNCImportMainMatcher*>(user_data);
627 
628  GtkBuilder *builder = gtk_builder_new ();
629  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer2");
630  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer3");
631  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer4");
632  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer5");
633  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer1");
634  gnc_builder_add_from_file (builder, "dialog-import.glade", "matcher_help_dialog");
635 
636  const gchar *class_extension = NULL;
637  if (info->dark_theme == true)
638  class_extension = "-dark";
639 
640  gchar *int_required_class = g_strconcat (CSS_INT_REQUIRED_CLASS, class_extension, NULL);
641  gchar *int_prob_required_class = g_strconcat (CSS_INT_PROB_REQUIRED_CLASS, class_extension, NULL);
642  gchar *int_not_required_class = g_strconcat (CSS_INT_NOT_REQUIRED_CLASS, class_extension, NULL);
643 
644  GtkWidget *box = GTK_WIDGET(gtk_builder_get_object (builder, "intervention_required_box"));
645  gnc_widget_style_context_add_class (GTK_WIDGET(box), int_required_class);
646 
647  box = GTK_WIDGET(gtk_builder_get_object (builder, "intervention_probably_required_box"));
648  gnc_widget_style_context_add_class (GTK_WIDGET(box), int_prob_required_class);
649 
650  box = GTK_WIDGET(gtk_builder_get_object (builder, "intervention_not_required_box"));
651  gnc_widget_style_context_add_class (GTK_WIDGET(box), int_not_required_class);
652 
653  GtkWidget *help_dialog = GTK_WIDGET(gtk_builder_get_object (builder, "matcher_help_dialog"));
654  gtk_window_set_transient_for (GTK_WINDOW(help_dialog), GTK_WINDOW(info->main_widget));
655 
656  // Set the name for this dialog so it can be easily manipulated with css
657  gtk_widget_set_name (GTK_WIDGET(help_dialog), "gnc-id-import-matcher-help");
658  gnc_widget_style_context_add_class (GTK_WIDGET(help_dialog), "gnc-class-imports");
659 
660  /* Connect the signals */
661  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, help_dialog);
662 
663  g_object_unref (G_OBJECT(builder));
664 
665  g_free (int_required_class);
666  g_free (int_prob_required_class);
667  g_free (int_not_required_class);
668 
669  gtk_widget_show (help_dialog);
670 }
671 
672 static void
673 run_match_dialog (GNCImportMainMatcher *info,
674  GNCImportTransInfo *trans_info)
675 {
676  gnc_import_match_picker_run_and_close (info->main_widget,
677  trans_info, info->pending_matches);
678 }
679 
680 static void
681 set_treeview_selection_from_path (GtkTreeView* view, const char* path)
682 {
683  auto selection = gtk_tree_view_get_selection (view);
684  auto tree_path = gtk_tree_path_new_from_string (path);
685  gtk_tree_selection_select_path (selection, tree_path);
686  gtk_tree_path_free (tree_path);
687 }
688 
689 static void
690 gen_trans_common_toggled_cb (GtkCellRendererToggle *cell_renderer, gchar *path,
691  GNCImportMainMatcher *gui, GNCImportAction action)
692 {
693  auto model = gtk_tree_view_get_model (gui->view);
694  GtkTreeIter tree_iter;
695  g_return_if_fail (gtk_tree_model_get_iter_from_string (model, &tree_iter, path));
696 
697  GNCImportTransInfo *transaction_info;
698  gtk_tree_model_get (model, &tree_iter, DOWNLOADED_COL_DATA, &transaction_info, -1);
699  if (gnc_import_TransInfo_get_action (transaction_info) == action &&
700  gnc_import_Settings_get_action_skip_enabled (gui->user_settings))
701  gnc_import_TransInfo_set_action (transaction_info, GNCImport_SKIP);
702  else
703  gnc_import_TransInfo_set_action (transaction_info, action);
704  refresh_model_row (gui, model, &tree_iter, transaction_info);
705 
706  set_treeview_selection_from_path (GTK_TREE_VIEW(gui->view), path);
707 }
708 
709 static void
710 gnc_gen_trans_add_toggled_cb (GtkCellRendererToggle *cell_renderer,
711  gchar *path,
712  GNCImportMainMatcher *gui)
713 {
714  gen_trans_common_toggled_cb (cell_renderer, path, gui, GNCImport_ADD);
715 }
716 
717 static void
718 gnc_gen_trans_clear_toggled_cb (GtkCellRendererToggle *cell_renderer,
719  gchar *path,
720  GNCImportMainMatcher *gui)
721 {
722  gen_trans_common_toggled_cb (cell_renderer, path, gui, GNCImport_CLEAR);
723 }
724 
725 static void
726 gnc_gen_trans_update_toggled_cb (GtkCellRendererToggle *cell_renderer,
727  gchar *path,
728  GNCImportMainMatcher *gui)
729 {
730  gen_trans_common_toggled_cb (cell_renderer, path, gui, GNCImport_UPDATE);
731 }
732 
733 static void
734 gnc_gen_trans_assign_transfer_account (GtkTreeView *treeview,
735  bool *first,
736  bool is_selection,
737  GtkTreePath *path,
738  Account **new_acc,
739  GNCImportMainMatcher *info)
740 {
741  gchar *path_str = gtk_tree_path_to_string (path);
742  gchar *acct_str = gnc_get_account_name_for_register (*new_acc);
743 
744  ENTER("");
745  DEBUG("first = %s", *first ? "true" : "false");
746  DEBUG("is_selection = %s", is_selection ? "true" : "false");
747  DEBUG("path = %s", path_str);
748  g_free (path_str);
749  DEBUG("account passed in = %s", acct_str);
750  g_free (acct_str);
751 
752  // only allow response at the top level
753  if (gtk_tree_path_get_depth (path) != 1)
754  return;
755 
756  GtkTreeModel *model = gtk_tree_view_get_model (treeview);
757  GtkTreeIter iter;
758  if (gtk_tree_model_get_iter (model, &iter, path))
759  {
760  GNCImportTransInfo *trans_info;
761  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, &trans_info, -1);
762 
763  switch (gnc_import_TransInfo_get_action (trans_info))
764  {
765  case GNCImport_ADD:
766  if (!gnc_import_TransInfo_is_balanced (trans_info))
767  {
768  Account *old_acc = gnc_import_TransInfo_get_destacc (trans_info);
769  if (*first)
770  {
771  gchar *acc_full_name;
772  *new_acc = gnc_import_select_account (info->main_widget,
773  NULL,
774  true,
775  _("Destination account for the auto-balance split."),
777  gnc_import_TransInfo_get_trans (trans_info)),
779  old_acc,
780  NULL);
781  *first = false;
782  acc_full_name = gnc_account_get_full_name (*new_acc);
783  DEBUG("account selected = %s", acc_full_name);
784  g_free (acc_full_name);
785  }
786  if (*new_acc)
787  {
788  gnc_import_TransInfo_set_destacc (trans_info, *new_acc, true);
789  defer_bal_computation (info, *new_acc);
790  }
791  }
792  break;
793  case GNCImport_CLEAR:
794  case GNCImport_UPDATE:
795  if (*first && !is_selection)
796  run_match_dialog (info, trans_info);
797  break;
798  case GNCImport_SKIP:
799  break;
800  default:
801  PERR("InvalidGNCImportValue");
802  break;
803  }
804  refresh_model_row (info, model, &iter, trans_info);
805  }
806  LEAVE("");
807 }
808 
810 {
811 public:
812  void operator()(GtkTreeRowReference* ptr) const { gtk_tree_row_reference_free (ptr); }
813 };
814 
815 using TreeRowReferencePtr = std::unique_ptr<GtkTreeRowReference, TreeRowRefDestructor>;
816 
817 // bug 799246. return a vector of TreeRowReferencePtr, from which
818 // get() will return the GtkTreeRowReference*
819 static std::vector<TreeRowReferencePtr>
820 get_treeview_selection_refs (GtkTreeView *treeview, GtkTreeModel *model)
821 {
822  std::vector<TreeRowReferencePtr> rv;
823 
824  g_return_val_if_fail (GTK_IS_TREE_VIEW (treeview) && GTK_IS_TREE_MODEL (model), rv);
825 
826  auto selection = gtk_tree_view_get_selection (treeview);
827  auto selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
828 
829  for (auto n = selected_rows; n; n = g_list_next (n))
830  rv.emplace_back (gtk_tree_row_reference_new (model, static_cast<GtkTreePath*>(n->data)));
831 
832  g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free);
833  return rv;
834 }
835 
836 static void
837 gnc_gen_trans_assign_transfer_account_to_selection_cb (GtkMenuItem *menuitem,
838  GNCImportMainMatcher *info)
839 {
840  ENTER("");
841 
842  GtkTreeView *treeview = GTK_TREE_VIEW(info->view);
843  GtkTreeModel *model = gtk_tree_view_get_model (treeview);
844  GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
845  auto selected_refs = get_treeview_selection_refs (treeview, model);
846  Account *assigned_account = NULL;
847  bool first = true;
848  bool is_selection = true;
849  auto debugging_enabled{qof_log_check (G_LOG_DOMAIN, QOF_LOG_DEBUG)};
850 
851  DEBUG("Rows in selection = %zu", selected_refs.size());
852 
853  for (const auto& ref : selected_refs)
854  {
855  auto path = gtk_tree_row_reference_get_path (ref.get());
856  if (debugging_enabled)
857  {
858  auto path_str = gtk_tree_path_to_string (path);
859  DEBUG("passing first = %s", first ? "true" : "false");
860  DEBUG("passing is_selection = %s", is_selection ? "true" : "false");
861  DEBUG("passing path = %s", path_str);
862  g_free (path_str);
863  }
864  gnc_gen_trans_assign_transfer_account (treeview,
865  &first, is_selection, path,
866  &assigned_account, info);
867  if (debugging_enabled)
868  {
869  auto fullname = gnc_account_get_full_name (assigned_account);
870  DEBUG("returned value of account = %s", fullname);
871  DEBUG("returned value of first = %s", first ? "true" : "false");
872  g_free (fullname);
873  }
874 
875  gtk_tree_path_free (path);
876  if (!assigned_account)
877  break;
878  }
879 
880  // now reselect the transaction rows. This is very slow if there are lots of transactions.
881  for (const auto& ref : selected_refs)
882  {
883  GtkTreePath *path = gtk_tree_row_reference_get_path (ref.get());
884  gtk_tree_selection_select_path (selection, path);
885  gtk_tree_path_free (path);
886  }
887 
888  LEAVE("");
889 }
890 
891 class RowInfo
892 {
893 public:
894  RowInfo (GtkTreePath *path, GNCImportMainMatcher *info)
895  {
896  init_from_path (path, info);
897  }
898  RowInfo (const TreeRowReferencePtr &ref, GNCImportMainMatcher *info)
899  {
900  auto path = gtk_tree_row_reference_get_path (ref.get());
901  init_from_path (path, info);
902  gtk_tree_path_free (path);
903  }
904  ~RowInfo ()
905  {
906  g_free (m_orig_desc);
907  g_free (m_orig_notes);
908  g_free (m_orig_memo);
909  }
910  GNCImportTransInfo* get_trans_info () { return m_trans_info; };
911  GtkTreeIter* get_iter () { return &m_iter; };
912  const char* get_orig_desc () { return m_orig_desc; };
913  const char* get_orig_notes () { return m_orig_notes; };
914  const char* get_orig_memo () { return m_orig_memo; };
915 private:
916  void init_from_path (GtkTreePath *path, GNCImportMainMatcher *info)
917  {
918  auto model = gtk_tree_view_get_model (info->view);
919  gtk_tree_model_get_iter (model, &m_iter, path);
920  gtk_tree_model_get (model, &m_iter,
921  DOWNLOADED_COL_DATA, &m_trans_info,
922  DOWNLOADED_COL_DESCRIPTION_ORIGINAL, &m_orig_desc,
923  DOWNLOADED_COL_NOTES_ORIGINAL, &m_orig_notes,
924  DOWNLOADED_COL_MEMO_ORIGINAL, &m_orig_memo,
925  -1);
926  }
927  GNCImportTransInfo *m_trans_info;
928  GtkTreeIter m_iter;
929  char *m_orig_desc, *m_orig_notes, *m_orig_memo;
930 };
931 
932 enum
933 {
934  COMPLETION_LIST_ORIGINAL,
935  COMPLETION_LIST_NORMALIZED_FOLDED,
936  NUM_COMPLETION_COLS
937 };
938 
939 static void populate_list (gpointer key, gpointer value, GtkListStore *list)
940 {
941  GtkTreeIter iter;
942  auto original = static_cast<const char*>(key);
943  char *normalized = g_utf8_normalize (original, -1, G_NORMALIZE_NFC);
944  char *normalized_folded = normalized ? g_utf8_casefold (normalized, -1) : NULL;
945  gtk_list_store_append (list, &iter);
946  gtk_list_store_set (list, &iter,
947  COMPLETION_LIST_ORIGINAL, original,
948  COMPLETION_LIST_NORMALIZED_FOLDED, normalized_folded,
949  -1);
950  g_free (normalized_folded);
951  g_free (normalized);
952 }
953 
954 static bool
955 match_func (GtkEntryCompletion *completion, const char *entry_str,
956  GtkTreeIter *iter, gpointer user_data)
957 {
958  auto model = static_cast<GtkTreeModel*>(user_data);
959  gchar *existing_str = NULL;
960  bool ret = false;
961  gtk_tree_model_get (model, iter,
962  COMPLETION_LIST_NORMALIZED_FOLDED, &existing_str,
963  -1);
964  if (existing_str && *existing_str && strstr (existing_str, entry_str))
965  ret = true;
966  g_free (existing_str);
967  return ret;
968 }
969 
970 typedef struct
971 {
972  GtkWidget *entry;
973  GObject *override_widget;
974  bool& can_edit;
975  GHashTable *hash;
976  const char *initial;
977 } EntryInfo;
978 
979 static void override_widget_clicked (GtkWidget *widget, EntryInfo *entryinfo)
980 {
981  gtk_widget_set_visible (GTK_WIDGET (entryinfo->override_widget), false);
982  gtk_widget_set_sensitive (entryinfo->entry, true);
983  gtk_entry_set_text (GTK_ENTRY (entryinfo->entry), "");
984  gtk_widget_grab_focus (entryinfo->entry);
985  entryinfo->can_edit = true;
986 }
987 
988 static void
989 setup_entry (EntryInfo& entryinfo)
990 {
991  auto sensitive = entryinfo.can_edit;
992  auto entry = entryinfo.entry;
993  auto override_widget = GTK_WIDGET (entryinfo.override_widget);
994  auto hash = entryinfo.hash;
995  auto initial = entryinfo.initial;
996 
997  gtk_widget_set_sensitive (entry, sensitive);
998  gtk_widget_set_visible (override_widget, !sensitive);
999 
1000  if (sensitive && initial && *initial)
1001  gtk_entry_set_text (GTK_ENTRY (entry), initial);
1002  else if (!sensitive)
1003  {
1004  gtk_entry_set_text (GTK_ENTRY (entry), _("Click Edit to modify"));
1005  g_signal_connect (override_widget, "clicked", G_CALLBACK (override_widget_clicked),
1006  &entryinfo);
1007  }
1008 
1009  GtkListStore *list = gtk_list_store_new (NUM_COMPLETION_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
1010  g_hash_table_foreach (hash, (GHFunc)populate_list, list);
1011  if (initial && *initial && !g_hash_table_lookup (hash, (gpointer)initial))
1012  populate_list ((gpointer)initial, NULL, list);
1013  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list),
1014  COMPLETION_LIST_ORIGINAL,
1015  GTK_SORT_ASCENDING);
1016 
1017  GtkEntryCompletion *completion = gtk_entry_completion_new ();
1018  gtk_entry_completion_set_model (completion, GTK_TREE_MODEL(list));
1019  gtk_entry_completion_set_text_column (completion, COMPLETION_LIST_ORIGINAL);
1020  gtk_entry_completion_set_match_func (completion,
1021  (GtkEntryCompletionMatchFunc)match_func,
1022  GTK_TREE_MODEL(list), NULL);
1023  gtk_entry_set_completion (GTK_ENTRY (entry), completion);
1024 }
1025 
1026 static bool
1027 input_new_fields (GNCImportMainMatcher *info, RowInfo& rowinfo,
1028  char **new_desc, char **new_notes, char **new_memo)
1029 {
1030  GtkBuilder *builder = gtk_builder_new ();
1031  gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_edit_dialog");
1032 
1033  GtkWidget *dialog = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_edit_dialog"));
1034 
1035  // Set the name for this dialog so it can be easily manipulated with css
1036  gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-import-matcher-edits");
1037  gnc_widget_style_context_add_class (GTK_WIDGET(dialog), "gnc-class-imports");
1038 
1039  GtkWidget *desc_entry = GTK_WIDGET(gtk_builder_get_object (builder, "desc_entry"));
1040  GtkWidget *memo_entry = GTK_WIDGET(gtk_builder_get_object (builder, "memo_entry"));
1041  GtkWidget *notes_entry = GTK_WIDGET(gtk_builder_get_object (builder, "notes_entry"));
1042 
1043  auto trans = gnc_import_TransInfo_get_trans (rowinfo.get_trans_info ());
1044  auto split = gnc_import_TransInfo_get_fsplit (rowinfo.get_trans_info ());
1045 
1046  std::vector<EntryInfo> entries = {
1047  { desc_entry, gtk_builder_get_object (builder, "desc_override"), info->can_edit_desc, info->desc_hash, xaccTransGetDescription (trans) },
1048  { notes_entry, gtk_builder_get_object (builder, "notes_override"), info->can_edit_notes, info->notes_hash, xaccTransGetNotes (trans) },
1049  { memo_entry, gtk_builder_get_object (builder, "memo_override"), info->can_edit_memo, info->memo_hash, xaccSplitGetMemo (split) },
1050  };
1051 
1052  std::for_each (entries.begin(), entries.end(), setup_entry);
1053 
1054  /* ensure that an override button doesn't have focus. find the
1055  first available entry and give it focus. */
1056  auto it = std::find_if (entries.begin(), entries.end(), [](auto info){ return info.can_edit; });
1057  if (it != entries.end())
1058  gtk_widget_grab_focus (it->entry);
1059 
1060  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (info->main_widget));
1061 
1062  // run the dialog
1063  gtk_widget_show (dialog);
1064 
1065  bool retval = false;
1066  switch (gtk_dialog_run (GTK_DIALOG(dialog)))
1067  {
1068  case GTK_RESPONSE_OK:
1069  *new_desc = g_strdup (gtk_entry_get_text (GTK_ENTRY (desc_entry)));
1070  *new_notes = g_strdup (gtk_entry_get_text (GTK_ENTRY (notes_entry)));
1071  *new_memo = g_strdup (gtk_entry_get_text (GTK_ENTRY (memo_entry)));
1072  retval = true;
1073  break;
1074  default:
1075  break;
1076  }
1077 
1078  gtk_widget_destroy (dialog);
1079  g_object_unref (G_OBJECT(builder));
1080  return retval;
1081 }
1082 
1083 static inline void
1084 maybe_add_string (GNCImportMainMatcher *info, GHashTable *hash, const char *str)
1085 {
1086  if (!str || !str[0] || g_hash_table_lookup (hash, str))
1087  return;
1088  char *new_string = g_strdup (str);
1089  info->new_strings = g_list_prepend (info->new_strings, new_string);
1090  g_hash_table_insert (hash, new_string, one);
1091 }
1092 
1093 static void
1094 gnc_gen_trans_set_price_to_selection_cb (GtkMenuItem *menuitem,
1095  GNCImportMainMatcher *info)
1096 {
1097  ENTER("");
1098  g_return_if_fail (info);
1099 
1100  GtkTreeView *treeview = GTK_TREE_VIEW(info->view);
1101  GtkTreeModel *model = gtk_tree_view_get_model (treeview);
1102  GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
1103  GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
1104 
1105  if (!selected_rows)
1106  {
1107  LEAVE ("No selected rows");
1108  return;
1109  }
1110 
1111  for (GList *n = selected_rows; n; n = g_list_next (n))
1112  {
1113  RowInfo row{static_cast<GtkTreePath*>(n->data), info};
1114  auto trans = gnc_import_TransInfo_get_trans (row.get_trans_info ());
1115  time64 post_date = xaccTransGetDate(trans);
1116  auto split = gnc_import_TransInfo_get_fsplit (row.get_trans_info ());
1117  Account *src_acc = xaccSplitGetAccount (split);
1118  auto dest_acc = gnc_import_TransInfo_get_destacc (row.get_trans_info ());
1119  auto dest_value = gnc_import_TransInfo_get_dest_value (row.get_trans_info ());
1120 
1121  XferDialog *xfer = gnc_xfer_dialog(GTK_WIDGET (info->main_widget), src_acc);
1122  gnc_xfer_dialog_select_to_account(xfer, dest_acc);
1123  gnc_xfer_dialog_set_amount(xfer, dest_value);
1124  gnc_xfer_dialog_set_date (xfer, post_date);
1125 
1126  /* All we want is the exchange rate so prevent the user from thinking
1127  * it makes sense to mess with other stuff */
1128  gnc_xfer_dialog_set_from_show_button_active(xfer, false);
1129  gnc_xfer_dialog_set_to_show_button_active(xfer, false);
1130  gnc_xfer_dialog_hide_from_account_tree(xfer);
1131  gnc_xfer_dialog_hide_to_account_tree(xfer);
1132  gnc_numeric exch = gnc_import_TransInfo_get_price (row.get_trans_info ());
1133  gnc_xfer_dialog_is_exchange_dialog(xfer, &exch);
1134 
1135  if (!gnc_xfer_dialog_run_until_done(xfer))
1136  break; /* If the user cancels, return to the payment dialog without changes */
1137 
1138 
1139  /* Note the exchange rate we received is backwards from what we really need:
1140  * it converts value to amount, but the remainder of the code expects
1141  * an exchange rate that converts from amount to value. So let's invert
1142  * the result (though only if that doesn't result in a division by 0). */
1143  if (!gnc_numeric_zero_p(exch))
1144  {
1145  gnc_import_TransInfo_set_price (row.get_trans_info (),
1146  gnc_numeric_invert(exch));
1147  refresh_model_row (info, model, row.get_iter(), row.get_trans_info());
1148  }
1149  }
1150  g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free);
1151  LEAVE("");
1152 }
1153 
1154 static void
1155 gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info)
1156 {
1157 
1158  ENTER("");
1159  g_return_if_fail (info);
1160 
1161  GtkTreeView *treeview = GTK_TREE_VIEW(info->view);
1162  GtkTreeModel *model = gtk_tree_view_get_model (treeview);
1163  GtkTreeStore *store = GTK_TREE_STORE (model);
1164  auto selected_refs = get_treeview_selection_refs (treeview, model);
1165 
1166  if (selected_refs.empty())
1167  {
1168  LEAVE ("No selected rows");
1169  return;
1170  }
1171 
1172  char *new_desc = NULL, *new_notes = NULL, *new_memo = NULL;
1173  RowInfo first_row{selected_refs[0], info};
1174  if (input_new_fields (info, first_row, &new_desc, &new_notes, &new_memo))
1175  {
1176  for (const auto& ref : selected_refs)
1177  {
1178  RowInfo row{ref, info};
1179  auto trans = gnc_import_TransInfo_get_trans (row.get_trans_info ());
1180  auto split = gnc_import_TransInfo_get_fsplit (row.get_trans_info ());
1181  if (info->can_edit_desc)
1182  {
1183  gint style = g_strcmp0 (new_desc, row.get_orig_desc()) ?
1184  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL;
1185  gtk_tree_store_set (store, row.get_iter(),
1186  DOWNLOADED_COL_DESCRIPTION, new_desc,
1187  DOWNLOADED_COL_DESCRIPTION_STYLE, style,
1188  -1);
1189  xaccTransSetDescription (trans, new_desc);
1190  maybe_add_string (info, info->desc_hash, new_desc);
1191  }
1192 
1193  if (info->can_edit_notes)
1194  {
1195  xaccTransSetNotes (trans, new_notes);
1196  maybe_add_string (info, info->notes_hash, new_notes);
1197  }
1198 
1199  if (info->can_edit_memo)
1200  {
1201  gint style = g_strcmp0 (new_memo, row.get_orig_memo()) ?
1202  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL;
1203  gtk_tree_store_set (store, row.get_iter(),
1204  DOWNLOADED_COL_MEMO, new_memo,
1205  DOWNLOADED_COL_MEMO_STYLE, style,
1206  -1);
1207  xaccSplitSetMemo (split, new_memo);
1208  maybe_add_string (info, info->memo_hash, new_memo);
1209  }
1210  }
1211  g_free (new_desc);
1212  g_free (new_memo);
1213  g_free (new_notes);
1214  }
1215  LEAVE("");
1216 }
1217 
1218 static void
1219 gnc_gen_trans_reset_edits_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info)
1220 {
1221  g_return_if_fail (info);
1222  ENTER("gnc_gen_trans_reset_edits_cb");
1223 
1224  GtkTreeView *treeview = GTK_TREE_VIEW(info->view);
1225  GtkTreeModel *model = gtk_tree_view_get_model (treeview);
1226  GtkTreeStore *store = GTK_TREE_STORE (model);
1227  auto selected_refs = get_treeview_selection_refs (treeview, model);
1228 
1229  if (selected_refs.empty())
1230  {
1231  LEAVE ("No selected rows");
1232  return;
1233  }
1234 
1235  for (const auto& ref : selected_refs)
1236  {
1237  RowInfo rowinfo{ref, info};
1238  auto trans = gnc_import_TransInfo_get_trans (rowinfo.get_trans_info ());
1239  auto split = gnc_import_TransInfo_get_fsplit (rowinfo.get_trans_info ());
1240  xaccTransSetDescription (trans, rowinfo.get_orig_desc());
1241  xaccTransSetNotes (trans, rowinfo.get_orig_notes());
1242  xaccSplitSetMemo (split, rowinfo.get_orig_memo());
1243  gtk_tree_store_set (store, rowinfo.get_iter(),
1244  DOWNLOADED_COL_DESCRIPTION, rowinfo.get_orig_desc(),
1245  DOWNLOADED_COL_MEMO, rowinfo.get_orig_memo(),
1246  DOWNLOADED_COL_DESCRIPTION_STYLE, PANGO_STYLE_NORMAL,
1247  DOWNLOADED_COL_MEMO_STYLE, PANGO_STYLE_NORMAL,
1248  -1);
1249  };
1250  LEAVE("");
1251 }
1252 
1253 static void
1254 gnc_gen_trans_row_activated_cb (GtkTreeView *treeview,
1255  GtkTreePath *path,
1256  GtkTreeViewColumn *column,
1257  GNCImportMainMatcher *info)
1258 {
1259  ENTER("");
1260 
1261  bool first = true;
1262  bool is_selection = false;
1263  Account *assigned_account = NULL;
1264  gnc_gen_trans_assign_transfer_account (treeview,
1265  &first, is_selection, path,
1266  &assigned_account, info);
1267 
1268  gtk_tree_selection_select_path (gtk_tree_view_get_selection (treeview), path);
1269 
1270  gchar *namestr = gnc_account_get_full_name (assigned_account);
1271  DEBUG("account returned = %s", namestr);
1272  g_free (namestr);
1273  LEAVE("");
1274 }
1275 
1276 static GNCImportAction
1277 get_action_for_path (GtkTreePath* path, GtkTreeModel *model)
1278 {
1279  GNCImportTransInfo *trans_info;
1280  GtkTreeIter iter;
1281  gtk_tree_model_get_iter (model, &iter, path);
1282  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, &trans_info, -1);
1283  if (!trans_info)
1284  // selected row is a potential match (depth 2)
1285  // instead of an imported transaction (depth 1)
1286  return GNCImport_INVALID_ACTION;
1287  return gnc_import_TransInfo_get_action (trans_info);
1288 }
1289 
1290 static void
1291 gnc_gen_trans_row_changed_cb (GtkTreeSelection *selection,
1292  GNCImportMainMatcher *info)
1293 {
1294  GtkTreeModel *model;
1295  GtkTreeIter iter;
1296 
1297  ENTER("");
1298  if (gtk_tree_selection_count_selected_rows (selection) >= 2)
1299  {
1300  // Unselect rows that should not be selectable
1301  GList* list = gtk_tree_selection_get_selected_rows (selection, &model);
1302  for (GList *n = list; n; n = n->next)
1303  {
1304  auto path = static_cast<GtkTreePath*>(n->data);
1305  if (get_action_for_path (path, model) != GNCImport_ADD)
1306  gtk_tree_selection_unselect_path (selection, path);
1307  }
1308  g_list_free_full (list, (GDestroyNotify)gtk_tree_path_free);
1309  }
1310 
1311  GtkSelectionMode mode = gtk_tree_selection_get_mode (selection);
1312  switch (mode)
1313  {
1314  case GTK_SELECTION_MULTIPLE:
1315  DEBUG("mode = GTK_SELECTION_MULTIPLE, no action");
1316  break;
1317  case GTK_SELECTION_NONE:
1318  DEBUG("mode = GTK_SELECTION_NONE, no action");
1319  break;
1320  case GTK_SELECTION_BROWSE:
1321  DEBUG("mode = GTK_SELECTION_BROWSE->default");
1322  case GTK_SELECTION_SINGLE:
1323  DEBUG("mode = GTK_SELECTION_SINGLE->default");
1324  default:
1325  DEBUG("mode = default unselect selected row");
1326  if (gtk_tree_selection_get_selected (selection, &model, &iter))
1327  {
1328  gtk_tree_selection_unselect_iter (selection, &iter);
1329  }
1330  }
1331  LEAVE("");
1332 }
1333 
1334 static void
1335 gnc_gen_trans_view_popup_menu (GtkTreeView *treeview,
1336  GdkEvent *event,
1337  GNCImportMainMatcher *info)
1338 {
1339  ENTER ("");
1340 
1341  GtkTreeModel *model = gtk_tree_view_get_model (treeview);
1342  GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
1343  GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
1344 
1345  const char *desc = NULL, *memo = NULL, *notes = NULL;
1346  if (selected_rows) /* should never be NULL. collect from first row. */
1347  {
1348  RowInfo first_rowinfo{static_cast<GtkTreePath*>(selected_rows->data), info};
1349  auto trans = gnc_import_TransInfo_get_trans (first_rowinfo.get_trans_info ());
1350  auto split = gnc_import_TransInfo_get_fsplit (first_rowinfo.get_trans_info ());
1351  desc = xaccTransGetDescription (trans);
1352  notes = xaccTransGetNotes (trans);
1353  memo = xaccSplitGetMemo (split);
1354  }
1355 
1356  /* determine which context menu items to enable */
1357  info->can_edit_desc = true;
1358  info->can_edit_notes = true;
1359  info->can_edit_memo = true;
1360  bool can_undo_edits = false;
1361  bool can_update_prices = true;
1362  bool can_assign_acct = true;
1363  for (GList *n = selected_rows; n; n = g_list_next(n))
1364  {
1365  RowInfo rowinfo{static_cast<GtkTreePath*>(n->data), info};
1366 
1367  /* Only allow assigning a destination account for unbalanced transactions */
1368  if (can_assign_acct)
1369  can_assign_acct = !gnc_import_TransInfo_is_balanced (rowinfo.get_trans_info ());
1370 
1371  /* Only allow updating prices for transactions with a destinatin account set
1372  * and for which the destination account commodity is different from the
1373  * transaction currency */
1374  auto trans = gnc_import_TransInfo_get_trans (rowinfo.get_trans_info ());
1375  if (can_update_prices)
1376  {
1377  gnc_commodity *trans_curr = xaccTransGetCurrency (trans);
1378  auto dest_acc = gnc_import_TransInfo_get_destacc (rowinfo.get_trans_info ());
1379  gnc_commodity *acc_comm = xaccAccountGetCommodity (dest_acc);
1380  if (!dest_acc || gnc_commodity_equiv (trans_curr, acc_comm))
1381  can_update_prices = false;
1382  }
1383 
1384  /* Only allow editing desc/notes/memo if they are equal for all selected
1385  * transactions */
1386  auto split = gnc_import_TransInfo_get_fsplit (rowinfo.get_trans_info ());
1387  if (info->can_edit_desc)
1388  info->can_edit_desc = !g_strcmp0 (desc, xaccTransGetDescription (trans));
1389  if (info->can_edit_notes)
1390  info->can_edit_notes = !g_strcmp0 (notes, xaccTransGetNotes (trans));
1391  if (info->can_edit_memo)
1392  info->can_edit_memo = !g_strcmp0 (memo, xaccSplitGetMemo (split));
1393 
1394  /* Only allow undoing desc/notes/memo edits if all selected transactions
1395  * have been edited */
1396  if (!can_undo_edits)
1397  can_undo_edits = (g_strcmp0 (xaccSplitGetMemo (split), rowinfo.get_orig_memo()) ||
1398  g_strcmp0 (xaccTransGetNotes (trans), rowinfo.get_orig_notes()) ||
1399  g_strcmp0 (xaccTransGetDescription (trans), rowinfo.get_orig_desc()));
1400 
1401  /* all flags were switched. no need to scan remaining rows. */
1402  if (!can_assign_acct && !can_update_prices &&
1403  !info->can_edit_desc && !info->can_edit_notes && !info->can_edit_memo &&
1404  can_undo_edits)
1405  break;
1406  }
1407 
1408  GtkWidget *menu = gtk_menu_new();
1409 
1410  auto add_menu_item = [&menu, &info](const char* name, bool sensitive, GCallback callback)
1411  {
1412  auto menuitem = gtk_menu_item_new_with_mnemonic (_(name));
1413  gtk_widget_set_sensitive (menuitem, sensitive);
1414  g_signal_connect (menuitem, "activate", callback, info);
1415  gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem);
1416  };
1417 
1418  /* Translators: Menu entry, no full stop */
1419  add_menu_item (N_("_Assign transfer account"),
1420  can_assign_acct,
1421  G_CALLBACK(gnc_gen_trans_assign_transfer_account_to_selection_cb));
1422 
1423  /* Translators: Menu entry, no full stop */
1424  add_menu_item (N_("Assign e_xchange rate"),
1425  can_update_prices,
1426  G_CALLBACK (gnc_gen_trans_set_price_to_selection_cb));
1427 
1428  /* Translators: Menu entry, no full stop */
1429  add_menu_item (N_("_Edit description, notes, or memo"),
1430  info->can_edit_desc || info->can_edit_notes || info->can_edit_memo,
1431  G_CALLBACK (gnc_gen_trans_edit_fields));
1432 
1433  /* Translators: Menu entry, no full stop */
1434  add_menu_item (N_("_Reset all edits"),
1435  can_undo_edits,
1436  G_CALLBACK (gnc_gen_trans_reset_edits_cb));
1437 
1438  gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (treeview), NULL);
1439 
1440  gtk_widget_show_all (menu);
1441  /* Note: event can be NULL here when called from view_onPopupMenu; */
1442  gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent*)event);
1443 
1444  g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free);
1445  LEAVE ("");
1446 }
1447 
1448 static bool
1449 gnc_gen_trans_onButtonPressed_cb (GtkTreeView *treeview,
1450  GdkEvent *event,
1451  GNCImportMainMatcher *info)
1452 {
1453  ENTER("");
1454  g_return_val_if_fail (treeview != NULL, false);
1455  g_return_val_if_fail (event != NULL, false);
1456  /* handle single click with the right mouse button? */
1457  if (event->type == GDK_BUTTON_PRESS)
1458  {
1459  GdkEventButton *event_button = (GdkEventButton *) event;
1460  if (event_button->button == GDK_BUTTON_SECONDARY)
1461  {
1462  DEBUG("Right mouseClick detected - popup the menu.");
1463 
1464  auto selection = gtk_tree_view_get_selection (treeview);
1465  GtkTreePath* path = nullptr;
1466 
1467  /* Get tree path for row that was clicked */
1468  if (gtk_tree_view_get_path_at_pos (treeview, event_button->x,
1469  event_button->y, &path,
1470  nullptr, nullptr, nullptr))
1471  {
1472  if (!gtk_tree_selection_path_is_selected (selection, path))
1473  {
1474  gtk_tree_selection_unselect_all (selection);
1475  gtk_tree_selection_select_path (selection, path);
1476  }
1477  gtk_tree_path_free (path);
1478  }
1479 
1480  if (gtk_tree_selection_count_selected_rows (selection) > 0)
1481  {
1482  GtkTreeModel *model;
1483  auto selected = gtk_tree_selection_get_selected_rows (selection, &model);
1484  if (get_action_for_path (static_cast<GtkTreePath*>(selected->data), model) == GNCImport_ADD)
1485  gnc_gen_trans_view_popup_menu (treeview, event, info);
1486  g_list_free_full (selected, (GDestroyNotify)gtk_tree_path_free);
1487  }
1488  LEAVE("return true");
1489  return true;
1490  }
1491  }
1492  LEAVE("return false");
1493  return false;
1494 }
1495 
1496 static bool
1497 gnc_gen_trans_onPopupMenu_cb (GtkTreeView *treeview,
1498  GNCImportMainMatcher *info)
1499 {
1500  ENTER("onPopupMenu_cb");
1501  /* respond to Shift-F10 popup menu hotkey */
1502  GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
1503  if (gtk_tree_selection_count_selected_rows (selection) > 0)
1504  {
1505  gnc_gen_trans_view_popup_menu (treeview, NULL, info);
1506  LEAVE ("true");
1507  return true;
1508  }
1509  LEAVE ("false");
1510  return true;
1511 }
1512 
1513 static GtkTreeViewColumn *
1514 add_text_column (GtkTreeView *view, const gchar *title, int col_num, bool ellipsize)
1515 {
1516  GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
1517  GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (title,
1518  renderer,
1519  "text", col_num,
1520  "background", DOWNLOADED_COL_COLOR,
1521  NULL);
1522 
1523  if (ellipsize)
1524  g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1525 
1526  // If date column, use the time64 value for the sorting.
1527  if (col_num == DOWNLOADED_COL_DATE_TXT)
1528  gtk_tree_view_column_set_sort_column_id(column, DOWNLOADED_COL_DATE_INT64);
1529  else if (col_num == DOWNLOADED_COL_AMOUNT) // If amount column, use double value
1530  {
1531  gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5); // right align amount column
1532  gtk_cell_renderer_set_padding (renderer, 5, 0); // add padding so its not close to description
1533  gtk_tree_view_column_set_sort_column_id (column, DOWNLOADED_COL_AMOUNT_DOUBLE);
1534  }
1535  else
1536  gtk_tree_view_column_set_sort_column_id (column, col_num);
1537 
1538  if (col_num == DOWNLOADED_COL_DESCRIPTION)
1539  gtk_tree_view_column_add_attribute (column, renderer, "style", DOWNLOADED_COL_DESCRIPTION_STYLE);
1540 
1541  if (col_num == DOWNLOADED_COL_MEMO)
1542  gtk_tree_view_column_add_attribute (column, renderer, "style", DOWNLOADED_COL_MEMO_STYLE);
1543 
1544  g_object_set (G_OBJECT(column),
1545  "reorderable", true,
1546  "resizable", true,
1547  NULL);
1548  gtk_tree_view_append_column (view, column);
1549  return column;
1550 }
1551 
1552 static GtkTreeViewColumn *
1553 add_toggle_column (GtkTreeView *view, const gchar *title, int col_num,
1554  GCallback cb_fn, gpointer cb_arg)
1555 {
1556  GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new ();
1557  GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (title, renderer,
1558  "active", col_num,
1559  "cell-background", DOWNLOADED_COL_COLOR,
1560  "activatable", DOWNLOADED_COL_ENABLE,
1561  "visible", DOWNLOADED_COL_ENABLE,
1562  NULL);
1563  gtk_tree_view_column_set_sort_column_id (column, col_num);
1564  g_object_set (G_OBJECT(column), "reorderable", true, NULL);
1565  g_signal_connect (renderer, "toggled", cb_fn, cb_arg);
1566  gtk_tree_view_append_column (view, column);
1567  return column;
1568 }
1569 
1570 static void
1571 gnc_gen_trans_init_view (GNCImportMainMatcher *info,
1572  bool show_account,
1573  bool show_update)
1574 {
1575  GtkTreeView *view = info->view;
1576  GtkTreeStore *store = gtk_tree_store_new (NUM_DOWNLOADED_COLS, G_TYPE_STRING, G_TYPE_INT64,
1577  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE,
1578  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, //description stuff
1579  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, //memo stuff
1580  G_TYPE_STRING, G_TYPE_BOOLEAN,
1581  G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING,
1582  GDK_TYPE_PIXBUF, G_TYPE_POINTER, G_TYPE_STRING,
1583  G_TYPE_BOOLEAN);
1584  gtk_tree_view_set_model (view, GTK_TREE_MODEL(store));
1585  g_object_unref (store);
1586 
1587  /* prevent the rows being dragged to a different order */
1588  gtk_tree_view_set_reorderable (view, false);
1589 
1590  /* Add the columns */
1591  add_text_column (view, _("Date"), DOWNLOADED_COL_DATE_TXT, false);
1592  info->account_column = add_text_column (view, _("Account"), DOWNLOADED_COL_ACCOUNT, false);
1593  gtk_tree_view_column_set_visible (info->account_column, show_account);
1594  add_text_column (view, _("Amount"), DOWNLOADED_COL_AMOUNT, false);
1595  add_text_column (view, _("Description"), DOWNLOADED_COL_DESCRIPTION, false);
1596  info->memo_column = add_text_column (view, _("Memo"), DOWNLOADED_COL_MEMO, true);
1597  add_toggle_column (view, C_("Column header for 'Adding transaction'", "A"),
1598  DOWNLOADED_COL_ACTION_ADD,
1599  G_CALLBACK(gnc_gen_trans_add_toggled_cb), info);
1600  GtkTreeViewColumn *column = add_toggle_column (view,
1601  C_("Column header for 'Updating plus Clearing transaction'", "U+C"),
1602  DOWNLOADED_COL_ACTION_UPDATE,
1603  G_CALLBACK(gnc_gen_trans_update_toggled_cb), info);
1604  gtk_tree_view_column_set_visible (column, show_update);
1605  add_toggle_column (view, C_("Column header for 'Clearing transaction'", "C"),
1606  DOWNLOADED_COL_ACTION_CLEAR,
1607  G_CALLBACK(gnc_gen_trans_clear_toggled_cb), info);
1608 
1609  /* The last column has multiple renderers */
1610  GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new ();
1611  g_object_set (renderer, "xalign", 0.0, NULL);
1612  column = gtk_tree_view_column_new_with_attributes (_("Info"), renderer,
1613  "pixbuf", DOWNLOADED_COL_ACTION_PIXBUF,
1614  "cell-background", DOWNLOADED_COL_COLOR,
1615  NULL);
1616 
1617  gtk_tree_view_append_column (info->view, column);
1618 
1619  column = add_text_column (view, _("Additional Comments"), DOWNLOADED_COL_ACTION_INFO, false);
1620  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1621 
1622  /* default sort order */
1623  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
1624  DOWNLOADED_COL_DATE_INT64,
1625  GTK_SORT_ASCENDING);
1626  GtkTreeSelection *selection = gtk_tree_view_get_selection (info->view);
1627 
1628  g_object_set (info->view, "has-tooltip", true, NULL);
1629 
1630  g_signal_connect (G_OBJECT(info->view), "query-tooltip",
1631  G_CALLBACK(query_tooltip_tree_view_cb), info);
1632  g_signal_connect (info->view, "row-activated",
1633  G_CALLBACK(gnc_gen_trans_row_activated_cb), info);
1634  g_signal_connect (selection, "changed",
1635  G_CALLBACK(gnc_gen_trans_row_changed_cb), info);
1636  g_signal_connect (view, "button-press-event",
1637  G_CALLBACK(gnc_gen_trans_onButtonPressed_cb), info);
1638  g_signal_connect (view, "popup-menu",
1639  G_CALLBACK(gnc_gen_trans_onPopupMenu_cb), info);
1640 }
1641 
1642 static void
1643 show_account_column_toggled_cb (GtkToggleButton *togglebutton,
1644  GNCImportMainMatcher *info)
1645 {
1646  gtk_tree_view_column_set_visible (info->account_column,
1647  gtk_toggle_button_get_active (togglebutton));
1648 }
1649 
1650 static void
1651 show_memo_column_toggled_cb (GtkToggleButton *togglebutton,
1652  GNCImportMainMatcher *info)
1653 {
1654  gtk_tree_view_column_set_visible (info->memo_column,
1655  gtk_toggle_button_get_active (togglebutton));
1656 }
1657 
1658 static void
1659 show_matched_info_toggled_cb (GtkToggleButton *togglebutton,
1660  GNCImportMainMatcher *info)
1661 {
1662  if (gtk_toggle_button_get_active (togglebutton))
1663  {
1664  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), true);
1665  gtk_tree_view_expand_all (info->view);
1666  }
1667  else
1668  {
1669  gtk_tree_view_column_set_visible (info->account_column,
1670  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(info->show_account_column)));
1671  gtk_tree_view_collapse_all (info->view);
1672  }
1673 }
1674 
1675 static void
1676 gnc_gen_trans_common_setup (GNCImportMainMatcher *info,
1677  GtkWidget *parent,
1678  GtkBuilder *builder,
1679  const gchar* heading,
1680  bool all_from_same_account,
1681  gint match_date_hardlimit)
1682 {
1683  info->pending_matches = gnc_import_PendingMatches_new ();
1684 
1685  /* Initialize user Settings. */
1686  info->user_settings = gnc_import_Settings_new ();
1687  gnc_import_Settings_set_match_date_hardlimit (info->user_settings, match_date_hardlimit);
1688 
1689  GtkStyleContext *stylectxt = gtk_widget_get_style_context (GTK_WIDGET(parent));
1690  GdkRGBA color;
1691  gtk_style_context_get_color (stylectxt, GTK_STATE_FLAG_NORMAL, &color);
1692  info->dark_theme = gnc_is_dark_theme (&color);
1693 
1694  /* Get the view */
1695  info->view = GTK_TREE_VIEW(gtk_builder_get_object (builder, "downloaded_view"));
1696  g_assert (info->view != NULL);
1697 
1698  info->show_account_column = GTK_WIDGET(gtk_builder_get_object (builder, "show_source_account_button"));
1699  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), all_from_same_account);
1700  g_signal_connect (G_OBJECT(info->show_account_column), "toggled",
1701  G_CALLBACK(show_account_column_toggled_cb), info);
1702 
1703  GtkWidget *button = GTK_WIDGET(gtk_builder_get_object (builder, "show_memo_column_button"));
1704  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), true);
1705  g_signal_connect (G_OBJECT(button), "toggled",
1706  G_CALLBACK(show_memo_column_toggled_cb), info);
1707 
1708  info->show_matched_info = GTK_WIDGET(gtk_builder_get_object (builder, "show_matched_info_button"));
1709  g_signal_connect (G_OBJECT(info->show_matched_info), "toggled",
1710  G_CALLBACK(show_matched_info_toggled_cb), info);
1711 
1712  info->append_text = GTK_WIDGET(gtk_builder_get_object (builder, "append_desc_notes_button"));
1713 
1714  // Create the checkbox, but do not show it unless there are transactions
1715  info->reconcile_after_close = GTK_WIDGET(gtk_builder_get_object (builder, "reconcile_after_close_button"));
1716 
1717 
1718  GtkWidget *heading_label = GTK_WIDGET(gtk_builder_get_object (builder, "heading_label"));
1719  if (heading)
1720  gtk_label_set_text (GTK_LABEL(heading_label), heading);
1721 
1722  bool show_update = gnc_import_Settings_get_action_update_enabled (info->user_settings);
1723  gnc_gen_trans_init_view (info, all_from_same_account, show_update);
1724 
1725  info->acct_id_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
1726  (GDestroyNotify)g_hash_table_destroy);
1727  info->desc_hash = g_hash_table_new (g_str_hash, g_str_equal);
1728  info->notes_hash = g_hash_table_new (g_str_hash, g_str_equal);
1729  info->memo_hash = g_hash_table_new (g_str_hash, g_str_equal);
1730  info->new_strings = NULL;
1731  info->transaction_processed_cb = NULL;
1732 
1733  /* Connect the signals */
1734  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, info);
1735 
1736  g_object_unref (G_OBJECT(builder));
1737 }
1738 
1739 
1740 GNCImportMainMatcher *
1741 gnc_gen_trans_list_new (GtkWidget *parent,
1742  const gchar* heading,
1743  bool all_from_same_account,
1744  gint match_date_hardlimit,
1745  bool show_all)
1746 {
1747  GNCImportMainMatcher *info = g_new0 (GNCImportMainMatcher, 1);
1748 
1749  /* Initialize the GtkDialog. */
1750  GtkBuilder *builder = gtk_builder_new ();
1751  gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_matcher_dialog");
1752  gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_matcher_content");
1753 
1754  info->main_widget = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_dialog"));
1755  g_assert (info->main_widget != NULL);
1756 
1757  /* Pack the content into the dialog vbox */
1758  GtkWidget *pbox = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_vbox"));
1759  GtkWidget *box = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_content"));
1760  gtk_box_pack_start (GTK_BOX(pbox), box, true, true, 0);
1761 
1762  // Set the name for this dialog so it can be easily manipulated with css
1763  gtk_widget_set_name (GTK_WIDGET(info->main_widget), "gnc-id-import-matcher-transactions");
1764  gtk_widget_set_name (GTK_WIDGET(box), "gnc-id-import-transaction-content");
1765  gnc_widget_style_context_add_class (GTK_WIDGET(info->main_widget), "gnc-class-imports");
1766 
1767  /* setup the common parts */
1768  gnc_gen_trans_common_setup (info, parent, builder, heading,
1769  all_from_same_account, match_date_hardlimit);
1770 
1771  if (parent)
1772  gtk_window_set_transient_for (GTK_WINDOW(info->main_widget), GTK_WINDOW(parent));
1773 
1774  gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(info->main_widget), GTK_WINDOW(parent));
1775 
1776  if (show_all)
1777  gtk_widget_show_all (GTK_WIDGET(info->main_widget));
1778 
1779  // Register this UI, it needs to be closed when the session is closed.
1780  info->id = gnc_register_gui_component (IMPORT_MAIN_MATCHER_CM_CLASS,
1781  NULL, /* no refresh handler */
1782  (GNCComponentCloseHandler)gnc_gen_trans_list_delete,
1783  info);
1784  // This ensure this dialog is closed when the session is closed.
1785  gnc_gui_component_set_session (info->id, gnc_get_current_session());
1786 
1787  return info;
1788 }
1789 
1790 /*****************************************************************
1791  * Assistant routines Start *
1792  *****************************************************************/
1793 
1794 GNCImportMainMatcher *
1795 gnc_gen_trans_assist_new (GtkWidget *parent,
1796  GtkWidget *assistant_page,
1797  const gchar* heading,
1798  bool all_from_same_account,
1799  gint match_date_hardlimit)
1800 {
1801  GNCImportMainMatcher *info = g_new0 (GNCImportMainMatcher, 1);
1802  info->main_widget = GTK_WIDGET(parent);
1803 
1804  /* load the interface */
1805  GtkBuilder *builder = gtk_builder_new ();
1806  gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_matcher_content");
1807 
1808  /* Pack content into Assistant page widget */
1809  GtkWidget *box = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_content"));
1810  g_assert (box != NULL);
1811  gtk_box_pack_start (GTK_BOX(assistant_page), box, true, true, 6);
1812 
1813  // Set the name for this dialog so it can be easily manipulated with css
1814  gtk_widget_set_name (GTK_WIDGET(box), "gnc-id-import-transaction-content");
1815 
1816  /* setup the common parts */
1817  gnc_gen_trans_common_setup (info, parent, builder, heading,
1818  all_from_same_account, match_date_hardlimit);
1819 
1820  return info;
1821 }
1822 
1823 void
1824 gnc_gen_trans_assist_start (GNCImportMainMatcher *info)
1825 {
1826  on_matcher_ok_clicked (NULL, info);
1827 }
1828 
1829 /*****************************************************************
1830  * Assistant routines End *
1831  *****************************************************************/
1832 
1833 void
1834 gnc_gen_trans_list_add_tp_cb (GNCImportMainMatcher *info,
1835  GNCTransactionProcessedCB trans_processed_cb,
1836  gpointer user_data)
1837 {
1838  info->user_data = user_data;
1839  info->transaction_processed_cb = trans_processed_cb;
1840 }
1841 
1842 bool
1843 gnc_gen_trans_list_run (GNCImportMainMatcher *info)
1844 {
1845  /* DEBUG("Begin"); */
1846  bool result = gtk_dialog_run (GTK_DIALOG (info->main_widget));
1847  /* DEBUG("Result was %d", result); */
1848 
1849  /* No destroying here since the dialog was already destroyed through
1850  the ok_clicked handlers. */
1851 
1852  return result;
1853 }
1854 
1855 static const gchar*
1856 get_required_color (const gchar *class_name)
1857 {
1858  GdkRGBA color;
1859  GtkWidget *label = gtk_label_new ("Color");
1860  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(label));
1861  gtk_style_context_add_class (context, class_name);
1862  gnc_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &color);
1863  static gchar *strbuf = NULL;
1864  if (strbuf)
1865  g_free (strbuf);
1866  strbuf = gdk_rgba_to_string (&color);
1867  return strbuf;
1868 }
1869 
1870 static void
1871 remove_child_row (GtkTreeModel *model, GtkTreeIter *iter)
1872 {
1873  if (gtk_tree_model_iter_has_child (model, iter))
1874  {
1875  GtkTreeIter child;
1876  gtk_tree_model_iter_nth_child (model, &child, iter, 0);
1877  gtk_tree_store_remove (GTK_TREE_STORE(model), &child);
1878  }
1879 }
1880 
1881 static void
1882 update_child_row (GNCImportMatchInfo *sel_match, GtkTreeModel *model, GtkTreeIter *iter)
1883 {
1884  GtkTreeStore *store = GTK_TREE_STORE(model);
1885  GtkTreeIter child;
1886  if (!gtk_tree_model_iter_has_child (model, iter))
1887  gtk_tree_store_append (GTK_TREE_STORE(model), &child, iter);
1888  else
1889  gtk_tree_model_iter_nth_child (model, &child, iter, 0);
1890 
1891  auto account_str = (xaccTransCountSplits (sel_match->trans) == 2)
1893  : _("-- Split Transaction --");
1894  auto amount_str = xaccPrintAmount (xaccSplitGetAmount (sel_match->split), gnc_split_amount_print_info (sel_match->split, true));
1895  auto date = qof_print_date (xaccTransGetDate (sel_match->trans));
1896 
1897  gtk_tree_store_set (store, &child,
1898  DOWNLOADED_COL_ACCOUNT, account_str,
1899  DOWNLOADED_COL_DATE_TXT, date,
1900  DOWNLOADED_COL_AMOUNT, amount_str,
1901  DOWNLOADED_COL_MEMO, xaccSplitGetMemo (sel_match->split),
1902  DOWNLOADED_COL_MEMO_STYLE, PANGO_STYLE_NORMAL,
1903  DOWNLOADED_COL_DESCRIPTION, xaccTransGetDescription (sel_match->trans),
1904  DOWNLOADED_COL_DESCRIPTION_STYLE, PANGO_STYLE_NORMAL,
1905  DOWNLOADED_COL_ENABLE, false,
1906  -1);
1907  g_free (date);
1908 }
1909 
1910 static gchar *
1911 get_peer_acct_names (Split *split)
1912 {
1913  GList *names = NULL, *accounts_seen = NULL;
1914  for (GList *n = xaccTransGetSplitList (xaccSplitGetParent (split)); n; n = n->next)
1915  {
1916  Account *account = xaccSplitGetAccount (static_cast<Split*>(n->data));
1917  if ((n->data == split) ||
1918  (xaccAccountGetType (account) == ACCT_TYPE_TRADING) ||
1919  (g_list_find (accounts_seen, account)))
1920  continue;
1921  gchar *name = gnc_account_get_full_name (account);
1922  names = g_list_prepend (names, name);
1923  accounts_seen = g_list_prepend (accounts_seen, account);
1924  }
1925  names = g_list_sort (names, (GCompareFunc)g_utf8_collate);
1926  auto retval = gnc_list_formatter (names);
1927  g_list_free_full (names, g_free);
1928  g_list_free (accounts_seen);
1929  return retval;
1930 }
1931 
1932 static void
1933 refresh_model_row (GNCImportMainMatcher *gui,
1934  GtkTreeModel *model,
1935  GtkTreeIter *iter,
1936  GNCImportTransInfo *info)
1937 {
1938  g_assert (gui);
1939  g_assert (model);
1940  g_assert (info);
1941  /*DEBUG("Begin");*/
1942 
1943  GtkTreeStore *store = GTK_TREE_STORE(model);
1944  gtk_tree_store_set (store, iter, DOWNLOADED_COL_DATA, info, -1);
1945 
1946  const gchar *class_extension = NULL;
1947  if (gui->dark_theme)
1948  class_extension = "-dark";
1949 
1950  gchar *int_required_class = g_strconcat (CSS_INT_REQUIRED_CLASS, class_extension, NULL);
1951  gchar *int_prob_required_class = g_strconcat (CSS_INT_PROB_REQUIRED_CLASS, class_extension, NULL);
1952  gchar *int_not_required_class = g_strconcat (CSS_INT_NOT_REQUIRED_CLASS, class_extension, NULL);
1953 
1954  /* This controls the visibility of the toggle cells */
1955  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ENABLE, true, -1);
1956 
1957  /*Account:*/
1958  Split *split = gnc_import_TransInfo_get_fsplit (info);
1959  g_assert (split); // Must not be NULL
1960  const gchar *ro_text = xaccAccountGetName (xaccSplitGetAccount (split));
1961  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACCOUNT, ro_text, -1);
1962 
1963  /*Date*/
1965  gchar *text = qof_print_date (date);
1966  gtk_tree_store_set (store, iter, DOWNLOADED_COL_DATE_TXT, text, -1);
1967  gtk_tree_store_set (store, iter, DOWNLOADED_COL_DATE_INT64, date, -1);
1968  g_free(text);
1969 
1970  /*Amount*/
1971  gnc_numeric amount = xaccSplitGetAmount (split);
1972  ro_text = xaccPrintAmount (amount, gnc_split_amount_print_info (split, true));
1973  gtk_tree_store_set (store, iter, DOWNLOADED_COL_AMOUNT, ro_text, -1);
1974  gtk_tree_store_set (store, iter, DOWNLOADED_COL_AMOUNT_DOUBLE, gnc_numeric_to_double (amount), -1);
1975 
1976  /* Notes */
1978  gtk_tree_store_set (store, iter, DOWNLOADED_COL_NOTES_ORIGINAL, ro_text, -1);
1979 
1980  /*Description*/
1982  gtk_tree_store_set (store, iter,
1983  DOWNLOADED_COL_DESCRIPTION, ro_text,
1984  DOWNLOADED_COL_DESCRIPTION_ORIGINAL, ro_text,
1985  -1);
1986  /*Memo*/
1987  ro_text = xaccSplitGetMemo (split);
1988  gtk_tree_store_set (store, iter,
1989  DOWNLOADED_COL_MEMO, ro_text,
1990  DOWNLOADED_COL_MEMO_ORIGINAL, ro_text,
1991  -1);
1992 
1993  /*Actions*/
1994 
1995  /* Action information */
1996  ro_text = text = NULL;
1997  const gchar *color = NULL;
1998  bool show_pixbuf = true;
1999  switch (gnc_import_TransInfo_get_action (info))
2000  {
2001  case GNCImport_ADD:
2003  {
2004  ro_text = _("New, already balanced");
2005  color = get_required_color (int_not_required_class);
2006  }
2007  else
2008  {
2009  Account *dest_acc = gnc_import_TransInfo_get_destacc (info);
2010  char *imbalance = NULL;
2011  if (dest_acc)
2012  {
2013  char *acct_full_name = gnc_account_get_full_name (dest_acc);
2014  gnc_numeric bal_amt = gnc_import_TransInfo_get_dest_amount (info);
2015  if (!gnc_numeric_zero_p (bal_amt))
2016  {
2017  GNCPrintAmountInfo pinfo = gnc_commodity_print_info (
2018  xaccAccountGetCommodity (dest_acc), true);
2019  imbalance = g_strdup (xaccPrintAmount (bal_amt, pinfo));
2020  color = get_required_color (int_not_required_class);
2022  {
2023  text =
2024  /* Translators: %1$s is the amount to be transferred,
2025  %2$s the destination account. */
2026  g_strdup_printf (_("New, transfer %s to (manual) \"%s\""),
2027  imbalance, acct_full_name);
2028  }
2029  else
2030  {
2031  text =
2032  /* Translators: %1$s is the amount to be transferred,
2033  %2$s the destination account. */
2034  g_strdup_printf (_("New, transfer %s to (auto) \"%s\""),
2035  imbalance, acct_full_name);
2036  }
2037  }
2038  else
2039  {
2040  GNCPrintAmountInfo pinfo = gnc_commodity_print_info (
2042  gnc_numeric bal_val = gnc_import_TransInfo_get_dest_value (info);
2043  imbalance = g_strdup (xaccPrintAmount (bal_val, pinfo));
2044  color = get_required_color (int_required_class);
2045  text =
2046  /* Translators: %s is the amount to be transferred. */
2047  g_strdup_printf (_("New, UNBALANCED (need price to transfer %s to acct %s)!"),
2048  imbalance, acct_full_name);
2049 
2050  }
2051 
2052  g_free (acct_full_name);
2053  }
2054  else
2055  {
2056  GNCPrintAmountInfo pinfo = gnc_commodity_print_info (
2058  gnc_numeric bal_val = gnc_import_TransInfo_get_dest_value (info);
2059  imbalance = g_strdup (xaccPrintAmount (bal_val, pinfo));
2060  color = get_required_color (int_prob_required_class);
2061  text =
2062  /* Translators: %s is the amount to be transferred. */
2063  g_strdup_printf (_("New, UNBALANCED (need acct to transfer %s)!"),
2064  imbalance);
2065  }
2066  remove_child_row (model, iter);
2067 
2068  g_free (imbalance);
2069  }
2070  break;
2071  case GNCImport_CLEAR:
2072  {
2074 
2075  if (sel_match)
2076  {
2077  gchar *full_names = get_peer_acct_names (sel_match->split);
2078  color = get_required_color (int_not_required_class);
2080  {
2081  text = g_strdup_printf (_("Reconcile (manual) match to %s"),
2082  full_names);
2083  }
2084  else
2085  {
2086  text = g_strdup_printf (_("Reconcile (auto) match to %s"),
2087  full_names);
2088  }
2089  g_free (full_names);
2090  update_child_row (sel_match, model, iter);
2091  }
2092  else
2093  {
2094  color = get_required_color (int_required_class);
2095  ro_text = _("Match missing!");
2096  show_pixbuf = false;
2097  remove_child_row (model, iter);
2098  }
2099  }
2100  break;
2101  case GNCImport_UPDATE:
2102  {
2104  if (sel_match)
2105  {
2106  gchar *full_names = get_peer_acct_names (sel_match->split);
2107  color = get_required_color (int_not_required_class);
2109  {
2110  text = g_strdup_printf (_("Update and reconcile (manual) match to %s"),
2111  full_names);
2112  }
2113  else
2114  {
2115  text = g_strdup_printf (_("Update and reconcile (auto) match to %s"),
2116  full_names);
2117  }
2118  g_free (full_names);
2119  update_child_row (sel_match, model, iter);
2120  }
2121  else
2122  {
2123  color = get_required_color (int_required_class);
2124  ro_text = _("Match missing!");
2125  show_pixbuf = false;
2126  remove_child_row (model, iter);
2127  }
2128  }
2129  break;
2130  case GNCImport_SKIP:
2131  color = get_required_color (int_required_class);
2132  ro_text = _("Do not import (no action selected)");
2133  show_pixbuf = false;
2134  remove_child_row (model, iter);
2135  break;
2136  default:
2137  color = "white";
2138  ro_text = "WRITEME, this is an unknown action";
2139  show_pixbuf = false;
2140  break;
2141  }
2142 
2143  gtk_tree_store_set (store, iter,
2144  DOWNLOADED_COL_COLOR, color,
2145  DOWNLOADED_COL_ACTION_INFO, ro_text ? ro_text : text,
2146  -1);
2147  if (text)
2148  g_free (text);
2149 
2150  g_free (int_required_class);
2151  g_free (int_prob_required_class);
2152  g_free (int_not_required_class);
2153 
2154  /* Set the pixmaps */
2155  gtk_tree_store_set (store, iter,
2156  DOWNLOADED_COL_ACTION_ADD,
2157  gnc_import_TransInfo_get_action (info) == GNCImport_ADD,
2158  -1);
2159  if (gnc_import_TransInfo_get_action (info) == GNCImport_SKIP)
2160  {
2161  /*If skipping the row, there is no best match's confidence pixmap*/
2162  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACTION_PIXBUF, NULL, -1);
2163  }
2164 
2165  gtk_tree_store_set (store, iter,
2166  DOWNLOADED_COL_ACTION_CLEAR,
2167  gnc_import_TransInfo_get_action (info) == GNCImport_CLEAR,
2168  -1);
2169  if (gnc_import_TransInfo_get_action (info) == GNCImport_CLEAR)
2170  {
2171  /*Show the best match's confidence pixmap in the info column*/
2172  if (show_pixbuf)
2173  gtk_tree_store_set (store, iter,
2174  DOWNLOADED_COL_ACTION_PIXBUF,
2177  gui->user_settings,
2178  GTK_WIDGET(gui->view)),
2179  -1);
2180  else
2181  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACTION_PIXBUF, NULL, -1);
2182  }
2183 
2184  gtk_tree_store_set (store, iter,
2185  DOWNLOADED_COL_ACTION_UPDATE,
2186  gnc_import_TransInfo_get_action (info) == GNCImport_UPDATE,
2187  -1);
2188  if (gnc_import_TransInfo_get_action (info) == GNCImport_UPDATE)
2189  {
2190  /*Show the best match's confidence pixmap in the info column*/
2191  if (show_pixbuf)
2192  gtk_tree_store_set (store, iter,
2193  DOWNLOADED_COL_ACTION_PIXBUF,
2196  gui->user_settings,
2197  GTK_WIDGET(gui->view)),
2198  -1);
2199  else
2200  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACTION_PIXBUF, NULL, -1);
2201  }
2202 
2203  // show child row if 'show matched info' is toggled
2204  if (gtk_tree_model_iter_has_child (model, iter))
2205  {
2206  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui->show_matched_info)))
2207  {
2208  GtkTreePath *path = gtk_tree_model_get_path (model, iter);
2209 
2210  gtk_tree_view_column_set_visible (gui->account_column, true);
2211  gtk_tree_view_column_set_visible (gui->memo_column, true);
2212 
2213  gtk_tree_view_expand_row (GTK_TREE_VIEW(gui->view), path, true);
2214  gtk_tree_path_free (path);
2215  }
2216  }
2217  GtkTreeSelection *selection = gtk_tree_view_get_selection (gui->view);
2218  gtk_tree_selection_unselect_all (selection);
2219 }
2220 
2221 void
2223  bool reconcile_after_close,
2224  bool active)
2225 {
2226  gtk_widget_set_visible (info->reconcile_after_close, reconcile_after_close);
2227  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (info->reconcile_after_close), active);
2228 }
2229 
2230 GtkWidget*
2232 {
2233  return info->reconcile_after_close;
2234 }
2235 
2236 
2237 static void
2238 gnc_gen_trans_list_add_trans_internal (GNCImportMainMatcher *gui, Transaction *trans,
2239  guint32 ref_id, GNCImportLastSplitInfo* lsplit)
2240 {
2241  g_assert (gui);
2242  g_assert (trans);
2243 
2244  Split *split = xaccTransGetSplit (trans, 0);
2245  Account *acc = xaccSplitGetAccount (split);
2246  defer_bal_computation (gui, acc);
2247 
2248  if (gnc_import_exists_online_id (trans, gui->acct_id_hash))
2249  {
2250  /* If it does, abort the process for this transaction, since
2251  it is already in the system. */
2252  DEBUG("%s", "Transaction with same online ID exists, destroying current transaction");
2253  xaccTransDestroy(trans);
2254  xaccTransCommitEdit(trans);
2255  return;
2256  }
2257 
2258  GNCImportTransInfo *transaction_info = gnc_import_TransInfo_new (trans, NULL);
2259  gnc_import_TransInfo_set_ref_id (transaction_info, ref_id);
2260  gnc_import_TransInfo_set_last_split_info (transaction_info, lsplit);
2261  // It's much faster to gather the imported transactions into a GSList than
2262  // directly into the treeview.
2263  gui->temp_trans_list = g_slist_prepend (gui->temp_trans_list, transaction_info);
2264 }
2265 
2266 void
2267 gnc_gen_trans_list_add_trans (GNCImportMainMatcher *gui, Transaction *trans)
2268 {
2269  gnc_gen_trans_list_add_trans_internal (gui, trans, 0, NULL);
2270 }
2271 
2272 void
2273 gnc_gen_trans_list_add_trans_with_ref_id (GNCImportMainMatcher *gui, Transaction *trans, guint32 ref_id)
2274 {
2275  gnc_gen_trans_list_add_trans_internal (gui, trans, ref_id, NULL);
2276 }
2277 
2278 void gnc_gen_trans_list_add_trans_with_split_data (GNCImportMainMatcher *gui,
2279  Transaction *trans,
2280  GNCImportLastSplitInfo *lsplit)
2281 {
2282  gnc_gen_trans_list_add_trans_internal (gui, trans, 0, lsplit);
2283 }
2284 
2285 /* Return a list of splits from already existing transactions for
2286  * which the account matches an account used by the transactions to
2287  * import. The matching range is also date-limited (configurable
2288  * via preferences) to not go too far in the past or future.
2289  */
2290 static GList*
2291 filter_existing_splits_on_account_and_date (GNCImportMainMatcher *gui)
2292 {
2293  static const int secs_per_day = 86400;
2294  gint match_date_limit =
2295  gnc_import_Settings_get_match_date_hardlimit (gui->user_settings);
2296  time64 min_time=G_MAXINT64, max_time=0;
2297  time64 match_timelimit = match_date_limit * secs_per_day;
2298  GList *all_accounts = NULL;
2299 
2300  /* Go through all imported transactions, gather the list of accounts, and
2301  * min/max date range.
2302  */
2303  for (GSList* txn = gui->temp_trans_list; txn != NULL;
2304  txn = g_slist_next (txn))
2305  {
2306  auto txn_info = static_cast<GNCImportTransInfo*>(txn->data);
2307  Account *txn_account =
2309  time64 txn_time =
2311  all_accounts = g_list_prepend (all_accounts, txn_account);
2312  min_time = MIN(min_time, txn_time);
2313  max_time = MAX(max_time, txn_time);
2314  }
2315 
2316  // Make a query to find splits with the right accounts and dates.
2317  Query *query = qof_query_create_for (GNC_ID_SPLIT);
2318  qof_query_set_book (query, gnc_get_current_book ());
2319  xaccQueryAddAccountMatch (query, all_accounts,
2320  QOF_GUID_MATCH_ANY, QOF_QUERY_AND);
2321  xaccQueryAddDateMatchTT (query,
2322  true, min_time - match_timelimit,
2323  true, max_time + match_timelimit,
2324  QOF_QUERY_AND);
2325  GList *query_results = qof_query_run (query);
2326  g_list_free (all_accounts);
2327  GList *retval = g_list_copy (query_results);
2328  qof_query_destroy (query);
2329 
2330  return retval;
2331 }
2332 
2333 /* Create a hash by account of all splits that could match one of the imported
2334  * transactions based on their account and date and organized per account.
2335  */
2336 static GHashTable*
2337 create_hash_of_potential_matches (GList *candidate_splits,
2338  GHashTable *account_hash)
2339 {
2340  for (GList* candidate = candidate_splits; candidate != NULL;
2341  candidate = g_list_next (candidate))
2342  {
2343  auto split = static_cast<Split*>(candidate->data);
2344  if (gnc_import_split_has_online_id (split))
2345  continue;
2346  /* In this context an open transaction represents a freshly
2347  * downloaded one. That can't possibly be a match yet */
2348  if (xaccTransIsOpen(xaccSplitGetParent(split)))
2349  continue;
2350  Account *split_account = xaccSplitGetAccount (split);
2351  /* g_hash_table_steal_extended would do the two calls in one shot but is
2352  * not available until GLib 2.58.
2353  */
2354  auto split_list = static_cast<GSList*>(g_hash_table_lookup (account_hash, split_account));
2355  g_hash_table_steal (account_hash, split_account);
2356  split_list = g_slist_prepend (split_list, split);
2357  g_hash_table_insert (account_hash, split_account, split_list);
2358  }
2359  return account_hash;
2360 }
2361 
2362 typedef struct _match_struct
2363 {
2364  GNCImportTransInfo* transaction_info;
2365  gint display_threshold;
2366  gint date_threshold;
2367  gint date_not_threshold;
2368  double fuzzy_amount;
2369 } match_struct;
2370 
2371 static void
2372 match_helper (Split* data, match_struct* s)
2373 {
2374  split_find_match (s->transaction_info, data,
2375  s->display_threshold,
2376  s->date_threshold,
2377  s->date_not_threshold,
2378  s->fuzzy_amount);
2379 }
2380 
2381 /* Iterate through the imported transactions selecting matches from the
2382  * potential match lists in the account hash and update the matcher with the
2383  * results.
2384  */
2385 
2386 static void
2387 perform_matching (GNCImportMainMatcher *gui, GHashTable *account_hash)
2388 {
2389  GtkTreeModel* model = gtk_tree_view_get_model (gui->view);
2390  gint display_threshold =
2391  gnc_import_Settings_get_display_threshold (gui->user_settings);
2392  gint date_threshold =
2393  gnc_import_Settings_get_date_threshold (gui->user_settings);
2394  gint date_not_threshold =
2395  gnc_import_Settings_get_date_not_threshold (gui->user_settings);
2396  double fuzzy_amount =
2397  gnc_import_Settings_get_fuzzy_amount (gui->user_settings);
2398 
2399  for (GSList *imported_txn = gui->temp_trans_list; imported_txn !=NULL;
2400  imported_txn = g_slist_next (imported_txn))
2401  {
2402  auto txn_info = static_cast<GNCImportTransInfo*>(imported_txn->data);
2403  Account *importaccount = xaccSplitGetAccount (gnc_import_TransInfo_get_fsplit (txn_info));
2404  match_struct s = {txn_info, display_threshold, date_threshold, date_not_threshold, fuzzy_amount};
2405 
2406  g_slist_foreach (static_cast<GSList*>(g_hash_table_lookup (account_hash, importaccount)),
2407  (GFunc) match_helper, &s);
2408 
2409  // Sort the matches, select the best match, and set the action.
2410  gnc_import_TransInfo_init_matches (txn_info, gui->user_settings);
2411 
2412  GNCImportMatchInfo *selected_match = gnc_import_TransInfo_get_selected_match (txn_info);
2413  bool match_selected_manually =
2415 
2416  if (selected_match)
2417  gnc_import_PendingMatches_add_match (gui->pending_matches,
2418  selected_match,
2419  match_selected_manually);
2420 
2421  GtkTreeIter iter;
2422  gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
2423  refresh_model_row (gui, model, &iter, txn_info);
2424  }
2425 }
2426 
2427 void
2428 gnc_gen_trans_list_create_matches (GNCImportMainMatcher *gui)
2429 {
2430  GHashTable* account_hash =
2431  g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
2432  (GDestroyNotify)g_slist_free);
2433  g_assert (gui);
2434  GList *candidate_splits = filter_existing_splits_on_account_and_date (gui);
2435 
2436  create_hash_of_potential_matches (candidate_splits, account_hash);
2437  perform_matching (gui, account_hash);
2438 
2439  g_list_free (candidate_splits);
2440  g_hash_table_destroy (account_hash);
2441  return;
2442 }
2443 
2444 GtkWidget *
2445 gnc_gen_trans_list_widget (GNCImportMainMatcher *info)
2446 {
2447  g_assert (info);
2448  return info->main_widget;
2449 }
2450 
2451 GtkWidget *
2452 gnc_gen_trans_list_append_text_widget (GNCImportMainMatcher *info)
2453 {
2454  g_assert (info);
2455  return info->append_text;
2456 }
2457 
2458 bool
2459 query_tooltip_tree_view_cb (GtkWidget *widget, gint x, gint y,
2460  bool keyboard_tip,
2461  GtkTooltip *tooltip,
2462  gpointer user_data)
2463 {
2464  GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
2465  GtkTreePath *path = NULL;
2466  GtkTreeViewColumn *column = NULL;
2467  gtk_tree_view_convert_widget_to_bin_window_coords (tree_view, x, y, &x, &y);
2468  if (keyboard_tip || !gtk_tree_view_get_path_at_pos (tree_view, x, y, &path,
2469  &column, NULL, NULL))
2470  {
2471  gtk_tree_path_free (path);
2472  return false;
2473  }
2474 
2475  // Get the iter pointing to our current column
2476  bool show_tooltip = false;
2477  GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
2478  GtkTreeIter iter;
2479  if (gtk_tree_model_get_iter(model, &iter, path) && column)
2480  {
2481  gchar *tooltip_text = NULL;
2482 
2483  // Select text based on column
2484  gint num_col = gtk_tree_view_column_get_sort_column_id (column);
2485  switch (num_col)
2486  {
2487  case DOWNLOADED_COL_DESCRIPTION:
2488  gtk_tree_model_get (model, &iter,
2489  DOWNLOADED_COL_DESCRIPTION_ORIGINAL, &tooltip_text,
2490  -1);
2491  break;
2492  case DOWNLOADED_COL_MEMO:
2493  gtk_tree_model_get (model, &iter,
2494  DOWNLOADED_COL_MEMO_ORIGINAL, &tooltip_text,
2495  -1);
2496  break;
2497  default:
2498  break;
2499  }
2500 
2501  // Did we select any text? If yes, display the tooltip
2502  if (tooltip_text && *tooltip_text)
2503  {
2504  show_tooltip = true;
2505  gtk_tooltip_set_text (tooltip, tooltip_text);
2506  gtk_tree_view_set_tooltip_cell (tree_view, tooltip, path, column, NULL);
2507  }
2508  g_free (tooltip_text);
2509  }
2510  // Clean up the object
2511  gtk_tree_path_free (path);
2512  return show_tooltip;
2513 }
2514 
void gnc_gen_trans_list_show_reconcile_after_close_button(GNCImportMainMatcher *info, bool reconcile_after_close, bool active)
Show and set the reconcile after close check button.
gnc_numeric gnc_import_TransInfo_get_dest_value(const GNCImportTransInfo *info)
Returns the destination split value for this TransInfo.
Split * xaccTransGetSplit(const Transaction *trans, int i)
Return a pointer to the indexed split in this transaction&#39;s split list.
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
void split_find_match(GNCImportTransInfo *trans_info, Split *split, gint display_threshold, gint date_threshold, gint date_not_threshold, double fuzzy_amount_difference)
The transaction matching heuristics are here.
void gnc_gen_trans_list_show_all(GNCImportMainMatcher *info)
Shows widgets.
gboolean xaccTransIsOpen(const Transaction *trans)
The xaccTransIsOpen() method returns TRUE if the transaction is open for editing. ...
utility functions for the GnuCash UI
GNCImportSettings * gnc_import_Settings_new(void)
Allocates a new GNCImportSettings object, and initialize it with the appropriate user prefs...
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
GdkPixbuf * gen_probability_pixbuf(gint score_original, GNCImportSettings *settings, GtkWidget *widget)
This function generates a new pixmap representing a match score.
void gnc_import_TransInfo_delete(GNCImportTransInfo *info)
Destructor.
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3237
gtk helper routines.
void xaccTransSetNotes(Transaction *trans, const char *notes)
Sets the transaction Notes.
gint gnc_import_Settings_get_display_threshold(GNCImportSettings *settings)
Return the selected threshold.
gboolean xaccAccountGetAppendText(const Account *acc)
Get the "import-append-text" flag for an account.
Definition: Account.cpp:4086
STRUCTS.
void gnc_import_TransInfo_set_ref_id(GNCImportTransInfo *info, guint32 ref_id)
Set the reference id for this TransInfo.
Tracking container for pending match status.
void gnc_import_Settings_delete(GNCImportSettings *settings)
Destructor.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
Generic importer backend interface.
GNCImportTransInfo * gnc_import_TransInfo_new(Transaction *trans, Account *base_acc)
Create a new object of GNCImportTransInfo here.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
globally unique ID User API
Split * gnc_import_TransInfo_get_fsplit(const GNCImportTransInfo *info)
Returns the first split of the transaction of this TransInfo.
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
void on_matcher_help_clicked(GtkButton *button, gpointer user_data)
This allows for the transaction help dialog to be started from the assistant button callback...
Transaction matcher main window.
Transaction * gnc_import_TransInfo_get_trans(const GNCImportTransInfo *info)
Returns the transaction of this TransInfo.
gboolean gnc_is_dark_theme(GdkRGBA *fg_color)
Return whether the current gtk theme is a dark one.
gint gnc_import_Settings_get_match_date_hardlimit(const GNCImportSettings *s)
Returns the hard-limiting number of days that a matching split may differ.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
Account * gnc_import_select_account(GtkWidget *parent, const gchar *account_online_id_value, gboolean prompt_on_no_match, const gchar *account_human_description, const gnc_commodity *new_account_default_commodity, GNCAccountType new_account_default_type, Account *default_selection, gboolean *ok_pressed)
Must be called with a string containing a unique identifier for the account.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
void gnc_gen_trans_list_add_tp_cb(GNCImportMainMatcher *info, GNCTransactionProcessedCB trans_processed_cb, gpointer user_data)
Add transaction processed callback to the transaction importer.
Generic and very flexible account matcher/picker.
GtkWidget * gnc_gen_trans_list_append_text_widget(GNCImportMainMatcher *info)
Returns the append_text widget of this dialog.
void gnc_import_TransInfo_set_destacc(GNCImportTransInfo *info, Account *acc, gboolean selected_manually)
Set the &#39;other account&#39; of this transaction (used for auto-balance if needed).
Import preference handling.
GNCImportMainMatcher * gnc_gen_trans_assist_new(GtkWidget *parent, GtkWidget *assistant_page, const gchar *heading, bool all_from_same_account, gint match_date_hardlimit)
Add the Transaction matcher to an existing page of an assistant.
These expect a single object and expect the QofAccessFunc returns GncGUID*.
Definition: qofquerycore.h:113
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
void gnc_style_context_get_background_color(GtkStyleContext *context, GtkStateFlags state, GdkRGBA *color)
Wrapper to get the background color of a widget for a given state.
void gnc_gen_trans_list_add_trans(GNCImportMainMatcher *gui, Transaction *trans)
Add a newly imported Transaction to the Transaction Importer.
void gnc_import_TransInfo_init_matches(GNCImportTransInfo *trans_info, GNCImportSettings *settings)
Iterates through all splits of trans_info&#39;s originating account match list.
GNCImportAction gnc_import_TransInfo_get_action(const GNCImportTransInfo *info)
Returns the currently selected action for this TransInfo.
Account used to record multiple commodity transactions.
Definition: Account.h:155
void xaccTransDestroy(Transaction *trans)
Destroys a transaction.
bool gnc_gen_trans_list_empty(GNCImportMainMatcher *info)
Checks whether there are no transactions to match.
gboolean qof_log_check(QofLogModule domain, QofLogLevel level)
Check to see if the given log_module is configured to log at the given log_level. ...
Definition: qoflog.cpp:324
const char * xaccTransGetNotes(const Transaction *trans)
Gets the transaction Notes.
char * gnc_get_account_name_for_register(const Account *account)
Get either the full name of the account or the simple name, depending on the configuration parameter ...
int xaccTransCountSplits(const Transaction *trans)
Returns the number of splits in this transaction.
gboolean gnc_import_process_trans_item(Account *base_acc, GNCImportTransInfo *trans_info)
/brief – Processes one match according to its selected action.
void gnc_gen_trans_list_add_trans_with_split_data(GNCImportMainMatcher *gui, Transaction *trans, GNCImportLastSplitInfo *lsplit)
Add a newly imported Transaction to the Transaction Importer.
gdouble gnc_numeric_to_double(gnc_numeric n)
Convert numeric to floating-point value.
char * qof_print_date(time64 secs)
Convenience; calls through to qof_print_date_dmy_buff().
Definition: gnc-date.cpp:609
gnc_numeric gnc_numeric_invert(gnc_numeric num)
Invert a gnc_numeric.
gint gnc_import_MatchInfo_get_probability(const GNCImportMatchInfo *info)
Get the probability (confidence level) of this MatchInfo.
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:3275
void qof_query_destroy(QofQuery *query)
Frees the resources associate with a Query object.
GtkWidget * gnc_gen_trans_list_get_reconcile_after_close_button(GNCImportMainMatcher *info)
Returns the reconcile after close check button.
GtkWidget * gnc_gen_trans_list_widget(GNCImportMainMatcher *info)
Returns the widget of this dialog.
Account public routines (C++ api)
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:204
void gnc_gen_trans_assist_start(GNCImportMainMatcher *info)
This starts the import process for transaction from an assistant.
void xaccSplitSetMemo(Split *split, const char *memo)
The memo is an arbitrary string associated with a split.
void qof_query_set_book(QofQuery *query, QofBook *book)
Set the book to be searched.
GNCImportMainMatcher * gnc_gen_trans_list_new(GtkWidget *parent, const gchar *heading, bool all_from_same_account, gint match_date_hardlimit, bool show_all)
Create a new generic transaction dialog window and return it.
gchar * gnc_list_formatter(GList *strings)
This function takes a GList of char*, and uses locale-sensitive list formatter.
void gnc_import_TransInfo_set_action(GNCImportTransInfo *info, GNCImportAction action)
Set the action for this TransInfo.
void gnc_import_Settings_set_match_date_hardlimit(GNCImportSettings *s, gint m)
void xaccAccountRecomputeBalance(Account *acc)
The following recompute the partial balances (stored with the transaction) and the total balance...
Definition: Account.cpp:2281
gboolean gnc_import_exists_online_id(Transaction *trans, GHashTable *acct_id_hash)
Checks whether the given transaction&#39;s online_id already exists in its parent account.
double gnc_import_Settings_get_fuzzy_amount(GNCImportSettings *settings)
Return the allowed amount range for fuzzy amount matching.
const char * xaccTransGetDescription(const Transaction *trans)
Gets the transaction Description.
gboolean gnc_account_get_defer_bal_computation(Account *acc)
Get the account&#39;s flag for deferred balance computation.
Definition: Account.cpp:1926
gnc_numeric gnc_import_TransInfo_get_dest_amount(const GNCImportTransInfo *info)
Returns the destination split amount for this TransInfo.
void gnc_import_TransInfo_set_append_text(GNCImportTransInfo *info, gboolean append_text)
Set the append_text for this TransInfo.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
All type declarations for the whole Gnucash engine.
void xaccAccountSetAppendText(Account *acc, gboolean val)
Set the "import-append-text" flag for an account.
Definition: Account.cpp:4092
GLib helper routines.
#define xaccTransGetGUID(X)
Definition: Transaction.h:788
void gnc_import_TransInfo_set_price(GNCImportTransInfo *info, gnc_numeric lprice)
Set the exchange rate for this TransInfo.
void gnc_gen_trans_list_add_trans_with_ref_id(GNCImportMainMatcher *gui, Transaction *trans, guint32 ref_id)
Add a newly imported Transaction to the Transaction Importer and provide an external reference id for...
gboolean gnc_import_Settings_get_action_update_enabled(GNCImportSettings *settings)
Return the selected action is enable state.
gboolean gnc_import_TransInfo_get_match_selected_manually(const GNCImportTransInfo *info)
Returns if the currently selected match was selected by the user.
GList * qof_query_run(QofQuery *query)
Perform the query, return the results.
void gnc_import_match_picker_run_and_close(GtkWidget *parent, GNCImportTransInfo *transaction_info, GNCImportPendingMatches *pending_matches)
Run a match_picker dialog so that the selected-MatchInfo in the given trans_info is updated according...
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition: Account.cpp:1477
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
Definition: gmock-Split.cpp:53
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3371
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
void gnc_gen_trans_list_delete(GNCImportMainMatcher *info)
Deletes the given object.
bool gnc_gen_trans_list_run(GNCImportMainMatcher *info)
Run this dialog and return only after the user pressed Ok, Cancel, or closed the window.
Split * xaccSplitGetOtherSplit(const Split *split)
The xaccSplitGetOtherSplit() is a convenience routine that returns the other of a pair of splits...
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
Account * gnc_import_TransInfo_get_destacc(const GNCImportTransInfo *info)
Returns the &#39;other account&#39; of this transaction.
const char * xaccSplitGetMemo(const Split *split)
Returns the memo string.
Definition: gmock-Split.cpp:99
gnc_numeric gnc_import_TransInfo_get_price(const GNCImportTransInfo *info)
Returns the exchange rate for this TransInfo.
GList * gnc_import_TransInfo_get_match_list(const GNCImportTransInfo *info)
Returns the stored list of possible matches.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
GNCImportMatchInfo * gnc_import_TransInfo_get_selected_match(const GNCImportTransInfo *info)
Returns the currently selected match in this TransInfo.
void gnc_account_set_defer_bal_computation(Account *acc, gboolean defer)
Set the defer balance flag.
Definition: Account.cpp:1913
const char * xaccAccountGetName(const Account *acc)
Get the account&#39;s name.
Definition: Account.cpp:3259
void gnc_import_TransInfo_remove_top_match(GNCImportTransInfo *info)
Remove the first match in the list of possible matches.
Not a type.
Definition: Account.h:105
The type used to store guids in C.
Definition: guid.h:75
A Query.
Definition: qofquery.cpp:74
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition: Account.cpp:1518
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.
gboolean gnc_import_Settings_get_action_skip_enabled(GNCImportSettings *settings)
Return the selected action is enable state.
gboolean gnc_import_TransInfo_is_balanced(const GNCImportTransInfo *info)
Returns if the transaction stored in the TransInfo is currently balanced.
void gnc_import_TransInfo_set_last_split_info(GNCImportTransInfo *info, GNCImportLastSplitInfo *lsplit)
Sets additional parameters to be used to generate the closing split.
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account&#39;s commodity.
Definition: gmock-Split.cpp:69
gboolean gnc_import_TransInfo_get_destacc_selected_manually(const GNCImportTransInfo *info)
Returns if the currently selected destination account for auto-matching was selected by the user...