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