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