GnuCash  4.8a-132-gcdaeb421d+
import-main-matcher.c
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 
40 #include "import-main-matcher.h"
41 
42 #include "dialog-utils.h"
43 #include "gnc-glib-utils.h"
44 #include "gnc-ui.h"
45 #include "gnc-ui-util.h"
46 #include "gnc-engine.h"
47 #include "gnc-gtk-utils.h"
48 #include "import-settings.h"
49 #include "import-match-picker.h"
50 #include "import-backend.h"
51 #include "import-account-matcher.h"
52 #include "import-pending-matches.h"
53 #include "gnc-component-manager.h"
54 #include "guid.h"
55 #include "gnc-session.h"
56 #include "Query.h"
57 #include "SplitP.h"
58 
59 #define GNC_PREFS_GROUP "dialogs.import.generic.transaction-list"
60 #define IMPORT_MAIN_MATCHER_CM_CLASS "transaction-matcher-dialog"
61 
63 {
64  GtkWidget *main_widget;
65  GtkTreeView *view;
66  GNCImportSettings *user_settings;
67  int selected_row;
68  gboolean dark_theme;
69  GNCTransactionProcessedCB transaction_processed_cb;
70  gpointer user_data;
71  GNCImportPendingMatches *pending_matches;
72  GtkTreeViewColumn *account_column;
73  GtkTreeViewColumn *memo_column;
74  GtkWidget *show_account_column;
75  GtkWidget *show_matched_info;
76  GtkWidget *reconcile_after_close;
77  gboolean add_toggled; // flag to indicate that add has been toggled to stop selection
78  gint id;
79  GSList* temp_trans_list; // Temporary list of imported transactions
80  GHashTable* acct_id_hash; // Hash table, per account, of list of transaction IDs.
81  GSList* edited_accounts; // List of accounts currently edited.
82 };
83 
84 enum downloaded_cols
85 {
86  DOWNLOADED_COL_DATE_TXT = 0,
87  DOWNLOADED_COL_DATE_INT64, // used only for sorting
88  DOWNLOADED_COL_ACCOUNT,
89  DOWNLOADED_COL_AMOUNT,
90  DOWNLOADED_COL_AMOUNT_DOUBLE, // used only for sorting
91  DOWNLOADED_COL_DESCRIPTION,
92  DOWNLOADED_COL_MEMO,
93  DOWNLOADED_COL_ACTION_ADD,
94  DOWNLOADED_COL_ACTION_CLEAR,
95  DOWNLOADED_COL_ACTION_UPDATE,
96  DOWNLOADED_COL_ACTION_INFO,
97  DOWNLOADED_COL_ACTION_PIXBUF,
98  DOWNLOADED_COL_DATA,
99  DOWNLOADED_COL_COLOR,
100  DOWNLOADED_COL_ENABLE,
101  NUM_DOWNLOADED_COLS
102 };
103 
104 #define CSS_INT_REQUIRED_CLASS "gnc-class-intervention-required"
105 #define CSS_INT_PROB_REQUIRED_CLASS "gnc-class-intervention-probably-required"
106 #define CSS_INT_NOT_REQUIRED_CLASS "gnc-class-intervention-not-required"
107 
108 /* Define log domain for extended debugging of matcher */
109 #define G_MOD_IMPORT_MATCHER "gnc.import.main-matcher"
110 /*static QofLogModule log_module = GNC_MOD_IMPORT;*/
111 static QofLogModule log_module = G_MOD_IMPORT_MATCHER;
112 
113 void on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info);
114 void on_matcher_cancel_clicked (GtkButton *button, gpointer user_data);
115 gboolean on_matcher_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data);
116 void on_matcher_help_clicked (GtkButton *button, gpointer user_data);
117 void on_matcher_help_close_clicked (GtkButton *button, gpointer user_data);
118 
119 static void gnc_gen_trans_list_create_matches (GNCImportMainMatcher *gui);
120 
121 /* Local prototypes */
122 static void gnc_gen_trans_assign_transfer_account (GtkTreeView *treeview,
123  gboolean *first,
124  gboolean is_selection,
125  GtkTreePath *path,
126  Account **new_acc,
127  GNCImportMainMatcher *info);
128 static void gnc_gen_trans_assign_transfer_account_to_selection_cb (GtkMenuItem *menuitem,
129  GNCImportMainMatcher *info);
130 static void gnc_gen_trans_view_popup_menu (GtkTreeView *treeview,
131  GdkEvent *event,
132  GNCImportMainMatcher *info);
133 static gboolean gnc_gen_trans_onButtonPressed_cb (GtkTreeView *treeview,
134  GdkEvent *event,
135  GNCImportMainMatcher *info);
136 static gboolean gnc_gen_trans_onPopupMenu_cb (GtkTreeView *treeview,
137  GNCImportMainMatcher *info);
138 static void refresh_model_row (GNCImportMainMatcher *gui,
139  GtkTreeModel *model,
140  GtkTreeIter *iter,
141  GNCImportTransInfo *info);
142 static gboolean query_tooltip_tree_view_cb (GtkWidget *widget, gint x, gint y,
143  gboolean keyboard_tip,
144  GtkTooltip *tooltip,
145  gpointer user_data);
146 /* end local prototypes */
147 
148 static
149 gboolean delete_hash (gpointer key, gpointer value, gpointer user_data)
150 {
151  // Value is a hash table that needs to be destroyed.
152  g_hash_table_destroy (value);
153  return TRUE;
154 }
155 
156 static void
157 update_all_balances (GNCImportMainMatcher *info)
158 {
159  for (GSList* iter = info->edited_accounts; iter; iter=iter->next)
160  {
161  gnc_account_set_defer_bal_computation (iter->data,FALSE);
162  xaccAccountRecomputeBalance (iter->data);
163  }
164  g_slist_free (info->edited_accounts);
165  info->edited_accounts = NULL;
166 }
167 
168 static void
169 defer_bal_computation (GNCImportMainMatcher *info, Account* acc)
170 {
172  {
174  info->edited_accounts = g_slist_prepend (info->edited_accounts, acc);
175  }
176 }
177 
178 void
179 gnc_gen_trans_list_delete (GNCImportMainMatcher *info)
180 {
181  GtkTreeModel *model;
182  GtkTreeIter iter;
183  GNCImportTransInfo *trans_info;
184 
185  if (info == NULL)
186  return;
187 
188  model = gtk_tree_view_get_model (info->view);
189  if (gtk_tree_model_get_iter_first (model, &iter))
190  {
191  do
192  {
193  gtk_tree_model_get (model, &iter,
194  DOWNLOADED_COL_DATA, &trans_info,
195  -1);
196 
197  if (info->transaction_processed_cb)
198  {
199  info->transaction_processed_cb (trans_info, FALSE,
200  info->user_data);
201  }
202  }
203  while (gtk_tree_model_iter_next (model, &iter));
204  }
205 
206  if (GTK_IS_DIALOG(info->main_widget))
207  {
208  gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(info->main_widget));
209  gnc_import_Settings_delete (info->user_settings);
210  gnc_unregister_gui_component (info->id);
211  gtk_widget_destroy (GTK_WIDGET(info->main_widget));
212  }
213  else
214  gnc_import_Settings_delete (info->user_settings);
215 
216  g_slist_free_full (info->temp_trans_list, (GDestroyNotify) gnc_import_TransInfo_delete);
217  info->temp_trans_list = NULL;
218 
219  // We've deferred balance computations on many accounts. Let's do it now that we're done.
220  update_all_balances (info);
221 
222  g_hash_table_foreach_remove (info->acct_id_hash, delete_hash, NULL);
223  info->acct_id_hash = NULL;
224  g_free (info);
225 }
226 
227 gboolean
228 gnc_gen_trans_list_empty (GNCImportMainMatcher *info)
229 {
230  GtkTreeModel *model;
231  GtkTreeIter iter;
232  GNCImportTransInfo *trans_info;
233  g_assert (info);
234  model = gtk_tree_view_get_model (info->view);
235  // Check that both the tree model and the temporary list are empty.
236  return !gtk_tree_model_get_iter_first (model, &iter) && !info->temp_trans_list;
237 }
238 
239 static void
240 gnc_gen_trans_list_show_accounts_column (GNCImportMainMatcher *info)
241 {
242  GtkTreeModel *model;
243  GtkTreeIter iter;
244  GNCImportTransInfo *trans_info;
245  gboolean multiple_accounts = FALSE;
246  gboolean valid;
247 
248  g_assert (info);
249 
250  model = gtk_tree_view_get_model (info->view);
251 
252  if (gtk_tree_model_iter_n_children (model, NULL) > 1)
253  {
254  /* Get first row in list store */
255  valid = gtk_tree_model_get_iter_first (model, &iter);
256  if (valid)
257  {
258  gchar *account_name;
259  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_ACCOUNT, &account_name, -1);
260 
261  valid = gtk_tree_model_iter_next (model, &iter);
262 
263  while (valid)
264  {
265  gchar *test_account_name;
266 
267  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_ACCOUNT, &test_account_name, -1);
268  if (g_strcmp0 (account_name, test_account_name) != 0)
269  {
270  multiple_accounts = TRUE;
271  g_free (test_account_name);
272  break;
273  }
274  valid = gtk_tree_model_iter_next (model, &iter);
275  g_free (test_account_name);
276  }
277  g_free (account_name);
278  }
279  // now toggle the column
280  if (multiple_accounts)
281  {
282  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), TRUE);
283  gtk_tree_view_expand_all (info->view);
284  }
285  else
286  {
287  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), FALSE);
288  gtk_tree_view_collapse_all (info->view);
289  }
290  }
291 }
292 
293 // This returns the transaction ID of the first match candidate in match_list
294 static const GncGUID*
295 get_top_trans_match_id (GList* match_list)
296 {
297  Transaction* trans = NULL;
298  GNCImportMatchInfo* match_info;
299  if (!match_list || !match_list->data) return NULL;
300  match_info = match_list->data;
301  trans = match_info->trans;
302  return xaccTransGetGUID (trans);
303 }
304 
305 // This returns the transaction score of the first match candidate in match_list
306 static gint
307 get_top_trans_match_score (GList* match_list)
308 {
309  GNCImportMatchInfo* match_info;
310  if (!match_list || !match_list->data) return 0;
311  match_info = match_list->data;
312  return match_info->probability;
313 }
314 
315 static GList*
316 get_trans_match_list (GtkTreeModel* model, GtkTreeIter* iter)
317 {
318  GNCImportTransInfo* transaction_info;
319  gtk_tree_model_get (model, iter,
320  DOWNLOADED_COL_DATA, &transaction_info,
321  -1);
322  return gnc_import_TransInfo_get_match_list (transaction_info);
323 }
324 
325 static GNCImportTransInfo*
326 get_trans_info (GtkTreeModel* model, GtkTreeIter* iter)
327 {
328  GNCImportTransInfo* transaction_info;
329  gtk_tree_model_get (model, iter,
330  DOWNLOADED_COL_DATA, &transaction_info,
331  -1);
332  return transaction_info;
333 }
334 /* This fuction find the top matching register transaction for the imported transaction pointed to by iter
335  * It then goes through the list of all other imported transactions and creates a list of the ones that
336  * have the same register transaction as their top match (i.e., are in conflict). It finds the best of them
337  * (match-score-wise) and returns the rest as a list. The imported transactions in that list will get their
338  * top match modified. */
339 static GList*
340 get_conflict_list (GtkTreeModel* model, GtkTreeIter import_iter, GncGUID* id, gint best_match)
341 {
342  GtkTreeIter iter = import_iter;
343  GNCImportTransInfo* best_import = get_trans_info (model, &import_iter);
344  GList* conflicts = g_list_prepend (NULL, best_import);
345 
346  while (gtk_tree_model_iter_next (model, &iter))
347  {
348  gint match_score = 0;
349  GNCImportTransInfo* trans_info;
350  GncGUID id2;
351  // Get the ID of the top matching trans for this imported trans.
352  GList* register_iter = get_trans_match_list (model, &iter);
353  if (!register_iter || !register_iter->data)
354  continue;
355 
356  id2 = *get_top_trans_match_id (register_iter);
357  if (!guid_equal (id, &id2))
358  continue;
359 
360  // Conflict. Get the match score, add this transaction to our list.
361  match_score = get_top_trans_match_score (register_iter);
362  trans_info = get_trans_info (model, &iter);
363  conflicts = g_list_prepend (conflicts, trans_info);
364 
365  if (match_score > best_match)
366  {
367  // Keep track of the imported transaction with the best score.
368  best_match = match_score;
369  best_import = trans_info;
370  }
371  }
372 
373  // Remove the best match from the list of conflicts, as it will keep its match
374  conflicts = g_list_remove (conflicts, best_import);
375  return conflicts;
376 }
377 
378 static void
379 remove_top_matches (GNCImportMainMatcher* gui, GtkTreeModel* model, GList* conflicts)
380 {
381  GList* iter = conflicts;
382  for (; iter && iter->data; iter=iter->next)
383  {
384  GNCImportTransInfo* trans_info = iter->data;
385  GList* match_trans = gnc_import_TransInfo_get_match_list (trans_info);
386  match_trans = g_list_remove (match_trans, match_trans->data);
387  gnc_import_TransInfo_set_match_list (trans_info, match_trans);
388  }
389 
390  g_list_free (conflicts);
391 }
392 
393 static void
394 resolve_conflicts (GNCImportMainMatcher *info)
395 {
396  GtkTreeModel* model = gtk_tree_view_get_model (info->view);
397  GtkTreeIter import_iter, best_import;
398  gint best_match = 0;
399 
400  /* A greedy conflict resolution. Find all imported trans that vie for the same
401  * register trans. Assign the reg trans to the imported trans with the best match.
402  * Loop over the imported transactions */
403  gboolean valid = gtk_tree_model_get_iter_first (model, &import_iter);
404  while (valid)
405  {
406  GList* conflicts = NULL;
407  GncGUID id;
408  GList* match_list = get_trans_match_list (model, &import_iter);
409  if (!match_list || !match_list->data)
410  {
411  valid = gtk_tree_model_iter_next (model, &import_iter);
412  continue;
413  }
414 
415  // The ID of the best current match for this imported trans
416  id = *get_top_trans_match_id (match_list);
417  best_match = get_top_trans_match_score (match_list);
418  best_import = import_iter;
419  /* Get a list of all imported transactions that have a conflict with this one.
420  * The returned list excludes the best transaction. */
421  conflicts = get_conflict_list (model, import_iter, &id, best_match);
422 
423  if (conflicts)
424  {
425  remove_top_matches (info, model, conflicts);
426  /* Go back to the beginning here, because a nth choice
427  * could now conflict with a previously assigned first choice. */
428  valid = gtk_tree_model_get_iter_first (model, &import_iter);
429  }
430  else
431  valid = gtk_tree_model_iter_next (model, &import_iter);
432  /* NOTE: The loop is guaranteed to terminate because whenever we go back to the top
433  * we remove at least 1 match, and there's a finite number of them. */
434  }
435 
436  // Refresh all
437  valid = gtk_tree_model_get_iter_first (model, &import_iter);
438  while (valid)
439  {
440  refresh_model_row (info, model, &import_iter, get_trans_info (model, &import_iter));
441  valid = gtk_tree_model_iter_next (model, &import_iter);
442  }
443 }
444 
445 void
446 gnc_gen_trans_list_show_all (GNCImportMainMatcher *info)
447 {
448  g_assert (info);
449  gnc_gen_trans_list_create_matches (info);
450  resolve_conflicts (info);
451  gtk_widget_show_all (GTK_WIDGET(info->main_widget));
452  gnc_gen_trans_list_show_accounts_column (info);
453 }
454 
455 void
456 on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info)
457 {
458  GtkTreeModel *model;
459  GtkTreeIter iter;
460  GNCImportTransInfo *trans_info;
461 
462  g_assert (info);
463 
464  /* DEBUG ("Begin") */
465 
466  model = gtk_tree_view_get_model (info->view);
467  if (!gtk_tree_model_get_iter_first (model, &iter))
468  {
469  // No transaction, we can just close the dialog.
471  return;
472  }
473 
474  /* Don't run any queries and/or split sorts while processing the matcher
475  results. */
476  gnc_suspend_gui_refresh ();
477  do
478  {
479  gtk_tree_model_get (model, &iter,
480  DOWNLOADED_COL_DATA, &trans_info,
481  -1);
482 
483  // Note: if there's only 1 split (unbalanced) one will be created with the unbalanced account,
484  // and for that account the defer balance will not be set. So things will be slow.
485 
486  if (gnc_import_process_trans_item (NULL, trans_info))
487  {
488  if (info->transaction_processed_cb)
489  {
490  info->transaction_processed_cb (trans_info, TRUE,
491  info->user_data);
492  }
493  }
494  }
495  while (gtk_tree_model_iter_next (model, &iter));
496 
498 
499  /* Allow GUI refresh again. */
500  gnc_resume_gui_refresh ();
501 
502  /* DEBUG ("End") */
503 }
504 
505 void
506 on_matcher_cancel_clicked (GtkButton *button, gpointer user_data)
507 {
508  GNCImportMainMatcher *info = user_data;
510 }
511 
512 gboolean
513 on_matcher_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
514 {
515  GNCImportMainMatcher *info = data;
517  return FALSE;
518 }
519 
520 void
521 on_matcher_help_close_clicked (GtkButton *button, gpointer user_data)
522 {
523  GtkWidget *help_dialog = user_data;
524 
525  gtk_widget_destroy (help_dialog);
526 }
527 
528 void
529 on_matcher_help_clicked (GtkButton *button, gpointer user_data)
530 {
531  GNCImportMainMatcher *info = user_data;
532  GtkBuilder *builder;
533  GtkWidget *help_dialog, *box;
534  gchar *int_required_class, *int_prob_required_class, *int_not_required_class;
535  gchar *class_extension = NULL;
536 
537  builder = gtk_builder_new ();
538  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer2");
539  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer3");
540  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer4");
541  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer5");
542  gnc_builder_add_from_file (builder, "dialog-import.glade", "textbuffer1");
543  gnc_builder_add_from_file (builder, "dialog-import.glade", "matcher_help_dialog");
544 
545  if (info->dark_theme == TRUE)
546  class_extension = "-dark";
547 
548  int_required_class = g_strconcat (CSS_INT_REQUIRED_CLASS, class_extension, NULL);
549  int_prob_required_class = g_strconcat (CSS_INT_PROB_REQUIRED_CLASS, class_extension, NULL);
550  int_not_required_class = g_strconcat (CSS_INT_NOT_REQUIRED_CLASS, class_extension, NULL);
551 
552  box = GTK_WIDGET(gtk_builder_get_object (builder, "intervention_required_box"));
553  gnc_widget_style_context_add_class (GTK_WIDGET(box), int_required_class);
554 
555  box = GTK_WIDGET(gtk_builder_get_object (builder, "intervention_probably_required_box"));
556  gnc_widget_style_context_add_class (GTK_WIDGET(box), int_prob_required_class);
557 
558  box = GTK_WIDGET(gtk_builder_get_object (builder, "intervention_not_required_box"));
559  gnc_widget_style_context_add_class (GTK_WIDGET(box), int_not_required_class);
560 
561  help_dialog = GTK_WIDGET(gtk_builder_get_object (builder, "matcher_help_dialog"));
562  gtk_window_set_transient_for (GTK_WINDOW(help_dialog), GTK_WINDOW(info->main_widget));
563 
564  /* Connect the signals */
565  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, help_dialog);
566 
567  g_object_unref (G_OBJECT(builder));
568 
569  g_free (int_required_class);
570  g_free (int_prob_required_class);
571  g_free (int_not_required_class);
572 
573  gtk_widget_show (help_dialog);
574 }
575 
576 static void
577 run_account_picker_dialog (GNCImportMainMatcher *info,
578  GtkTreeModel *model,
579  GtkTreeIter *iter,
580  GNCImportTransInfo *trans_info)
581 {
582  Account *old_acc, *new_acc;
583  gboolean ok_pressed;
584  g_assert (trans_info);
585  old_acc = gnc_import_TransInfo_get_destacc (trans_info);
586 
587  new_acc = gnc_import_select_account (
588  info->main_widget,
589  NULL,
590  TRUE,
591  _("Destination account for the auto-balance split."),
594  old_acc,
595  &ok_pressed);
596  if (ok_pressed)
597  {
598  gnc_import_TransInfo_set_destacc (trans_info, new_acc, TRUE);
599  defer_bal_computation (info, new_acc);
600  }
601 }
602 
603 static void
604 run_match_dialog (GNCImportMainMatcher *info,
605  GNCImportTransInfo *trans_info)
606 {
607  gnc_import_match_picker_run_and_close (info->main_widget,
608  trans_info, info->pending_matches);
609 }
610 
611 static void
612 gnc_gen_trans_add_toggled_cb (GtkCellRendererToggle *cell_renderer,
613  gchar *path,
614  GNCImportMainMatcher *gui)
615 {
616  GtkTreeModel *model;
617  GtkTreeIter iter;
618  GNCImportTransInfo *trans_info;
619 
620  ENTER("");
621  model = gtk_tree_view_get_model (gui->view);
622  if (!gtk_tree_model_get_iter_from_string (model, &iter, path))
623  return;
624  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, &trans_info, -1);
625 
626  if (gnc_import_TransInfo_get_action (trans_info) == GNCImport_ADD &&
627  gnc_import_Settings_get_action_skip_enabled (gui->user_settings) == TRUE)
628  {
629  gnc_import_TransInfo_set_action (trans_info, GNCImport_SKIP);
630  }
631  else
632  {
633  gnc_import_TransInfo_set_action (trans_info, GNCImport_ADD);
634  }
635  refresh_model_row (gui, model, &iter, trans_info);
636  LEAVE("");
637 }
638 
639 static void
640 gnc_gen_trans_clear_toggled_cb (GtkCellRendererToggle *cell_renderer,
641  gchar *path,
642  GNCImportMainMatcher *gui)
643 {
644  GtkTreeModel *model;
645  GtkTreeIter iter;
646  GNCImportTransInfo *trans_info;
647 
648  ENTER("");
649  model = gtk_tree_view_get_model (gui->view);
650 
651  if (!gtk_tree_model_get_iter_from_string (model, &iter, path))
652  return;
653  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, &trans_info, -1);
654 
655  if (gnc_import_TransInfo_get_action (trans_info) == GNCImport_CLEAR &&
656  gnc_import_Settings_get_action_skip_enabled (gui->user_settings) == TRUE)
657  {
658  gnc_import_TransInfo_set_action (trans_info, GNCImport_SKIP);
659  }
660  else
661  {
662  gnc_import_TransInfo_set_action (trans_info, GNCImport_CLEAR);
663  }
664  refresh_model_row (gui, model, &iter, trans_info);
665  LEAVE("");
666 }
667 
668 static void
669 gnc_gen_trans_update_toggled_cb (GtkCellRendererToggle *cell_renderer,
670  gchar *path,
671  GNCImportMainMatcher *gui)
672 {
673  GtkTreeModel *model;
674  GtkTreeIter iter;
675  GNCImportTransInfo *trans_info;
676 
677  ENTER("");
678  model = gtk_tree_view_get_model (gui->view);
679 
680  if (!gtk_tree_model_get_iter_from_string (model, &iter, path))
681  return;
682  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, &trans_info, -1);
683 
684  if (gnc_import_TransInfo_get_action (trans_info) == GNCImport_UPDATE &&
685  gnc_import_Settings_get_action_skip_enabled (gui->user_settings) == TRUE)
686  {
687  gnc_import_TransInfo_set_action (trans_info, GNCImport_SKIP);
688  }
689  else
690  {
691  gnc_import_TransInfo_set_action (trans_info, GNCImport_UPDATE);
692  }
693  refresh_model_row (gui, model, &iter, trans_info);
694  LEAVE("");
695 }
696 
697 static void
698 gnc_gen_trans_assign_transfer_account (GtkTreeView *treeview,
699  gboolean *first,
700  gboolean is_selection,
701  GtkTreePath *path,
702  Account **new_acc,
703  GNCImportMainMatcher *info)
704 {
705  GtkTreeModel *model;
706  GtkTreeIter iter;
707  GNCImportTransInfo *trans_info;
708  Account *old_acc;
709  gboolean ok_pressed;
710  gchar *path_str = gtk_tree_path_to_string (path);
711  gchar *acct_str = gnc_get_account_name_for_register (*new_acc);
712 
713  ENTER("");
714  DEBUG("first = %s", *first ? "true" : "false");
715  DEBUG("is_selection = %s", is_selection ? "true" : "false");
716  DEBUG("path = %s", path_str);
717  g_free (path_str);
718  DEBUG("account passed in = %s", acct_str);
719  g_free (acct_str);
720 
721  // only allow response at the top level
722  if (gtk_tree_path_get_depth (path) != 1)
723  return;
724 
725  model = gtk_tree_view_get_model (treeview);
726  if (gtk_tree_model_get_iter (model, &iter, path))
727  {
728  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, &trans_info, -1);
729 
730  switch (gnc_import_TransInfo_get_action (trans_info))
731  {
732  case GNCImport_ADD:
733  if (gnc_import_TransInfo_is_balanced (trans_info) == FALSE)
734  {
735  ok_pressed = TRUE;
736  old_acc = gnc_import_TransInfo_get_destacc (trans_info);
737  if (*first)
738  {
739  gchar *acc_full_name = gnc_account_get_full_name (*new_acc);
740  ok_pressed = FALSE;
741  *new_acc = gnc_import_select_account (info->main_widget,
742  NULL,
743  TRUE,
744  _("Destination account for the auto-balance split."),
746  gnc_import_TransInfo_get_trans (trans_info)),
748  old_acc,
749  &ok_pressed);
750  *first = FALSE;
751  acc_full_name = gnc_account_get_full_name (*new_acc);
752  DEBUG("account selected = %s", acc_full_name);
753  g_free (acc_full_name);
754  }
755  if (ok_pressed)
756  {
757  gnc_import_TransInfo_set_destacc (trans_info, *new_acc, TRUE);
758  defer_bal_computation (info, *new_acc);
759  }
760  }
761  break;
762  case GNCImport_CLEAR:
763  case GNCImport_UPDATE:
764  if (*first && !is_selection)
765  run_match_dialog (info, trans_info);
766  break;
767  case GNCImport_SKIP:
768  break;
769  default:
770  PERR("InvalidGNCImportValue");
771  break;
772  }
773  refresh_model_row (info, model, &iter, trans_info);
774  }
775  LEAVE("");
776 }
777 
778 static void
779 gnc_gen_trans_assign_transfer_account_to_selection_cb (GtkMenuItem *menuitem,
780  GNCImportMainMatcher *info)
781 {
782  GtkTreeView *treeview;
783  GtkTreeSelection *selection;
784  GtkTreeModel *model;
785  GtkTreeIter iter;
786  GNCImportTransInfo *trans_info;
787  Account *assigned_account;
788  GList *selected_rows, *l;
789  gboolean first, is_selection;
790  GList *refs = NULL;
791 
792  ENTER("assign_transfer_account_to_selection_cb");
793  treeview = GTK_TREE_VIEW(info->view);
794  model = gtk_tree_view_get_model (treeview);
795  selection = gtk_tree_view_get_selection (treeview);
796  selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
797  assigned_account = NULL;
798  first = TRUE;
799  is_selection = TRUE;
800 
801  DEBUG("Rows in selection = %i",
802  gtk_tree_selection_count_selected_rows (selection));
803  DEBUG("Entering loop over selection");
804 
805  if (gtk_tree_selection_count_selected_rows (selection) > 0)
806  {
807  for (l = selected_rows; l != NULL; l = l->next)
808  {
809  gchar *path_str = gtk_tree_path_to_string (l->data);
810  GtkTreeRowReference *ref = gtk_tree_row_reference_new (model, l->data);
811  gchar *fullname;
812  DEBUG("passing first = %s", first ? "true" : "false");
813  DEBUG("passing is_selection = %s", is_selection ? "true" : "false");
814  DEBUG("passing path = %s", path_str);
815  g_free (path_str);
816  refs = g_list_prepend (refs, ref);
817  fullname = gnc_account_get_full_name (assigned_account);
818  DEBUG("passing account value = %s", fullname);
819  g_free (fullname);
820  gnc_gen_trans_assign_transfer_account (treeview,
821  &first, is_selection, l->data,
822  &assigned_account, info);
823  fullname = gnc_account_get_full_name (assigned_account);
824  DEBUG("returned value of account = %s", fullname);
825  DEBUG("returned value of first = %s", first ? "true" : "false");
826  g_free (fullname);
827  if (assigned_account == NULL)
828  break;
829 
830  }
831  }
832  g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free);
833 
834  // now reselect the transaction rows. This is very slow if there are lots of transactions.
835  for (l = refs; l != NULL; l = l->next)
836  {
837  GtkTreePath *path = gtk_tree_row_reference_get_path (l->data);
838 
839  gtk_tree_selection_select_path (selection, path);
840 
841  gtk_tree_path_free (path);
842  gtk_tree_row_reference_free (l->data);
843  }
844  g_list_free (refs);
845 
846  LEAVE("");
847 }
848 
849 static void
850 gnc_gen_trans_row_activated_cb (GtkTreeView *treeview,
851  GtkTreePath *path,
852  GtkTreeViewColumn *column,
853  GNCImportMainMatcher *info)
854 {
855  Account *assigned_account;
856  gboolean first, is_selection;
857  gchar *namestr;
858 
859  ENTER("");
860  assigned_account = NULL;
861  first = TRUE;
862  is_selection = FALSE;
863  gnc_gen_trans_assign_transfer_account (treeview,
864  &first, is_selection, path,
865  &assigned_account, info);
866 
867  gtk_tree_selection_select_path (gtk_tree_view_get_selection (treeview), path);
868 
869  namestr = gnc_account_get_full_name (assigned_account);
870  DEBUG("account returned = %s", namestr);
871  g_free (namestr);
872  LEAVE("");
873 }
874 
875 static GNCImportAction
876 get_action_for_path (GtkTreePath* path, GtkTreeModel *model)
877 {
878  GNCImportTransInfo *trans_info;
879  GtkTreeIter iter;
880  gtk_tree_model_get_iter (model, &iter, path);
881  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, &trans_info, -1);
882  if (!trans_info)
883  // selected row is a potential match (depth 2)
884  // instead of an imported transaction (depth 1)
885  return GNCImport_INVALID_ACTION;
886  return gnc_import_TransInfo_get_action (trans_info);
887 }
888 
889 static void
890 gnc_gen_trans_row_changed_cb (GtkTreeSelection *selection,
891  GNCImportMainMatcher *info)
892 {
893  GtkTreeModel *model;
894  GtkTreeIter iter;
895  GtkSelectionMode mode;
896 
897  ENTER("");
898  mode = gtk_tree_selection_get_mode (selection);
899  if (gtk_tree_selection_count_selected_rows (selection) >= 2)
900  {
901  // Unselect rows that should not be selectable
902  GList* list = gtk_tree_selection_get_selected_rows (selection, &model);
903  for ( ; list; list=list->next)
904  {
905  if (get_action_for_path (list->data, model) != GNCImport_ADD)
906  gtk_tree_selection_unselect_path (selection, list->data);
907  }
908  g_list_free_full (list, (GDestroyNotify)gtk_tree_path_free);
909  }
910 
911  switch (mode)
912  {
913  case GTK_SELECTION_MULTIPLE:
914  DEBUG("mode = GTK_SELECTION_MULTIPLE, no action");
915  break;
916  case GTK_SELECTION_NONE:
917  DEBUG("mode = GTK_SELECTION_NONE, no action");
918  break;
919  case GTK_SELECTION_BROWSE:
920  DEBUG("mode = GTK_SELECTION_BROWSE->default");
921  case GTK_SELECTION_SINGLE:
922  DEBUG("mode = GTK_SELECTION_SINGLE->default");
923  default:
924  DEBUG("mode = default unselect selected row");
925  if (gtk_tree_selection_get_selected (selection, &model, &iter))
926  {
927  gtk_tree_selection_unselect_iter (selection, &iter);
928  }
929  }
930  LEAVE("");
931 }
932 
933 static void
934 gnc_gen_trans_view_popup_menu (GtkTreeView *treeview,
935  GdkEvent *event,
936  GNCImportMainMatcher *info)
937 {
938  GtkWidget *menu, *menuitem;
939  GdkEventButton *event_button;
940 
941  ENTER ("");
942  menu = gtk_menu_new();
943  menuitem = gtk_menu_item_new_with_label (
944  _("Assign a transfer account to the selection."));
945  g_signal_connect (menuitem, "activate",
946  G_CALLBACK(
947  gnc_gen_trans_assign_transfer_account_to_selection_cb),
948  info);
949  DEBUG("Callback to assign destination account to selection connected");
950  gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem);
951  gtk_widget_show_all (menu);
952  event_button = (GdkEventButton *) event;
953  /* Note: event can be NULL here when called from view_onPopupMenu; */
954  gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent*)event);
955 
956  LEAVE ("");
957 }
958 
959 static gboolean
960 gnc_gen_trans_onButtonPressed_cb (GtkTreeView *treeview,
961  GdkEvent *event,
962  GNCImportMainMatcher *info)
963 {
964  GdkEventButton *event_button;
965  GtkTreeSelection *selection;
966  ENTER("");
967  g_return_val_if_fail (treeview != NULL, FALSE);
968  g_return_val_if_fail (event != NULL, FALSE);
969  /* handle single click with the right mouse button? */
970  if (event->type == GDK_BUTTON_PRESS)
971  {
972  event_button = (GdkEventButton *) event;
973  if (event_button->button == GDK_BUTTON_SECONDARY)
974  {
975  int count = 0;
976  DEBUG("Right mouseClick detected- popup the menu.");
977  // Only pop up the menu if there's more than 1 selected transaction,
978  // or the selected transaction is an ADD.
979  selection = gtk_tree_view_get_selection (treeview);
980  count = gtk_tree_selection_count_selected_rows (selection);
981  if (count > 1)
982  gnc_gen_trans_view_popup_menu (treeview, event, info);
983  else if (count > 0)
984  {
985  GList* selected;
986  GtkTreeModel *model;
987  selected = gtk_tree_selection_get_selected_rows (selection, &model);
988  if (get_action_for_path (selected->data, model) == GNCImport_ADD)
989  gnc_gen_trans_view_popup_menu (treeview, event, info);
990  g_list_free_full (selected, (GDestroyNotify)gtk_tree_path_free);
991  }
992  LEAVE("return TRUE");
993  return TRUE;
994  }
995  }
996  LEAVE("return FALSE");
997  return FALSE;
998 }
999 
1000 static gboolean
1001 gnc_gen_trans_onPopupMenu_cb (GtkTreeView *treeview,
1002  GNCImportMainMatcher *info)
1003 {
1004  GtkTreeSelection *selection;
1005  ENTER("onPopupMenu_cb");
1006  /* respond to Shift-F10 popup menu hotkey */
1007  selection = gtk_tree_view_get_selection (treeview);
1008  if (gtk_tree_selection_count_selected_rows (selection) > 0)
1009  {
1010  gnc_gen_trans_view_popup_menu (treeview, NULL, info);
1011  LEAVE ("TRUE");
1012  return TRUE;
1013  }
1014  LEAVE ("FALSE");
1015  return TRUE;
1016 }
1017 
1018 static GtkTreeViewColumn *
1019 add_text_column (GtkTreeView *view, const gchar *title, int col_num, gboolean ellipsize)
1020 {
1021  GtkCellRenderer *renderer;
1022  GtkTreeViewColumn *column;
1023 
1024  renderer = gtk_cell_renderer_text_new ();
1025  column = gtk_tree_view_column_new_with_attributes (title,
1026  renderer,
1027  "text", col_num,
1028  "background", DOWNLOADED_COL_COLOR,
1029  NULL);
1030 
1031  if (ellipsize)
1032  g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1033 
1034  // If date column, use the time64 value for the sorting.
1035  if (col_num == DOWNLOADED_COL_DATE_TXT)
1036  gtk_tree_view_column_set_sort_column_id(column, DOWNLOADED_COL_DATE_INT64);
1037  else if (col_num == DOWNLOADED_COL_AMOUNT) // If amount column, use double value
1038  {
1039  gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5); // right align amount column
1040  gtk_cell_renderer_set_padding (renderer, 5, 0); // add padding so its not close to description
1041  gtk_tree_view_column_set_sort_column_id (column, DOWNLOADED_COL_AMOUNT_DOUBLE);
1042  }
1043  else
1044  gtk_tree_view_column_set_sort_column_id (column, col_num);
1045 
1046  g_object_set (G_OBJECT(column),
1047  "reorderable", TRUE,
1048  "resizable", TRUE,
1049  NULL);
1050  gtk_tree_view_append_column (view, column);
1051  return column;
1052 }
1053 
1054 static GtkTreeViewColumn *
1055 add_toggle_column (GtkTreeView *view, const gchar *title, int col_num,
1056  GCallback cb_fn, gpointer cb_arg)
1057 {
1058  GtkCellRenderer *renderer;
1059  GtkTreeViewColumn *column;
1060 
1061  renderer = gtk_cell_renderer_toggle_new ();
1062  column = gtk_tree_view_column_new_with_attributes (title, renderer,
1063  "active", col_num,
1064  "cell-background", DOWNLOADED_COL_COLOR,
1065  "activatable", DOWNLOADED_COL_ENABLE,
1066  "visible", DOWNLOADED_COL_ENABLE,
1067  NULL);
1068  gtk_tree_view_column_set_sort_column_id (column, col_num);
1069  g_object_set (G_OBJECT(column), "reorderable", TRUE, NULL);
1070  g_signal_connect (renderer, "toggled", cb_fn, cb_arg);
1071  gtk_tree_view_append_column (view, column);
1072  return column;
1073 }
1074 
1075 static void
1076 gnc_gen_trans_init_view (GNCImportMainMatcher *info,
1077  gboolean show_account,
1078  gboolean show_update)
1079 {
1080  GtkTreeView *view;
1081  GtkTreeStore *store;
1082  GtkCellRenderer *renderer;
1083  GtkTreeViewColumn *column;
1084  GtkTreeSelection *selection;
1085 
1086  view = info->view;
1087  store = gtk_tree_store_new (NUM_DOWNLOADED_COLS, G_TYPE_STRING, G_TYPE_INT64,
1088  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE,
1089  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN,
1090  G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING,
1091  GDK_TYPE_PIXBUF, G_TYPE_POINTER, G_TYPE_STRING,
1092  G_TYPE_BOOLEAN);
1093  gtk_tree_view_set_model (view, GTK_TREE_MODEL(store));
1094  g_object_unref (store);
1095 
1096  /* prevent the rows being dragged to a different order */
1097  gtk_tree_view_set_reorderable (view, FALSE);
1098 
1099  /* Add the columns */
1100  add_text_column (view, _("Date"), DOWNLOADED_COL_DATE_TXT, FALSE);
1101  info->account_column = add_text_column (view, _("Account"), DOWNLOADED_COL_ACCOUNT, FALSE);
1102  gtk_tree_view_column_set_visible (info->account_column, show_account);
1103  add_text_column (view, _("Amount"), DOWNLOADED_COL_AMOUNT, FALSE);
1104  add_text_column (view, _("Description"), DOWNLOADED_COL_DESCRIPTION, FALSE);
1105  info->memo_column = add_text_column (view, _("Memo"), DOWNLOADED_COL_MEMO, TRUE);
1106  add_toggle_column (view, C_("Column header for 'Adding transaction'", "A"),
1107  DOWNLOADED_COL_ACTION_ADD,
1108  G_CALLBACK(gnc_gen_trans_add_toggled_cb), info);
1109  column = add_toggle_column (view, C_("Column header for 'Updating plus Clearing transaction'", "U+C"),
1110  DOWNLOADED_COL_ACTION_UPDATE,
1111  G_CALLBACK(gnc_gen_trans_update_toggled_cb), info);
1112  gtk_tree_view_column_set_visible (column, show_update);
1113  add_toggle_column (view, C_("Column header for 'Clearing transaction'", "C"),
1114  DOWNLOADED_COL_ACTION_CLEAR,
1115  G_CALLBACK(gnc_gen_trans_clear_toggled_cb), info);
1116 
1117  /* The last column has multiple renderers */
1118  renderer = gtk_cell_renderer_pixbuf_new ();
1119  g_object_set (renderer, "xalign", 0.0, NULL);
1120  column = gtk_tree_view_column_new_with_attributes (_("Info"), renderer,
1121  "pixbuf", DOWNLOADED_COL_ACTION_PIXBUF,
1122  "cell-background", DOWNLOADED_COL_COLOR,
1123  NULL);
1124 
1125  gtk_tree_view_append_column (info->view, column);
1126 
1127  column = add_text_column (view, _("Additional Comments"), DOWNLOADED_COL_ACTION_INFO, FALSE);
1128  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1129 
1130  /* default sort order */
1131  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
1132  DOWNLOADED_COL_DATE_INT64,
1133  GTK_SORT_ASCENDING);
1134  selection = gtk_tree_view_get_selection (info->view);
1135 
1136  g_object_set (info->view, "has-tooltip", TRUE, NULL);
1137 
1138  g_signal_connect (G_OBJECT(info->view), "query-tooltip",
1139  G_CALLBACK(query_tooltip_tree_view_cb), info);
1140  g_signal_connect (info->view, "row-activated",
1141  G_CALLBACK(gnc_gen_trans_row_activated_cb), info);
1142  g_signal_connect (selection, "changed",
1143  G_CALLBACK(gnc_gen_trans_row_changed_cb), info);
1144  g_signal_connect (view, "button-press-event",
1145  G_CALLBACK(gnc_gen_trans_onButtonPressed_cb), info);
1146  g_signal_connect (view, "popup-menu",
1147  G_CALLBACK(gnc_gen_trans_onPopupMenu_cb), info);
1148 
1149  info->acct_id_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
1150 }
1151 
1152 static void
1153 show_account_column_toggled_cb (GtkToggleButton *togglebutton,
1154  GNCImportMainMatcher *info)
1155 {
1156  gtk_tree_view_column_set_visible (info->account_column,
1157  gtk_toggle_button_get_active (togglebutton));
1158 }
1159 
1160 static void
1161 show_memo_column_toggled_cb (GtkToggleButton *togglebutton,
1162  GNCImportMainMatcher *info)
1163 {
1164  gtk_tree_view_column_set_visible (info->memo_column,
1165  gtk_toggle_button_get_active (togglebutton));
1166 }
1167 
1168 static void
1169 show_matched_info_toggled_cb (GtkToggleButton *togglebutton,
1170  GNCImportMainMatcher *info)
1171 {
1172  if (gtk_toggle_button_get_active (togglebutton))
1173  {
1174  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), TRUE);
1175  gtk_tree_view_expand_all (info->view);
1176  }
1177  else
1178  {
1179  gtk_tree_view_column_set_visible (info->account_column,
1180  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(info->show_account_column)));
1181  gtk_tree_view_collapse_all (info->view);
1182  }
1183 }
1184 
1185 static void
1186 gnc_gen_trans_common_setup (GNCImportMainMatcher *info,
1187  GtkWidget *parent,
1188  GtkBuilder *builder,
1189  const gchar* heading,
1190  gboolean all_from_same_account,
1191  gint match_date_hardlimit)
1192 {
1193  GtkStyleContext *stylectxt;
1194  GdkRGBA color;
1195  GtkWidget *heading_label, *button;
1196  gboolean show_update;
1197 
1198  info->pending_matches = gnc_import_PendingMatches_new ();
1199 
1200  /* Initialize user Settings. */
1201  info->user_settings = gnc_import_Settings_new ();
1202  gnc_import_Settings_set_match_date_hardlimit (info->user_settings, match_date_hardlimit);
1203 
1204  stylectxt = gtk_widget_get_style_context (GTK_WIDGET(parent));
1205  gtk_style_context_get_color (stylectxt, GTK_STATE_FLAG_NORMAL, &color);
1206  info->dark_theme = gnc_is_dark_theme (&color);
1207 
1208  /* Get the view */
1209  info->view = GTK_TREE_VIEW(gtk_builder_get_object (builder, "downloaded_view"));
1210  g_assert (info->view != NULL);
1211 
1212  info->show_account_column = GTK_WIDGET(gtk_builder_get_object (builder, "show_source_account_button"));
1213  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->show_account_column), all_from_same_account);
1214  g_signal_connect (G_OBJECT(info->show_account_column), "toggled",
1215  G_CALLBACK(show_account_column_toggled_cb), info);
1216 
1217  button = GTK_WIDGET(gtk_builder_get_object (builder, "show_memo_column_button"));
1218  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
1219  g_signal_connect (G_OBJECT(button), "toggled",
1220  G_CALLBACK(show_memo_column_toggled_cb), info);
1221 
1222  info->show_matched_info = GTK_WIDGET(gtk_builder_get_object (builder, "show_matched_info_button"));
1223  g_signal_connect (G_OBJECT(info->show_matched_info), "toggled",
1224  G_CALLBACK(show_matched_info_toggled_cb), info);
1225 
1226  // Create the checkbox, but do not show it by default.
1227  info->reconcile_after_close = GTK_WIDGET(gtk_builder_get_object (builder, "reconcile_after_close_button"));
1228 
1229  show_update = gnc_import_Settings_get_action_update_enabled (info->user_settings);
1230  gnc_gen_trans_init_view (info, all_from_same_account, show_update);
1231  heading_label = GTK_WIDGET(gtk_builder_get_object (builder, "heading_label"));
1232  g_assert (heading_label != NULL);
1233 
1234  if (heading)
1235  gtk_label_set_text (GTK_LABEL(heading_label), heading);
1236 
1237  info->transaction_processed_cb = NULL;
1238 
1239  /* Connect the signals */
1240  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, info);
1241 
1242  g_object_unref (G_OBJECT(builder));
1243 }
1244 
1245 
1246 GNCImportMainMatcher *
1247 gnc_gen_trans_list_new (GtkWidget *parent,
1248  const gchar* heading,
1249  gboolean all_from_same_account,
1250  gint match_date_hardlimit,
1251  gboolean show_all)
1252 {
1253  GNCImportMainMatcher *info;
1254  GtkBuilder *builder;
1255  GtkWidget *box, *pbox;
1256 
1257  info = g_new0 (GNCImportMainMatcher, 1);
1258 
1259  /* Initialize the GtkDialog. */
1260  builder = gtk_builder_new ();
1261  gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_matcher_dialog");
1262  gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_matcher_content");
1263 
1264  info->main_widget = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_dialog"));
1265  g_assert (info->main_widget != NULL);
1266 
1267  /* Pack the content into the dialog vbox */
1268  pbox = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_vbox"));
1269  box = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_content"));
1270  gtk_box_pack_start (GTK_BOX(pbox), box, TRUE, TRUE, 0);
1271 
1272  /* setup the common parts */
1273  gnc_gen_trans_common_setup (info, parent, builder, heading,
1274  all_from_same_account, match_date_hardlimit);
1275 
1276  if (parent)
1277  gtk_window_set_transient_for (GTK_WINDOW(info->main_widget), GTK_WINDOW(parent));
1278 
1279  gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(info->main_widget), GTK_WINDOW(parent));
1280 
1281  if (show_all)
1282  gtk_widget_show_all (GTK_WIDGET(info->main_widget));
1283 
1284  // Register this UI, it needs to be closed when the session is closed.
1285  info->id = gnc_register_gui_component (IMPORT_MAIN_MATCHER_CM_CLASS,
1286  NULL, /* no refresh handler */
1287  (GNCComponentCloseHandler)gnc_gen_trans_list_delete,
1288  info);
1289  // This ensure this dialog is closed when the session is closed.
1290  gnc_gui_component_set_session (info->id, gnc_get_current_session());
1291  return info;
1292 }
1293 
1294 /*****************************************************************
1295  * Assistant routines Start *
1296  *****************************************************************/
1297 
1298 GNCImportMainMatcher *
1299 gnc_gen_trans_assist_new (GtkWidget *parent,
1300  GtkWidget *assistant_page,
1301  const gchar* heading,
1302  gboolean all_from_same_account,
1303  gint match_date_hardlimit)
1304 {
1305  GNCImportMainMatcher *info;
1306  GtkBuilder *builder;
1307  GtkWidget *box;
1308 
1309  info = g_new0 (GNCImportMainMatcher, 1);
1310  info->main_widget = GTK_WIDGET(parent);
1311 
1312  /* load the interface */
1313  builder = gtk_builder_new ();
1314  gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_matcher_content");
1315 
1316  /* Pack content into Assistant page widget */
1317  box = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_matcher_content"));
1318  g_assert (box != NULL);
1319  gtk_box_pack_start (GTK_BOX(assistant_page), box, TRUE, TRUE, 6);
1320 
1321  /* setup the common parts */
1322  gnc_gen_trans_common_setup (info, parent, builder, heading,
1323  all_from_same_account, match_date_hardlimit);
1324 
1325  return info;
1326 }
1327 
1328 void
1329 gnc_gen_trans_assist_start (GNCImportMainMatcher *info)
1330 {
1331  on_matcher_ok_clicked (NULL, info);
1332 }
1333 
1334 /*****************************************************************
1335  * Assistant routines End *
1336  *****************************************************************/
1337 
1338 void
1339 gnc_gen_trans_list_add_tp_cb (GNCImportMainMatcher *info,
1340  GNCTransactionProcessedCB trans_processed_cb,
1341  gpointer user_data)
1342 {
1343  info->user_data = user_data;
1344  info->transaction_processed_cb = trans_processed_cb;
1345 }
1346 
1347 gboolean
1348 gnc_gen_trans_list_run (GNCImportMainMatcher *info)
1349 {
1350  gboolean result;
1351 
1352  /* DEBUG("Begin"); */
1353  result = gtk_dialog_run (GTK_DIALOG (info->main_widget));
1354  /* DEBUG("Result was %d", result); */
1355 
1356  /* No destroying here since the dialog was already destroyed through
1357  the ok_clicked handlers. */
1358 
1359  return result;
1360 }
1361 
1362 static const gchar*
1363 get_required_color (const gchar *class_name)
1364 {
1365  GdkRGBA color;
1366  static gchar *strbuf = NULL;
1367  GtkWidget *label = gtk_label_new ("Color");
1368  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(label));
1369  gtk_style_context_add_class (context, class_name);
1370  gnc_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &color);
1371  if (strbuf)
1372  g_free (strbuf);
1373  strbuf = gdk_rgba_to_string (&color);
1374  return strbuf;
1375 }
1376 
1377 static void
1378 remove_child_row (GtkTreeModel *model, GtkTreeIter *iter)
1379 {
1380  if (gtk_tree_model_iter_has_child (model, iter))
1381  {
1382  GtkTreeIter child;
1383  gtk_tree_model_iter_nth_child (model, &child, iter, 0);
1384  gtk_tree_store_remove (GTK_TREE_STORE(model), &child);
1385  }
1386 }
1387 
1388 static void
1389 update_child_row (GNCImportMatchInfo *sel_match, GtkTreeModel *model, GtkTreeIter *iter)
1390 {
1391  GtkTreeStore *store;
1392  GtkTreeIter child;
1393  gchar *text = qof_print_date (xaccTransGetDate (sel_match->trans));
1394  const gchar *ro_text;
1395 
1396  const gchar *desc = xaccTransGetDescription (sel_match->trans);
1397  const gchar *memo = xaccSplitGetMemo (sel_match->split);
1398 
1399  store = GTK_TREE_STORE(model);
1400 
1401  if (!gtk_tree_model_iter_has_child (model, iter))
1402  gtk_tree_store_append (GTK_TREE_STORE(model), &child, iter);
1403  else
1404  gtk_tree_model_iter_nth_child (model, &child, iter, 0);
1405 
1406  gtk_tree_store_set (store, &child, DOWNLOADED_COL_DATE_TXT, text, -1);
1407 
1408  if (xaccTransCountSplits (sel_match->trans) == 2)
1409  gtk_tree_store_set (store, &child, DOWNLOADED_COL_ACCOUNT, xaccAccountGetName (
1410  xaccSplitGetAccount (xaccSplitGetOtherSplit (sel_match->split))), -1);
1411  else
1412  gtk_tree_store_set (store, &child, DOWNLOADED_COL_ACCOUNT, _("-- Split Transaction --"), -1);
1413 
1414  ro_text = xaccPrintAmount (xaccSplitGetAmount (sel_match->split),
1415  gnc_split_amount_print_info (sel_match->split, TRUE));
1416 
1417  gtk_tree_store_set (store, &child, DOWNLOADED_COL_AMOUNT, ro_text, -1);
1418  gtk_tree_store_set (store, &child, DOWNLOADED_COL_MEMO, memo, -1);
1419  gtk_tree_store_set (store, &child, DOWNLOADED_COL_DESCRIPTION, desc, -1);
1420 
1421  gtk_tree_store_set (store, &child, DOWNLOADED_COL_ENABLE, FALSE, -1);
1422  g_free (text);
1423 }
1424 
1425 static gchar *
1426 get_peer_acct_names (Split *split)
1427 {
1428  GList *names = NULL, *accounts_seen = NULL;
1429  gchar *retval, *name;
1430  for (GList *n = xaccTransGetSplitList (xaccSplitGetParent (split)); n; n = n->next)
1431  {
1432  Account *account = xaccSplitGetAccount (n->data);
1433  if ((n->data == split) ||
1434  (xaccAccountGetType (account) == ACCT_TYPE_TRADING) ||
1435  (g_list_find (accounts_seen, account)))
1436  continue;
1437  name = gnc_account_get_full_name (account);
1438  names = g_list_prepend (names, g_strdup_printf ("\"%s\"", name));
1439  accounts_seen = g_list_prepend (accounts_seen, account);
1440  g_free (name);
1441  }
1442  retval = gnc_g_list_stringjoin (names, ", ");
1443  g_list_free_full (names, g_free);
1444  g_list_free (accounts_seen);
1445  return retval;
1446 }
1447 
1448 static void
1449 refresh_model_row (GNCImportMainMatcher *gui,
1450  GtkTreeModel *model,
1451  GtkTreeIter *iter,
1452  GNCImportTransInfo *info)
1453 {
1454  GtkTreeStore *store;
1455  GtkTreeSelection *selection;
1456  gchar *tmp, *imbalance, *text;
1457  const gchar *ro_text, *color = NULL;
1458  gchar *int_required_class, *int_prob_required_class, *int_not_required_class;
1459  gchar *class_extension = NULL;
1460  gboolean show_pixbuf = TRUE;
1461  Split *split;
1462  time64 date;
1463  gnc_numeric amount;
1464  g_assert (gui);
1465  g_assert (model);
1466  g_assert (info);
1467  /*DEBUG("Begin");*/
1468 
1469  store = GTK_TREE_STORE(model);
1470  gtk_tree_store_set (store, iter, DOWNLOADED_COL_DATA, info, -1);
1471 
1472  if (gui->dark_theme == TRUE)
1473  class_extension = "-dark";
1474 
1475  int_required_class = g_strconcat (CSS_INT_REQUIRED_CLASS, class_extension, NULL);
1476  int_prob_required_class = g_strconcat (CSS_INT_PROB_REQUIRED_CLASS, class_extension, NULL);
1477  int_not_required_class = g_strconcat (CSS_INT_NOT_REQUIRED_CLASS, class_extension, NULL);
1478 
1479  /* This controls the visibility of the toggle cells */
1480  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ENABLE, TRUE, -1);
1481 
1482  /*Account:*/
1483  split = gnc_import_TransInfo_get_fsplit (info);
1484  g_assert (split); // Must not be NULL
1485  ro_text = xaccAccountGetName (xaccSplitGetAccount (split));
1486  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACCOUNT, ro_text, -1);
1487 
1488  /*Date*/
1490  text = qof_print_date (date);
1491  gtk_tree_store_set (store, iter, DOWNLOADED_COL_DATE_TXT, text, -1);
1492  gtk_tree_store_set (store, iter, DOWNLOADED_COL_DATE_INT64, date, -1);
1493  g_free(text);
1494 
1495  /*Amount*/
1496  amount = xaccSplitGetAmount (split);
1497  ro_text = xaccPrintAmount (amount, gnc_split_amount_print_info (split, TRUE));
1498  gtk_tree_store_set (store, iter, DOWNLOADED_COL_AMOUNT, ro_text, -1);
1499  gtk_tree_store_set (store, iter, DOWNLOADED_COL_AMOUNT_DOUBLE, gnc_numeric_to_double (amount), -1);
1500 
1501  /*Description*/
1503  gtk_tree_store_set (store, iter, DOWNLOADED_COL_DESCRIPTION, ro_text, -1);
1504 
1505  /*Memo*/
1506  ro_text = xaccSplitGetMemo (split);
1507  gtk_tree_store_set (store, iter, DOWNLOADED_COL_MEMO, ro_text, -1);
1508 
1509  /*Actions*/
1510 
1511  /* Action information */
1512  ro_text = text = NULL;
1513  switch (gnc_import_TransInfo_get_action (info))
1514  {
1515  case GNCImport_ADD:
1516  if (gnc_import_TransInfo_is_balanced (info) == TRUE)
1517  {
1518  ro_text = _("New, already balanced");
1519  color = get_required_color (int_not_required_class);
1520  }
1521  else
1522  {
1523  /* Assume that importers won't create transactions in two or more
1524  currencies so we can use xaccTransGetImbalanceValue */
1525  imbalance =
1526  g_strdup
1530  gnc_commodity_print_info
1532  TRUE)));
1533  if (gnc_import_TransInfo_get_destacc (info) != NULL)
1534  {
1535  color = get_required_color (int_not_required_class);
1539  == TRUE)
1540  {
1541  text =
1542  /* Translators: %1$s is the amount to be
1543  transferred. %2$s is the destination account. */
1544  g_strdup_printf (_("New, transfer %s to (manual) \"%s\""),
1545  imbalance, tmp);
1546  }
1547  else
1548  {
1549  text =
1550  /* Translators: %1$s is the amount to be
1551  transferred. %2$s is the destination account. */
1552  g_strdup_printf (_("New, transfer %s to (auto) \"%s\""),
1553  imbalance, tmp);
1554  }
1555  g_free (tmp);
1556 
1557  }
1558  else
1559  {
1560  color = get_required_color (int_prob_required_class);
1561  text =
1562  /* Translators: %s is the amount to be transferred. */
1563  g_strdup_printf (_("New, UNBALANCED (need acct to transfer %s)!"),
1564  imbalance);
1565  }
1566  remove_child_row (model, iter);
1567 
1568  g_free (imbalance);
1569  }
1570  break;
1571  case GNCImport_CLEAR:
1572  {
1574 
1575  if (sel_match)
1576  {
1577  gchar *full_names = get_peer_acct_names (sel_match->split);
1578  color = get_required_color (int_not_required_class);
1580  {
1581  text = g_strdup_printf (_("Reconcile (manual) match to %s"),
1582  full_names);
1583  }
1584  else
1585  {
1586  text = g_strdup_printf (_("Reconcile (auto) match to %s"),
1587  full_names);
1588  }
1589  g_free (full_names);
1590  update_child_row (sel_match, model, iter);
1591  }
1592  else
1593  {
1594  color = get_required_color (int_required_class);
1595  ro_text = _("Match missing!");
1596  show_pixbuf = FALSE;
1597  remove_child_row (model, iter);
1598  }
1599  }
1600  break;
1601  case GNCImport_UPDATE:
1602  {
1604 
1605  if (sel_match)
1606  {
1607  gchar *full_names = get_peer_acct_names (sel_match->split);
1608  color = get_required_color (int_not_required_class);
1610  {
1611  text = g_strdup_printf (_("Update and reconcile (manual) match to %s"),
1612  full_names);
1613  }
1614  else
1615  {
1616  text = g_strdup_printf (_("Update and reconcile (auto) match to %s"),
1617  full_names);
1618  }
1619  g_free (full_names);
1620  update_child_row (sel_match, model, iter);
1621  }
1622  else
1623  {
1624  color = get_required_color (int_required_class);
1625  ro_text = _("Match missing!");
1626  show_pixbuf = FALSE;
1627  remove_child_row (model, iter);
1628  }
1629  }
1630  break;
1631  case GNCImport_SKIP:
1632  color = get_required_color (int_required_class);
1633  ro_text = _("Do not import (no action selected)");
1634  show_pixbuf = FALSE;
1635  remove_child_row (model, iter);
1636  break;
1637  default:
1638  color = "white";
1639  ro_text = "WRITEME, this is an unknown action";
1640  show_pixbuf = FALSE;
1641  break;
1642  }
1643 
1644  gtk_tree_store_set (store, iter,
1645  DOWNLOADED_COL_COLOR, color,
1646  DOWNLOADED_COL_ACTION_INFO, ro_text ? ro_text : text,
1647  -1);
1648  if (text)
1649  g_free (text);
1650 
1651  g_free (int_required_class);
1652  g_free (int_prob_required_class);
1653  g_free (int_not_required_class);
1654 
1655  /* Set the pixmaps */
1656  gtk_tree_store_set (store, iter,
1657  DOWNLOADED_COL_ACTION_ADD,
1658  gnc_import_TransInfo_get_action (info) == GNCImport_ADD,
1659  -1);
1660  if (gnc_import_TransInfo_get_action (info) == GNCImport_SKIP)
1661  {
1662  /*If skipping the row, there is no best match's confidence pixmap*/
1663  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACTION_PIXBUF, NULL, -1);
1664  }
1665 
1666  gtk_tree_store_set (store, iter,
1667  DOWNLOADED_COL_ACTION_CLEAR,
1668  gnc_import_TransInfo_get_action (info) == GNCImport_CLEAR,
1669  -1);
1670  if (gnc_import_TransInfo_get_action (info) == GNCImport_CLEAR)
1671  {
1672  /*Show the best match's confidence pixmap in the info column*/
1673  if (show_pixbuf)
1674  gtk_tree_store_set (store, iter,
1675  DOWNLOADED_COL_ACTION_PIXBUF,
1678  gui->user_settings,
1679  GTK_WIDGET(gui->view)),
1680  -1);
1681  else
1682  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACTION_PIXBUF, NULL, -1);
1683  }
1684 
1685  gtk_tree_store_set (store, iter,
1686  DOWNLOADED_COL_ACTION_UPDATE,
1687  gnc_import_TransInfo_get_action (info) == GNCImport_UPDATE,
1688  -1);
1689  if (gnc_import_TransInfo_get_action (info) == GNCImport_UPDATE)
1690  {
1691  /*Show the best match's confidence pixmap in the info column*/
1692  if (show_pixbuf)
1693  gtk_tree_store_set (store, iter,
1694  DOWNLOADED_COL_ACTION_PIXBUF,
1697  gui->user_settings,
1698  GTK_WIDGET(gui->view)),
1699  -1);
1700  else
1701  gtk_tree_store_set (store, iter, DOWNLOADED_COL_ACTION_PIXBUF, NULL, -1);
1702  }
1703 
1704  // show child row if 'show matched info' is toggled
1705  if (gtk_tree_model_iter_has_child (model, iter))
1706  {
1707  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(gui->show_matched_info)))
1708  {
1709  GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1710 
1711  gtk_tree_view_column_set_visible (gui->account_column, TRUE);
1712  gtk_tree_view_column_set_visible (gui->memo_column, TRUE);
1713 
1714  gtk_tree_view_expand_row (GTK_TREE_VIEW(gui->view), path, TRUE);
1715  gtk_tree_path_free (path);
1716  }
1717  }
1718  selection = gtk_tree_view_get_selection (gui->view);
1719  gtk_tree_selection_unselect_all (selection);
1720 }
1721 
1722 void
1723 gnc_gen_trans_list_add_trans (GNCImportMainMatcher *gui, Transaction *trans)
1724 {
1725  Account* acc = NULL;
1726  Split* split = NULL;
1727  int i=0;
1728 
1729  split = xaccTransGetSplit (trans, 0);
1730  acc = xaccSplitGetAccount (split);
1731  defer_bal_computation (gui, acc);
1732 
1734  return;
1735 }/* end gnc_import_add_trans() */
1736 
1737 void
1739  gboolean reconcile_after_close,
1740  gboolean active)
1741 {
1742  gtk_widget_set_visible (info->reconcile_after_close, reconcile_after_close);
1743  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (info->reconcile_after_close), active);
1744 }
1745 
1746 GtkWidget*
1748 {
1749  return info->reconcile_after_close;
1750 }
1751 
1752 void
1753 gnc_gen_trans_list_add_trans_with_ref_id (GNCImportMainMatcher *gui, Transaction *trans, guint32 ref_id)
1754 {
1755  GNCImportTransInfo * transaction_info = NULL;
1756  GtkTreeModel *model;
1757  GtkTreeIter iter;
1758  g_assert (gui);
1759  g_assert (trans);
1760 
1761  if (gnc_import_exists_online_id (trans, gui->acct_id_hash))
1762  return;
1763  else
1764  {
1765  transaction_info = gnc_import_TransInfo_new (trans, NULL);
1766  gnc_import_TransInfo_set_ref_id (transaction_info, ref_id);
1767  // It's much faster to gather the imported transactions into a GSList than directly into the
1768  // treeview.
1769  gui->temp_trans_list = g_slist_prepend (gui->temp_trans_list, transaction_info);
1770  }
1771  return;
1772 }
1773 
1774 /* Query the accounts used by the imported transactions to find a list of
1775  * candidate matching transactions.
1776  */
1777 static GList*
1778 query_imported_transaction_accounts (GNCImportMainMatcher *gui)
1779 {
1780  static const int secs_per_day = 86400;
1781  GList* query_results = NULL;
1782  GList* all_accounts = NULL;
1783  GList* retval = NULL;
1784  gint match_date_limit =
1785  gnc_import_Settings_get_match_date_hardlimit (gui->user_settings);
1786  time64 min_time=G_MAXINT64, max_time=0;
1787  time64 match_timelimit = match_date_limit * secs_per_day;
1788  Query *query = qof_query_create_for (GNC_ID_SPLIT);
1789 
1790  /* Go through all imported transactions, gather the list of accounts, and
1791  * min/max date range.
1792  */
1793  for (GSList* txn = gui->temp_trans_list; txn != NULL;
1794  txn = g_slist_next (txn))
1795  {
1796  GNCImportTransInfo* txn_info = txn->data;
1797  Account *txn_account =
1799  time64 txn_time =
1801  all_accounts = g_list_prepend (all_accounts, txn_account);
1802  min_time = MIN(min_time, txn_time);
1803  max_time = MAX(max_time, txn_time);
1804  }
1805 
1806  // Make a query to find splits with the right accounts and dates.
1807  qof_query_set_book (query, gnc_get_current_book ());
1808  xaccQueryAddAccountMatch (query, all_accounts,
1809  QOF_GUID_MATCH_ANY, QOF_QUERY_AND);
1810  xaccQueryAddDateMatchTT (query,
1811  TRUE, min_time - match_timelimit,
1812  TRUE, max_time + match_timelimit,
1813  QOF_QUERY_AND);
1814  query_results = qof_query_run (query);
1815  g_list_free (all_accounts);
1816  retval = g_list_copy (query_results);
1817  qof_query_destroy (query);
1818 
1819  return retval;
1820 }
1821 
1822 /* Create a hash by account of all splits that could match one of the imported
1823  * transactions based on their account and date and organized per account.
1824  */
1825 static GHashTable*
1826 create_hash_of_potential_matches (GList *candidate_txns,
1827  GHashTable *account_hash)
1828 {
1829  for (GList* candidate = candidate_txns; candidate != NULL;
1830  candidate = g_list_next (candidate))
1831  {
1832  Account* split_account;
1833  GSList* split_list;
1834  if (gnc_import_split_has_online_id (candidate->data))
1835  continue;
1836  split_account = xaccSplitGetAccount (candidate->data);
1837  /* g_hash_table_steal_extended would do the two calls in one shot but is
1838  * not available until GLib 2.58.
1839  */
1840  split_list = g_hash_table_lookup (account_hash, split_account);
1841  g_hash_table_steal (account_hash, split_account);
1842  split_list = g_slist_prepend (split_list, candidate->data);
1843  g_hash_table_insert (account_hash, split_account, split_list);
1844  }
1845  return account_hash;
1846 }
1847 
1848 typedef struct _match_struct
1849 {
1850  GNCImportTransInfo* transaction_info;
1851  gint display_threshold;
1852  gint date_threshold;
1853  gint date_not_threshold;
1854  double fuzzy_amount;
1855 } match_struct;
1856 
1857 static void
1858 match_helper (Split* data, match_struct* s)
1859 {
1860  split_find_match (s->transaction_info, data,
1861  s->display_threshold,
1862  s->date_threshold,
1863  s->date_not_threshold,
1864  s->fuzzy_amount);
1865 }
1866 
1867 /* Iterate through the imported transactions selecting matches from the
1868  * potential match lists in the account hash and update the matcher with the
1869  * results.
1870  */
1871 
1872 static void
1873 perform_matching (GNCImportMainMatcher *gui, GHashTable *account_hash)
1874 {
1875  GtkTreeModel* model = gtk_tree_view_get_model (gui->view);
1876  gint display_threshold =
1877  gnc_import_Settings_get_display_threshold (gui->user_settings);
1878  gint date_threshold =
1879  gnc_import_Settings_get_date_threshold (gui->user_settings);
1880  gint date_not_threshold =
1881  gnc_import_Settings_get_date_not_threshold (gui->user_settings);
1882  double fuzzy_amount =
1883  gnc_import_Settings_get_fuzzy_amount (gui->user_settings);
1884 
1885  for (GSList *imported_txn = gui->temp_trans_list; imported_txn !=NULL;
1886  imported_txn = g_slist_next (imported_txn))
1887  {
1888  GtkTreeIter iter;
1889  GNCImportMatchInfo *selected_match;
1890  gboolean match_selected_manually;
1891  GNCImportTransInfo* txn_info = imported_txn->data;
1892  Account *importaccount = xaccSplitGetAccount (gnc_import_TransInfo_get_fsplit (txn_info));
1893  match_struct s = {txn_info, display_threshold, date_threshold, date_not_threshold, fuzzy_amount};
1894 
1895  g_slist_foreach (g_hash_table_lookup (account_hash, importaccount),
1896  (GFunc) match_helper, &s);
1897 
1898  // Sort the matches, select the best match, and set the action.
1899  gnc_import_TransInfo_init_matches (txn_info, gui->user_settings);
1900 
1901  selected_match = gnc_import_TransInfo_get_selected_match (txn_info);
1902  match_selected_manually =
1904 
1905  if (selected_match)
1906  gnc_import_PendingMatches_add_match (gui->pending_matches,
1907  selected_match,
1908  match_selected_manually);
1909 
1910  gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
1911  refresh_model_row (gui, model, &iter, txn_info);
1912  }
1913 }
1914 
1915 void
1916 gnc_gen_trans_list_create_matches (GNCImportMainMatcher *gui)
1917 {
1918  GHashTable* account_hash =
1919  g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
1920  (GDestroyNotify)g_slist_free);
1921  GList *candidate_txns;
1922  g_assert (gui);
1923  candidate_txns = query_imported_transaction_accounts (gui);
1924 
1925  create_hash_of_potential_matches (candidate_txns, account_hash);
1926  perform_matching (gui, account_hash);
1927 
1928  g_list_free (candidate_txns);
1929  g_hash_table_destroy (account_hash);
1930  return;
1931 }
1932 
1933 GtkWidget *
1934 gnc_gen_trans_list_widget (GNCImportMainMatcher *info)
1935 {
1936  g_assert (info);
1937  return info->main_widget;
1938 }
1939 
1940 gboolean
1941 query_tooltip_tree_view_cb (GtkWidget *widget, gint x, gint y,
1942  gboolean keyboard_tip,
1943  GtkTooltip *tooltip,
1944  gpointer user_data)
1945 {
1946  GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
1947  GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
1948  GtkTreePath *path = NULL;
1949  GtkTreeViewColumn *column = NULL;
1950  GtkTreeIter iter;
1951  gboolean show_tooltip = FALSE;
1952 
1953  gtk_tree_view_convert_widget_to_bin_window_coords (tree_view, x, y, &x, &y);
1954  if (keyboard_tip || !gtk_tree_view_get_path_at_pos (tree_view, x, y, &path,
1955  &column, NULL, NULL))
1956  {
1957  gtk_tree_path_free (path);
1958  return FALSE;
1959  }
1960 
1961  // Get the iter pointing to our current column
1962  if (gtk_tree_model_get_iter(model, &iter, path) && column)
1963  {
1964  const gchar *tooltip_text = NULL;
1965 
1966  // Select text based on column
1967  gint num_col = gtk_tree_view_column_get_sort_column_id (column);
1968  switch (num_col)
1969  {
1970  case DOWNLOADED_COL_DESCRIPTION:
1971  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DESCRIPTION, &tooltip_text, -1);
1972  break;
1973  case DOWNLOADED_COL_MEMO:
1974  gtk_tree_model_get (model, &iter, DOWNLOADED_COL_MEMO, &tooltip_text, -1);
1975  break;
1976  default:
1977  break;
1978  }
1979 
1980  // Did we select any text? If yes, display the tooltip
1981  if (tooltip_text && *tooltip_text)
1982  {
1983  show_tooltip = TRUE;
1984  gtk_tooltip_set_text (tooltip, tooltip_text);
1985  gtk_tree_view_set_tooltip_cell (tree_view, tooltip, path, column, NULL);
1986  }
1987  }
1988  // Clean up the object
1989  gtk_tree_path_free (path);
1990  return show_tooltip;
1991 }
1992 
void gnc_gen_trans_list_show_reconcile_after_close_button(GNCImportMainMatcher *info, gboolean reconcile_after_close, gboolean active)
Show and set the reconcile after close check button.
GNCImportMainMatcher * gnc_gen_trans_list_new(GtkWidget *parent, const gchar *heading, gboolean all_from_same_account, gint match_date_hardlimit, gboolean show_all)
Create a new generic transaction dialog window and return it.
GNCImportTransInfo * gnc_import_TransInfo_new(Transaction *trans, GncImportMatchMap *matchmap)
Create a new object of GNCImportTransInfo here.
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.
gchar * gnc_g_list_stringjoin(GList *list_of_strings, const gchar *sep)
Return a string joining a GList whose elements are gchar* strings.
void gnc_gen_trans_list_show_all(GNCImportMainMatcher *info)
Shows widgets.
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...
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:3236
gtk helper routines.
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value...
gint gnc_import_Settings_get_display_threshold(GNCImportSettings *settings)
Return the selected threshold.
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.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
Definition: gnc-ui-util.c:1854
globally unique ID User API
Split * gnc_import_TransInfo_get_fsplit(const GNCImportTransInfo *info)
Returns the first split of the transaction of this TransInfo.
Account * gnc_import_select_account(GtkWidget *parent, const gchar *account_online_id_value, gboolean auto_create, 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.
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.
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.
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.
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 the originating account of trans_info.
gboolean gnc_gen_trans_list_run(GNCImportMainMatcher *info)
Run this dialog and return only after the user pressed Ok, Cancel, or closed the window.
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:158
int xaccTransCountSplits(const Transaction *trans)
Returns the number of splits in this transaction.
Definition: Transaction.c:2396
gchar * 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 ...
Definition: gnc-ui-util.c:579
double gnc_numeric_to_double(gnc_numeric in)
Convert numeric to floating-point value.
char * qof_print_date(time64 secs)
Convenience; calls through to qof_print_date_dmy_buff().
Definition: gnc-date.cpp:617
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:3265
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.
The transaction match picker dialog interface.
GtkWidget * gnc_gen_trans_list_widget(GNCImportMainMatcher *info)
Returns the widget of this dialog.
gnc_numeric xaccTransGetImbalanceValue(const Transaction *trans)
The xaccTransGetImbalanceValue() method returns the total value of the transaction.
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 qof_query_set_book(QofQuery *query, QofBook *book)
Set the book to be searched.
gboolean gnc_gen_trans_list_empty(GNCImportMainMatcher *info)
Checks whether there are no transactions to match.
gboolean gnc_import_process_trans_item(GncImportMatchMap *matchmap, GNCImportTransInfo *trans_info)
/brief – Processes one match according to its selected action.
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:2262
gboolean gnc_import_exists_online_id(Transaction *trans, GHashTable *acct_id_hash)
Checks whether the given transaction&#39;s online_id already exists in its parent account.
double gnc_import_Settings_get_fuzzy_amount(GNCImportSettings *settings)
Return the allowed amount range for fuzzy amount matching.
const char * xaccTransGetDescription(const Transaction *trans)
Gets the transaction Description.
gboolean gnc_account_get_defer_bal_computation(Account *acc)
Get the account&#39;s flag for deferred balance computation.
Definition: Account.cpp:1910
All type declarations for the whole Gnucash engine.
GLib helper routines.
#define xaccTransGetGUID(X)
Definition: Transaction.h:785
void gnc_import_TransInfo_set_match_list(GNCImportTransInfo *info, GList *match_list)
Assigns the list of possible matches.
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.
GNCImportMainMatcher * gnc_gen_trans_assist_new(GtkWidget *parent, GtkWidget *assistant_page, const gchar *heading, gboolean all_from_same_account, gint match_date_hardlimit)
Add the Transaction matcher to an existing page of an assistant.
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...
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
Definition: gmock-Split.cpp:53
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
Definition: Transaction.c:1366
void gnc_gen_trans_list_delete(GNCImportMainMatcher *info)
Deletes the given object.
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
GList * gnc_import_TransInfo_get_match_list(const GNCImportTransInfo *info)
Returns the stored list of possible matches.
gint64 time64
Many systems, including Microsoft Windows and BSD-derived Unixes like Darwin, are retaining the int-3...
Definition: gnc-date.h:93
GNCImportMatchInfo * gnc_import_TransInfo_get_selected_match(const GNCImportTransInfo *info)
Returns the currently selected match in this TransInfo.
void gnc_account_set_defer_bal_computation(Account *acc, gboolean defer)
Set the defer balance flag.
Definition: Account.cpp:1897
const char * xaccAccountGetName(const Account *acc)
Get the account&#39;s name.
Definition: Account.cpp:3258
Not a type.
Definition: Account.h:108
The type used to store guids in C.
Definition: guid.h:75
SplitList * xaccTransGetSplitList(const Transaction *trans)
The xaccTransGetSplitList() method returns a GList of the splits in a transaction.
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.
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...