1 /********************************************************************\
2  * This program is free software; you can redistribute it and/or    *
3  * modify it under the terms of the GNU General Public License as   *
4  * published by the Free Software Foundation; either version 2 of   *
5  * the License, or (at your option) any later version.              *
6  *                                                                  *
7  * This program is distributed in the hope that it will be useful,  *
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
10  * GNU General Public License for more details.                     *
11  *                                                                  *
12  * You should have received a copy of the GNU General Public License*
13  * along with this program; if not, contact:                        *
14  *                                                                  *
15  * Free Software Foundation           Voice:  +1-617-542-5942       *
16  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
17  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
18 \********************************************************************/
19 /** @addtogroup Import_Export
20     @{ */
21 /** @internal
22 @file import-match-picker.c
23    @brief The transaction match picker dialog
24    implementation
25    @author Copyright (C) 2002 Benoit Grégoire
26    @author Copyright (c) 2006 David Hampton <hampton@employees.org>
27 */
28 
29 #include <config.h>
30 
31 #include <gtk/gtk.h>
32 #include <glib/gi18n.h>
33 
34 #include "import-backend.h"
35 #include "import-match-picker.h"
36 #include "import-pending-matches.h"
37 
38 #include "qof.h"
39 #include "gnc-ui-util.h"
40 #include "dialog-utils.h"
41 #include "gnc-prefs.h"
42 
43 /********************************************************************\
44  *   Constants   *
45 \********************************************************************/
46 
47 #define GNC_PREFS_GROUP "dialogs.import.generic.match-picker"
48 #define GNC_PREF_DISPLAY_RECONCILED "display-reconciled"
49 
50 enum downloaded_cols
51 {
52     DOWNLOADED_COL_ACCOUNT = 0,
53     DOWNLOADED_COL_DATE,
54     DOWNLOADED_COL_AMOUNT,
55     DOWNLOADED_COL_DESCRIPTION,
56     DOWNLOADED_COL_MEMO,
57     DOWNLOADED_COL_BALANCED,
58     DOWNLOADED_COL_INFO_PTR,
59     NUM_DOWNLOADED_COLS
60 };
61 
62 enum matcher_cols
63 {
64     MATCHER_COL_CONFIDENCE = 0,
65     MATCHER_COL_CONFIDENCE_PIXBUF,
66     MATCHER_COL_DATE,
67     MATCHER_COL_AMOUNT,
68     MATCHER_COL_DESCRIPTION,
69     MATCHER_COL_MEMO,
70     MATCHER_COL_RECONCILED,
71     MATCHER_COL_PENDING,
72     MATCHER_COL_INFO_PTR,
73     NUM_MATCHER_COLS
74 };
75 
76 /* Needs to be commented in again if any DEBUG() macro is used here. */
77 /*static short module = MOD_IMPORT;*/
78 
79 /********************************************************************\
80  *   Constants, should idealy be defined a user preference dialog    *
81 \********************************************************************/
82 
83 static const int SHOW_NUMERIC_SCORE = FALSE;
84 
85 /********************************************************************\
86  *               Structures passed between the functions             *
87 \********************************************************************/
88 
89 struct _transpickerdialog
90 {
91     GtkWidget * transaction_matcher;
92     GtkTreeView * downloaded_view;
93     GtkTreeView * match_view;
94     GtkCheckButton * reconciled_chk;
95     GNCImportSettings * user_settings;
96     struct _transactioninfo * selected_trans_info;
97     GNCImportMatchInfo * selected_match_info;
98     GNCImportPendingMatches * pending_matches;
99 };
100 
101 
102 
103 static void
downloaded_transaction_append(GNCImportMatchPicker * matcher,GNCImportTransInfo * transaction_info)104 downloaded_transaction_append(GNCImportMatchPicker * matcher,
105                               GNCImportTransInfo * transaction_info)
106 {
107     GtkListStore *store;
108     GtkTreeIter iter;
109     GtkTreeSelection *selection;
110     Transaction *trans;
111     Split *split;
112     gchar *text;
113     const gchar *ro_text;
114     gboolean found = FALSE;
115     GNCImportTransInfo *local_info;
116 
117     g_assert(matcher);
118     g_assert(transaction_info);
119 
120     /*DEBUG("Begin");*/
121 
122     /* Has the transaction already been added? */
123     store = GTK_LIST_STORE(gtk_tree_view_get_model(matcher->downloaded_view));
124     if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter))
125     {
126         do
127         {
128             gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
129                                DOWNLOADED_COL_INFO_PTR, &local_info,
130                                -1);
131             if (local_info == transaction_info)
132             {
133                 found = TRUE;
134                 break;
135             }
136         }
137         while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter));
138     }
139     if (!found)
140         gtk_list_store_append(store, &iter);
141 
142     split = gnc_import_TransInfo_get_fsplit(transaction_info);
143     trans = gnc_import_TransInfo_get_trans(transaction_info);
144 
145     /*Account*/
146     ro_text = xaccAccountGetName(xaccSplitGetAccount(split));
147     gtk_list_store_set(store, &iter, DOWNLOADED_COL_ACCOUNT, ro_text, -1);
148 
149     /*Date*/
150     text = qof_print_date(xaccTransGetDate(trans));
151     gtk_list_store_set(store, &iter, DOWNLOADED_COL_DATE, text, -1);
152     g_free(text);
153 
154     /*Amount*/
155     ro_text = xaccPrintAmount(xaccSplitGetAmount(split),
156                               gnc_split_amount_print_info(split, TRUE));
157     gtk_list_store_set(store, &iter, DOWNLOADED_COL_AMOUNT, ro_text, -1);
158 
159     /*Description*/
160     ro_text = xaccTransGetDescription(trans);
161     gtk_list_store_set(store, &iter, DOWNLOADED_COL_DESCRIPTION, ro_text, -1);
162 
163     /*Memo*/
164     ro_text = xaccSplitGetMemo(split);
165     gtk_list_store_set(store, &iter, DOWNLOADED_COL_MEMO, ro_text, -1);
166 
167     /*Imbalance*/
168     /* Assume that the importer won't create a transaction that involves two or more
169        currencies and no non-currency commodity.  In that case can use the simpler
170        value imbalance check. */
171     ro_text = xaccPrintAmount(xaccTransGetImbalanceValue(trans),
172                               gnc_commodity_print_info (xaccTransGetCurrency (trans), TRUE));
173     gtk_list_store_set(store, &iter, DOWNLOADED_COL_BALANCED, ro_text, -1);
174 
175     gtk_list_store_set(store, &iter, DOWNLOADED_COL_INFO_PTR,
176                        transaction_info, -1);
177 
178     selection = gtk_tree_view_get_selection(matcher->downloaded_view);
179     gtk_tree_selection_select_iter(selection, &iter);
180 }
181 
182 static void
match_update_match_model(GNCImportMatchPicker * matcher)183 match_update_match_model (GNCImportMatchPicker *matcher)
184 {
185     GNCImportMatchInfo * match_info;
186     GtkTreeIter iter;
187     gboolean show_reconciled;
188     gchar reconciled;
189     GtkListStore *match_store;
190     GList * list_element;
191     gchar *text;
192     const gchar *ro_text;
193     GNCImportPendingMatchType pending_match_type;
194 
195     show_reconciled = gtk_toggle_button_get_active
196                         (GTK_TOGGLE_BUTTON(matcher->reconciled_chk));
197 
198     /* Now rewrite the "match" model based on that trans. */
199     match_store = GTK_LIST_STORE(gtk_tree_view_get_model(matcher->match_view));
200     gtk_list_store_clear(match_store);
201     list_element = g_list_first (gnc_import_TransInfo_get_match_list
202                                  (matcher->selected_trans_info));
203     while (list_element != NULL)
204     {
205         match_info = list_element->data;
206 
207         /* Skip this match if reconciled and we're not showing those */
208         reconciled = xaccSplitGetReconcile
209                         (gnc_import_MatchInfo_get_split(match_info));
210         if (show_reconciled == FALSE && reconciled != NREC)
211         {
212             list_element = g_list_next (list_element);
213             continue;
214         }
215 
216         gtk_list_store_append(match_store, &iter);
217 
218         /* Print fields. */
219 
220         /* Probability */
221         text = g_strdup_printf("%d", gnc_import_MatchInfo_get_probability (match_info));
222         gtk_list_store_set(match_store, &iter, MATCHER_COL_CONFIDENCE, text, -1);
223         g_free(text);
224 
225         /* Date */
226         text =
227             qof_print_date
228             ( xaccTransGetDate
229               ( xaccSplitGetParent
230                 ( gnc_import_MatchInfo_get_split(match_info) ) ));
231         gtk_list_store_set(match_store, &iter, MATCHER_COL_DATE, text, -1);
232         g_free(text);
233 
234         /* Amount */
235         ro_text =
236             xaccPrintAmount( xaccSplitGetAmount ( gnc_import_MatchInfo_get_split(match_info)  ),
237                              gnc_split_amount_print_info(gnc_import_MatchInfo_get_split(match_info), TRUE)
238                            );
239         gtk_list_store_set(match_store, &iter, MATCHER_COL_AMOUNT, ro_text, -1);
240 
241         /*Description*/
242         ro_text = xaccTransGetDescription
243                   ( xaccSplitGetParent( gnc_import_MatchInfo_get_split(match_info)) );
244         gtk_list_store_set(match_store, &iter, MATCHER_COL_DESCRIPTION, ro_text, -1);
245 
246         /*Split memo*/
247         ro_text = xaccSplitGetMemo(gnc_import_MatchInfo_get_split(match_info) );
248         gtk_list_store_set(match_store, &iter, MATCHER_COL_MEMO, ro_text, -1);
249 
250         /*Reconciled*/
251         ro_text = gnc_get_reconcile_str (reconciled);
252         gtk_list_store_set (match_store, &iter, MATCHER_COL_RECONCILED, ro_text,
253                             -1);
254 
255         /*Pending Action*/
256         pending_match_type = gnc_import_PendingMatches_get_match_type
257                                  (matcher->pending_matches, match_info);
258 
259         /* If it has a pending match mark it cleared, otherwise leave alone */
260         if (pending_match_type == GNCImportPending_MANUAL ||
261             pending_match_type == GNCImportPending_AUTO)
262         {
263             ro_text = gnc_get_reconcile_str (CREC);
264             text = g_strdup_printf("%s (%s)",
265                                    ro_text,
266                                    gnc_import_PendingMatches_get_type_str
267                                        (pending_match_type));
268 
269             gtk_list_store_set (match_store, &iter, MATCHER_COL_PENDING, text, -1);
270             g_free (text);
271         }
272 
273         gtk_list_store_set(match_store, &iter, MATCHER_COL_INFO_PTR, match_info, -1);
274         if (gnc_import_MatchInfo_get_probability(match_info) != 0)
275         {
276                 gtk_list_store_set(match_store, &iter,
277                                    MATCHER_COL_CONFIDENCE_PIXBUF,
278                                    gen_probability_pixbuf(gnc_import_MatchInfo_get_probability(match_info),
279                                            matcher->user_settings,
280                                            GTK_WIDGET(matcher->match_view)),
281                                    -1);
282         }
283 
284         if (match_info ==
285                 gnc_import_TransInfo_get_selected_match (matcher->selected_trans_info))
286         {
287             GtkTreeSelection *selection;
288 
289             selection = gtk_tree_view_get_selection(matcher->match_view);
290             gtk_tree_selection_select_iter(selection, &iter);
291         }
292 
293         list_element = g_list_next(list_element);
294     }
295 }
296 
297 /********************************************************************\
298  *                                                                   *
299  *                       GUI callbacks                               *
300  *                                                                   *
301 \********************************************************************/
302 
303 static void
downloaded_transaction_changed_cb(GtkTreeSelection * selection,GNCImportMatchPicker * matcher)304 downloaded_transaction_changed_cb (GtkTreeSelection *selection,
305                                    GNCImportMatchPicker *matcher)
306 {
307     GtkTreeModel *dl_model;
308     GtkTreeIter iter;
309     /*DEBUG("row: %d%s%d",row,", column: ",column);*/
310 
311     /* Get the transaction info from the "downloaded" model.  */
312     if (!gtk_tree_selection_get_selected(selection, &dl_model, &iter))
313     {
314         matcher->selected_trans_info = NULL;
315         return;
316     }
317     gtk_tree_model_get(dl_model, &iter,
318                        DOWNLOADED_COL_INFO_PTR, &matcher->selected_trans_info,
319                        -1);
320 
321     match_update_match_model (matcher);
322 }
323 
324 static void
match_show_reconciled_changed_cb(GtkCheckButton * checkbox,GNCImportMatchPicker * matcher)325 match_show_reconciled_changed_cb (GtkCheckButton* checkbox,
326                                   GNCImportMatchPicker *matcher)
327 {
328     match_update_match_model (matcher);
329 }
330 
331 static void
match_transaction_changed_cb(GtkTreeSelection * selection,GNCImportMatchPicker * matcher)332 match_transaction_changed_cb (GtkTreeSelection *selection,
333                               GNCImportMatchPicker *matcher)
334 {
335     GtkTreeModel *model;
336     GtkTreeIter iter;
337 
338     if (!gtk_tree_selection_get_selected (selection, &model, &iter))
339     {
340         matcher->selected_match_info = NULL;
341         return;
342     }
343 
344     gtk_tree_model_get(model, &iter,
345                        MATCHER_COL_INFO_PTR, &matcher->selected_match_info,
346                        -1);
347 }
348 
349 static void
match_transaction_row_activated_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * column,GNCImportMatchPicker * matcher)350 match_transaction_row_activated_cb (GtkTreeView *view, GtkTreePath *path,
351                                     GtkTreeViewColumn *column,
352                                     GNCImportMatchPicker *matcher)
353 {
354     g_return_if_fail (matcher && matcher->transaction_matcher);
355 
356     gtk_dialog_response (GTK_DIALOG (matcher->transaction_matcher),
357                          GTK_RESPONSE_OK);
358 }
359 
360 static void
add_column(GtkTreeView * view,const gchar * title,int col_num)361 add_column(GtkTreeView *view, const gchar *title, int col_num)
362 {
363     GtkCellRenderer *renderer;
364     GtkTreeViewColumn *column;
365 
366     renderer = gtk_cell_renderer_text_new();
367     column = gtk_tree_view_column_new_with_attributes(title, renderer,
368              "text", col_num,
369              NULL);
370     gtk_tree_view_append_column(view, column);
371     g_object_set(G_OBJECT(column),
372                  "reorderable", TRUE,
373                  "resizable", TRUE,
374                  NULL);
375 }
376 
377 static void
gnc_import_match_picker_init_downloaded_view(GNCImportMatchPicker * matcher)378 gnc_import_match_picker_init_downloaded_view (GNCImportMatchPicker * matcher)
379 {
380     GtkTreeView *view;
381     GtkListStore *store;
382     GtkTreeSelection *selection;
383 
384     view = matcher->downloaded_view;
385     store = gtk_list_store_new(NUM_DOWNLOADED_COLS,
386                                G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
387                                G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
388                                G_TYPE_POINTER);
389     gtk_tree_view_set_model(view, GTK_TREE_MODEL(store));
390     g_object_unref(store);
391 
392     add_column(view, _("Account"),     DOWNLOADED_COL_ACCOUNT);
393     add_column(view, _("Date"),        DOWNLOADED_COL_DATE);
394     add_column(view, _("Amount"),      DOWNLOADED_COL_AMOUNT);
395     add_column(view, _("Description"), DOWNLOADED_COL_DESCRIPTION);
396     add_column(view, _("Memo"),        DOWNLOADED_COL_MEMO);
397     add_column(view, _("Balanced"),    DOWNLOADED_COL_BALANCED);
398 
399     selection = gtk_tree_view_get_selection(view);
400     g_signal_connect(selection, "changed",
401                      G_CALLBACK(downloaded_transaction_changed_cb), matcher);
402 }
403 
404 static void
gnc_import_match_picker_init_match_view(GNCImportMatchPicker * matcher)405 gnc_import_match_picker_init_match_view (GNCImportMatchPicker * matcher)
406 {
407     GtkTreeView *view;
408     GtkListStore *store;
409     GtkCellRenderer *renderer;
410     GtkTreeViewColumn *column;
411     GtkTreeSelection *selection;
412 
413     view = matcher->match_view;
414     store = gtk_list_store_new(NUM_MATCHER_COLS,
415                                G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING,
416                                G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
417                                G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
418     gtk_tree_view_set_model(view, GTK_TREE_MODEL(store));
419     g_object_unref(store);
420 
421     renderer = gtk_cell_renderer_pixbuf_new();
422     g_object_set(renderer, "xalign", 0.0, NULL);
423     column = gtk_tree_view_column_new_with_attributes(_("Confidence"), renderer,
424              "pixbuf", MATCHER_COL_CONFIDENCE_PIXBUF,
425              NULL);
426     renderer = gtk_cell_renderer_text_new();
427     gtk_tree_view_column_pack_start(column, renderer, TRUE);
428     gtk_tree_view_column_set_attributes(column, renderer,
429                                         "text", MATCHER_COL_CONFIDENCE,
430                                         NULL);
431     gtk_tree_view_append_column(view, column);
432 
433     add_column(view, _("Date"),           MATCHER_COL_DATE);
434     add_column(view, _("Amount"),         MATCHER_COL_AMOUNT);
435     add_column(view, _("Description"),    MATCHER_COL_DESCRIPTION);
436     add_column(view, _("Memo"),           MATCHER_COL_MEMO);
437     add_column(view, _("Reconciled"),     MATCHER_COL_RECONCILED);
438     add_column(view, _("Pending Action"), MATCHER_COL_PENDING);
439 
440     selection = gtk_tree_view_get_selection(view);
441     g_signal_connect(selection, "changed",
442                      G_CALLBACK(match_transaction_changed_cb), matcher);
443     g_signal_connect(view, "row-activated",
444                      G_CALLBACK(match_transaction_row_activated_cb), matcher);
445 }
446 
447 /********************************************************************\
448  * init_match_picker_gui()
449  * -- GUI initialization for the Match_Picker Dialog
450 \********************************************************************/
451 static void
init_match_picker_gui(GtkWidget * parent,GNCImportMatchPicker * matcher)452 init_match_picker_gui(GtkWidget *parent, GNCImportMatchPicker * matcher)
453 {
454     GtkBuilder *builder;
455 
456     /* DEBUG("Begin..."); */
457 
458     /* Initialize user Settings. */
459     matcher->user_settings = gnc_import_Settings_new ();
460 
461     /* load the interface */
462     builder = gtk_builder_new();
463     gnc_builder_add_from_file (builder, "dialog-import.glade", "match_picker_dialog");
464     g_return_if_fail (builder != NULL);
465 
466     matcher->transaction_matcher = GTK_WIDGET(gtk_builder_get_object (builder, "match_picker_dialog"));
467     matcher->downloaded_view = (GtkTreeView *)GTK_WIDGET(gtk_builder_get_object (builder, "download_view"));
468     matcher->match_view = (GtkTreeView *)GTK_WIDGET(gtk_builder_get_object (builder, "matched_view"));
469     matcher->reconciled_chk = (GtkCheckButton *)GTK_WIDGET(gtk_builder_get_object(builder, "hide_reconciled_check1"));
470 
471     gtk_window_set_transient_for (GTK_WINDOW (matcher->transaction_matcher), GTK_WINDOW(parent));
472 
473     gnc_prefs_bind (GNC_PREFS_GROUP, GNC_PREF_DISPLAY_RECONCILED,
474                     matcher->reconciled_chk, "active");
475 
476     gnc_import_match_picker_init_downloaded_view(matcher);
477     gnc_import_match_picker_init_match_view(matcher);
478 
479     /* DEBUG("User prefs:%s%d%s%d%s%d%s%d%s%d",
480        " action_replace_enabled:",matcher->action_replace_enabled,
481        ", action_skip_enabled:",matcher->action_skip_enabled,
482        ", clear_threshold:",matcher->clear_threshold,
483        ", add_threshold:",matcher->add_threshold,
484        ", display_threshold:",matcher->display_threshold); */
485 
486     /* now that we've bound the checkbox appropriately we can hook up the
487      * change callback */
488     g_signal_connect ((GObject *)matcher->reconciled_chk, "toggled",
489                        G_CALLBACK(match_show_reconciled_changed_cb), matcher);
490 
491     /* now that we've bound the checkbox appropriately we can hook up the change callback */
492     g_signal_connect((GObject *)matcher->reconciled_chk, "toggled", G_CALLBACK(match_show_reconciled_changed_cb), matcher);
493 
494     gnc_restore_window_size(GNC_PREFS_GROUP,
495                             GTK_WINDOW (matcher->transaction_matcher), GTK_WINDOW(parent));
496     gtk_widget_show(matcher->transaction_matcher);
497 
498     g_object_unref(G_OBJECT(builder));
499 
500 }/* end init_match_picker_gui */
501 
502 /**
503  * Run a match_picker dialog so that the selected-MatchInfo in the
504  * given trans_info is updated accordingly. This functions will only
505  * return after the user clicked Ok, Cancel, or Window-Close.
506  */
507 void
gnc_import_match_picker_run_and_close(GtkWidget * parent,GNCImportTransInfo * transaction_info,GNCImportPendingMatches * pending_matches)508 gnc_import_match_picker_run_and_close (GtkWidget *parent, GNCImportTransInfo *transaction_info,
509                                        GNCImportPendingMatches *pending_matches)
510 {
511     GNCImportMatchPicker *matcher;
512     gint response;
513     GNCImportMatchInfo *old;
514     gboolean old_selected_manually;
515     g_assert (transaction_info);
516 
517     /* Create a new match_picker, even though it's stored in a
518        transmatcher struct :-) */
519     matcher = g_new0(GNCImportMatchPicker, 1);
520 
521     matcher->pending_matches = pending_matches;
522 
523     /* DEBUG("Init match_picker"); */
524     init_match_picker_gui(parent, matcher);
525 
526     /* Append this single transaction to the view and select it */
527     downloaded_transaction_append(matcher, transaction_info);
528 
529     old = gnc_import_TransInfo_get_selected_match (transaction_info);
530     old_selected_manually =
531         gnc_import_TransInfo_get_match_selected_manually (transaction_info);
532 
533     /* Let this dialog run and close. */
534     /*DEBUG("Right before run and close");*/
535     gtk_window_set_modal(GTK_WINDOW(matcher->transaction_matcher), TRUE);
536     response = gtk_dialog_run (GTK_DIALOG (matcher->transaction_matcher));
537 
538     gnc_save_window_size(GNC_PREFS_GROUP,
539                          GTK_WINDOW (matcher->transaction_matcher));
540     gtk_widget_destroy (matcher->transaction_matcher);
541     /*DEBUG("Right after run and close");*/
542     /* DEBUG("Response was %d.", response); */
543     if (response == GTK_RESPONSE_OK && matcher->selected_match_info != old)
544     {
545         /* OK was pressed */
546         gnc_import_TransInfo_set_selected_match_info (transaction_info,
547                 matcher->selected_match_info,
548                 TRUE);
549 
550         gnc_import_PendingMatches_remove_match (pending_matches,
551                                                 old,
552                                                 old_selected_manually);
553         gnc_import_PendingMatches_add_match (pending_matches,
554                                              matcher->selected_match_info,
555                                              TRUE);
556     }
557 }
558 
559 /** @} */
560