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