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