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