1 /*
2  * assistant-ab-initial.c -- Initialise the AqBanking wizard
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of
7  * the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, contact:
16  *
17  * Free Software Foundation           Voice:  +1-617-542-5942
18  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
19  * Boston, MA  02110-1301,  USA       gnu@gnu.org
20  */
21 
22 /**
23  * @internal
24  * @file assistant-ab-initial.c
25  * @brief AqBanking setup functionality
26  * @author Copyright (C) 2002 Christian Stimming <stimming@tuhh.de>
27  * @author Copyright (C) 2006 David Hampton <hampton@employees.org>
28  * @author Copyright (C) 2008 Andreas Koehler <andi5.py@gmx.net>
29  * @author Copyright (C) 2011 Robert Fewell
30  * @author Copyright (C) 2020 Peter Zimmerer <pkzw@web.de>
31  */
32 
33 #include <config.h>
34 
35 #include <platform.h>
36 #if PLATFORM(WINDOWS)
37 #include <windows.h>
38 #endif
39 
40 #include "gnc-ab-utils.h" /* For version macros */
41 
42 #include <aqbanking/banking.h>
43 #ifdef AQBANKING6
44 #include <aqbanking/types/account_spec.h>
45 #include <gwenhywfar/gui.h>
46 #endif
47 #include <glib.h>
48 #include <glib/gi18n.h>
49 #include <glib/gstdio.h>
50 #include <gdk/gdkkeysyms.h>
51 #ifdef HAVE_SYS_WAIT_H
52 #    include <sys/wait.h>
53 #endif
54 #include <fcntl.h>
55 #include <unistd.h>
56 
57 #include "dialog-utils.h"
58 #include "assistant-ab-initial.h"
59 #include "gnc-ab-kvp.h"
60 #include "gnc-ab-utils.h"
61 #include "gnc-component-manager.h"
62 #include "gnc-glib-utils.h"
63 #include "gnc-ui.h"
64 #include "gnc-ui-util.h"
65 #include "gnc-session.h"
66 #include "import-account-matcher.h"
67 #include "import-utilities.h"
68 #ifndef AQBANKING6
69 # include <aqbanking/dlg_setup.h>
70 #endif
71 /* This static indicates the debugging module that this .o belongs to.  */
72 static QofLogModule log_module = GNC_MOD_ASSISTANT;
73 
74 #define GNC_PREFS_GROUP "dialogs.ab-initial"
75 #define ASSISTANT_AB_INITIAL_CM_CLASS "assistant-ab-initial"
76 
77 typedef struct _ABInitialInfo ABInitialInfo;
78 typedef struct _DeferredInfo DeferredInfo;
79 typedef struct _AccCbData AccCbData;
80 typedef struct _RevLookupData RevLookupData;
81 
82 void aai_on_prepare (GtkAssistant  *assistant, GtkWidget *page,
83                      gpointer user_data);
84 
85 void aai_on_finish (GtkAssistant *gtkassistant, gpointer user_data);
86 void aai_on_cancel (GtkAssistant *assistant, gpointer user_data);
87 void aai_destroy_cb(GtkWidget *object, gpointer user_data);
88 
89 gboolean aai_key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
90 
91 void aai_wizard_page_prepare (GtkAssistant *assistant, gpointer user_data);
92 void aai_wizard_button_clicked_cb(GtkButton *button, gpointer user_data);
93 void aai_match_delete_button_clicked_cb(GtkButton *button, gpointer user_data);
94 
95 #ifdef AQBANKING6
96 static guint aai_ab_account_hash(gconstpointer v);
97 static gboolean aai_ab_account_equal(gconstpointer v1, gconstpointer v2);
98 #endif
99 void aai_match_page_prepare (GtkAssistant *assistant, gpointer user_data);
100 
101 static gboolean banking_has_accounts(AB_BANKING *banking);
102 static void hash_from_kvp_acc_cb(Account *gnc_acc, gpointer user_data);
103 static ABInitialInfo *single_info = NULL;
104 static gchar *ab_account_longname(const GNC_AB_ACCOUNT_SPEC *ab_acc);
105 static GNC_AB_ACCOUNT_SPEC *update_account_list_acc_cb(GNC_AB_ACCOUNT_SPEC *ab_acc, gpointer user_data);
106 static void update_account_list(ABInitialInfo *info);
107 static gboolean find_gnc_acc_cb(gpointer key, gpointer value, gpointer user_data);
108 static gboolean clear_line_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data);
109 static void account_list_clicked_cb (GtkTreeView *view, GtkTreePath *path,
110                                      GtkTreeViewColumn  *col, gpointer user_data);
111 static void delete_account_match(ABInitialInfo *info, RevLookupData *data);
112 static void delete_selected_match_cb(gpointer data, gpointer user_data);
113 static void insert_acc_into_revhash_cb(gpointer ab_acc, gpointer gnc_acc, gpointer revhash);
114 static void remove_acc_from_revhash_cb(gpointer ab_acc, gpointer gnc_acc, gpointer revhash);
115 static void clear_kvp_acc_cb(gpointer key, gpointer value, gpointer user_data);
116 static void save_kvp_acc_cb(gpointer key, gpointer value, gpointer user_data);
117 static void aai_close_handler(gpointer user_data);
118 
119 struct _ABInitialInfo
120 {
121     GtkWidget *window;
122     GtkWidget *assistant;
123 
124     /* account match page */
125     gboolean match_page_prepared;
126     GtkTreeView *account_view;
127     GtkListStore *account_store;
128 
129     /* managed by child_exit_cb */
130     DeferredInfo *deferred_info;
131 
132     /* AqBanking stuff */
133     AB_BANKING *api;
134     /* AB_ACCOUNT* -> Account* -- DO NOT DELETE THE KEYS! */
135     GHashTable *gnc_hash;
136     /* Reverse hash table for lookup of matched GnuCash accounts */
137     GHashTable *gnc_revhash;
138 };
139 
140 struct _DeferredInfo
141 {
142     ABInitialInfo *initial_info;
143     gchar *wizard_path;
144     gboolean qt_probably_unavailable;
145 };
146 
147 struct _AccCbData
148 {
149     AB_BANKING *api;
150     GHashTable *hash;
151 };
152 
153 struct _RevLookupData
154 {
155     Account *gnc_acc;
156     GNC_AB_ACCOUNT_SPEC *ab_acc;
157 };
158 
159 enum account_list_cols
160 {
161     ACCOUNT_LIST_COL_INDEX = 0,
162     ACCOUNT_LIST_COL_AB_NAME,
163     ACCOUNT_LIST_COL_AB_ACCT,
164     ACCOUNT_LIST_COL_GNC_NAME,
165     ACCOUNT_LIST_COL_CHECKED,
166     NUM_ACCOUNT_LIST_COLS
167 };
168 
169 gboolean
aai_key_press_event_cb(GtkWidget * widget,GdkEventKey * event,gpointer user_data)170 aai_key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
171 {
172     if (event->keyval == GDK_KEY_Escape)
173     {
174         gtk_widget_destroy(widget);
175         return TRUE;
176     }
177     else
178     {
179         return FALSE;
180     }
181 }
182 
183 void
aai_on_cancel(GtkAssistant * gtkassistant,gpointer user_data)184 aai_on_cancel (GtkAssistant *gtkassistant, gpointer user_data)
185 {
186     ABInitialInfo *info = user_data;
187 
188     gtk_widget_destroy(info->window);
189 }
190 
191 void
aai_destroy_cb(GtkWidget * object,gpointer user_data)192 aai_destroy_cb(GtkWidget *object, gpointer user_data)
193 {
194     ABInitialInfo *info = user_data;
195     g_return_if_fail (single_info && info == single_info);
196 
197     gnc_unregister_gui_component_by_data(ASSISTANT_AB_INITIAL_CM_CLASS, info);
198 
199     if (info->deferred_info)
200     {
201         PINFO("Online Banking assistant is being closed but the wizard is still "
202                   "running.  Inoring.");
203 
204         /* Tell child_exit_cb() that there is no assistant anymore */
205         info->deferred_info->initial_info = NULL;
206     }
207 
208     if (info->gnc_hash)
209     {
210 #ifndef AQBANKING6
211         AB_Banking_OnlineFini(info->api);
212 #endif
213         g_hash_table_destroy(info->gnc_hash);
214         info->gnc_hash = NULL;
215     }
216 
217     if (info->gnc_revhash)
218     {
219         g_hash_table_destroy(info->gnc_revhash);
220         info->gnc_revhash = NULL;
221     }
222 
223     if (info->api)
224     {
225         gnc_AB_BANKING_delete(info->api);
226         info->api = NULL;
227     }
228 
229     gtk_widget_destroy(info->window);
230     info->window = NULL;
231 
232     g_free(info);
233     single_info = NULL;
234 }
235 
236 void
aai_wizard_page_prepare(GtkAssistant * assistant,gpointer user_data)237 aai_wizard_page_prepare (GtkAssistant *assistant, gpointer user_data)
238 {
239     ABInitialInfo *info = user_data;
240     gint num = gtk_assistant_get_current_page (assistant);
241     GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
242 
243     g_return_if_fail(info->api);
244 
245     /* Enable the Assistant Buttons if we accounts */
246     if (banking_has_accounts(info->api))
247         gtk_assistant_set_page_complete (assistant, page, TRUE);
248     else
249         gtk_assistant_set_page_complete (assistant, page, FALSE);
250 }
251 
252 void
aai_wizard_button_clicked_cb(GtkButton * button,gpointer user_data)253 aai_wizard_button_clicked_cb(GtkButton *button, gpointer user_data)
254 {
255     ABInitialInfo *info = user_data;
256     gint num = gtk_assistant_get_current_page (GTK_ASSISTANT(info->window));
257     GtkWidget *page = gtk_assistant_get_nth_page (GTK_ASSISTANT(info->window), num);
258 
259     AB_BANKING *banking = info->api;
260     g_return_if_fail(banking);
261 
262     ENTER("user_data: %p", user_data);
263 
264     if (info->deferred_info)
265     {
266         LEAVE("Wizard is still running");
267         return;
268     }
269 
270     {
271 #ifdef AQBANKING6
272         GWEN_DIALOG *dlg = AB_Banking_CreateSetupDialog(banking);
273 #else
274         GWEN_DIALOG *dlg = AB_SetupDialog_new(banking);
275 
276         if (AB_Banking_OnlineInit(banking) != 0)
277         {
278             PERR("Got error on AB_Banking_OnlineInit!");
279         }
280 #endif
281         if (!dlg)
282         {
283             PERR("Could not lookup Setup Dialog of aqbanking!");
284         }
285         else
286         {
287             int rv = GWEN_Gui_ExecDialog(dlg, 0);
288             if (rv <= 0)
289             {
290                 /* Dialog was aborted/rejected */
291                 PERR("Setup Dialog of aqbanking aborted/rejected, code %d", rv);
292             }
293             GWEN_Dialog_free(dlg);
294         }
295 #ifndef AQBANKING6
296         if (AB_Banking_OnlineFini(banking) != 0)
297         {
298             PERR("Got error on AB_Banking_OnlineFini!");
299         }
300 #endif
301     }
302 
303     /* Enable the Assistant Buttons if we accounts */
304     if (banking_has_accounts(info->api))
305         gtk_assistant_set_page_complete (GTK_ASSISTANT(info->window), page, TRUE);
306     else
307         gtk_assistant_set_page_complete (GTK_ASSISTANT(info->window), page, FALSE);
308 
309     LEAVE(" ");
310 }
311 
delete_account_match(ABInitialInfo * info,RevLookupData * data)312 static void delete_account_match(ABInitialInfo *info, RevLookupData *data)
313 {
314     g_return_if_fail(info && info->gnc_hash &&
315         info->account_view && data && data->ab_acc);
316 
317     g_hash_table_remove(info->gnc_hash, data->ab_acc);
318     gtk_tree_model_foreach(
319         GTK_TREE_MODEL(info->account_store),
320         (GtkTreeModelForeachFunc) clear_line_cb,
321         data);
322 }
323 
324 static void
delete_selected_match_cb(gpointer data,gpointer user_data)325 delete_selected_match_cb(gpointer data, gpointer user_data)
326 {
327     GNC_AB_ACCOUNT_SPEC *ab_acc = NULL;
328     GtkTreeIter iter;
329     GtkTreeModel *model = NULL;
330     RevLookupData revLookupData = {NULL, NULL};
331 
332     GtkTreePath *path = (GtkTreePath *) data;
333     ABInitialInfo *info = (ABInitialInfo *) user_data;
334     g_return_if_fail(path && info && info->account_view);
335 
336     model = gtk_tree_view_get_model(info->account_view);
337     g_return_if_fail(model);
338 
339     if (gtk_tree_model_get_iter(model, &iter, path))
340     {
341         gtk_tree_model_get(model, &iter, ACCOUNT_LIST_COL_AB_ACCT, &revLookupData.ab_acc, -1);
342         if (revLookupData.ab_acc)
343             delete_account_match(info, &revLookupData);
344     }
345 }
346 
347 void
aai_match_delete_button_clicked_cb(GtkButton * button,gpointer user_data)348 aai_match_delete_button_clicked_cb(GtkButton *button, gpointer user_data)
349 {
350     GList *selected_matches = NULL;
351     GtkTreeSelection *selection = NULL;
352     ABInitialInfo *info = (ABInitialInfo *) user_data;
353 
354     g_return_if_fail(info && info->api && info->account_view && info->gnc_hash);
355 
356     PINFO("Selected account matches are deleted");
357 
358     selection = gtk_tree_view_get_selection (info->account_view);
359     if (selection)
360     {
361         selected_matches = gtk_tree_selection_get_selected_rows (selection, NULL);
362         if (selected_matches)
363         {
364             g_list_foreach (selected_matches, delete_selected_match_cb, info);
365             g_list_free_full (
366                 selected_matches,
367                 (GDestroyNotify) gtk_tree_path_free);
368         }
369     }
370 }
371 
372 #ifdef AQBANKING6
373 static guint
aai_ab_account_hash(gconstpointer v)374 aai_ab_account_hash (gconstpointer v)
375 {
376 	if (v == NULL)
377 		return 0;
378 	else
379 		/* Use the account unique id as hash value */
380 		return AB_AccountSpec_GetUniqueId((const GNC_AB_ACCOUNT_SPEC *) v);
381 }
382 
383 static gboolean
aai_ab_account_equal(gconstpointer v1,gconstpointer v2)384 aai_ab_account_equal (gconstpointer v1, gconstpointer v2)
385 {
386 	if (v1 == NULL || v2 == NULL)
387 		return v1 == v2;
388 	else
389 	{
390 		/* Use the account unique id to check for equality */
391 		uint32_t uid1 = AB_AccountSpec_GetUniqueId((const GNC_AB_ACCOUNT_SPEC *) v1);
392 		uint32_t uid2 = AB_AccountSpec_GetUniqueId((const GNC_AB_ACCOUNT_SPEC *) v2);
393 		return uid1 == uid2;
394 	}
395 }
396 #endif
397 
398 static void
insert_acc_into_revhash_cb(gpointer ab_acc,gpointer gnc_acc,gpointer revhash)399 insert_acc_into_revhash_cb(gpointer ab_acc, gpointer gnc_acc, gpointer revhash)
400 {
401     g_return_if_fail(revhash && gnc_acc && ab_acc);
402     g_hash_table_insert((GHashTable *) revhash, gnc_acc, ab_acc);
403 }
404 
405 static void
remove_acc_from_revhash_cb(gpointer ab_acc,gpointer gnc_acc,gpointer revhash)406 remove_acc_from_revhash_cb(gpointer ab_acc, gpointer gnc_acc, gpointer revhash)
407 {
408     g_return_if_fail(revhash && gnc_acc);
409     g_hash_table_remove((GHashTable *) revhash, gnc_acc);
410 }
411 
412 void
aai_match_page_prepare(GtkAssistant * assistant,gpointer user_data)413 aai_match_page_prepare (GtkAssistant *assistant, gpointer user_data)
414 {
415     ABInitialInfo *info = user_data;
416     gint num = gtk_assistant_get_current_page (assistant);
417     GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
418 
419     Account *root;
420     AccCbData data;
421 
422     g_return_if_fail(info && info->api);
423 
424     /* Do not run this twice */
425     if (!info->match_page_prepared)
426     {
427 #ifndef AQBANKING6
428         /* Load aqbanking accounts */
429         AB_Banking_OnlineInit(info->api);
430 #endif
431         /* Determine current mapping */
432         root = gnc_book_get_root_account(gnc_get_current_book());
433 #ifdef AQBANKING6
434         info->gnc_hash = g_hash_table_new(&aai_ab_account_hash, &aai_ab_account_equal);
435 #else
436         info->gnc_hash = g_hash_table_new(&g_direct_hash, &g_direct_equal);
437 #endif
438         data.api = info->api;
439         data.hash = info->gnc_hash;
440         gnc_account_foreach_descendant(root, (AccountCb) hash_from_kvp_acc_cb, &data);
441         /* Memorize initial matches in reverse hash table */
442         info->gnc_revhash = g_hash_table_new(NULL, NULL);
443         g_hash_table_foreach(data.hash, (GHFunc) insert_acc_into_revhash_cb, (gpointer) info->gnc_revhash);
444 
445         info->match_page_prepared = TRUE;
446     }
447     /* Update the graphical representation */
448     update_account_list(info);
449 
450     /* Enable the Assistant Buttons */
451     gtk_assistant_set_page_complete (assistant, page, TRUE);
452 }
453 
454 void
aai_on_finish(GtkAssistant * assistant,gpointer user_data)455 aai_on_finish (GtkAssistant *assistant, gpointer user_data)
456 {
457     ABInitialInfo *info = user_data;
458 
459     g_return_if_fail(info && info->gnc_hash && info->gnc_revhash);
460 
461     /* Remove GnuCash accounts from reverse hash table which are still
462      * matched to an AqBanking account. For the remaining GnuCash accounts
463      * the KVPs must be cleared (i.e. deleted).
464      * Please note that the value (i.e. the GnuCash account) stored in info->gnc_hash
465      * is used as key for info->gnc_revhash */
466     g_hash_table_foreach(info->gnc_hash, (GHFunc) remove_acc_from_revhash_cb, info->gnc_revhash);
467     /* Commit the changes */
468     g_hash_table_foreach(info->gnc_revhash, (GHFunc) clear_kvp_acc_cb, NULL);
469     g_hash_table_foreach(info->gnc_hash, (GHFunc) save_kvp_acc_cb, NULL);
470 
471     gtk_widget_destroy(info->window);
472 }
473 
474 static gboolean
banking_has_accounts(AB_BANKING * banking)475 banking_has_accounts(AB_BANKING *banking)
476 {
477     GNC_AB_ACCOUNT_SPEC_LIST *accl = NULL;
478     gboolean result = FALSE;
479 
480     g_return_val_if_fail(banking, FALSE);
481 
482 #ifdef AQBANKING6
483     if (AB_Banking_GetAccountSpecList (banking, &accl) >= 0 &&
484         accl && AB_AccountSpec_List_GetCount (accl))
485         result = TRUE;
486     if (accl)
487         AB_AccountSpec_List_free (accl);
488 #else
489     AB_Banking_OnlineInit(banking);
490 
491     accl = AB_Banking_GetAccounts(banking);
492     if (accl && (AB_Account_List2_GetSize(accl) > 0))
493         result = TRUE;
494 
495     if (accl)
496         AB_Account_List2_free(accl);
497 
498     AB_Banking_OnlineFini(banking);
499 #endif
500 
501     return result;
502 }
503 
504 static void
hash_from_kvp_acc_cb(Account * gnc_acc,gpointer user_data)505 hash_from_kvp_acc_cb(Account *gnc_acc, gpointer user_data)
506 {
507     AccCbData *data = user_data;
508     GNC_AB_ACCOUNT_SPEC *ab_acc;
509 
510     ab_acc = gnc_ab_get_ab_account(data->api, gnc_acc);
511     if (ab_acc)
512         g_hash_table_insert(data->hash, ab_acc, gnc_acc);
513 }
514 
515 static gchar *
ab_account_longname(const GNC_AB_ACCOUNT_SPEC * ab_acc)516 ab_account_longname(const GNC_AB_ACCOUNT_SPEC *ab_acc)
517 {
518     gchar *bankname = NULL;
519     gchar *result = NULL;
520     const char *ab_bankname, *bankcode, *subAccountId, *account_number;
521 
522     g_return_val_if_fail(ab_acc, NULL);
523 
524 #ifdef AQBANKING6
525     bankcode = AB_AccountSpec_GetBankCode(ab_acc);
526     subAccountId = AB_AccountSpec_GetSubAccountNumber(ab_acc);
527     account_number = AB_AccountSpec_GetAccountNumber (ab_acc);
528 #else
529     ab_bankname = AB_Account_GetBankName(ab_acc);
530     bankname = ab_bankname ? gnc_utf8_strip_invalid_strdup(ab_bankname) : NULL;
531     bankcode = AB_Account_GetBankCode(ab_acc);
532     subAccountId = AB_Account_GetSubAccountId(ab_acc);
533     account_number = AB_Account_GetAccountNumber (ab_acc);
534 #endif
535     /* Translators: Strings are 1. Bank code, 2. Bank name,
536        3. Account Number, 4. Subaccount ID                  */
537     result = g_strdup_printf(_("Bank code %s (%s), Account %s (%s)"),
538                              bankcode,
539                              bankname ? bankname : "",
540                              account_number,
541                              subAccountId ? subAccountId : "");
542     g_free(bankname);
543 
544     return result;
545 
546 }
547 
548 static GNC_AB_ACCOUNT_SPEC *
update_account_list_acc_cb(GNC_AB_ACCOUNT_SPEC * ab_acc,gpointer user_data)549 update_account_list_acc_cb(GNC_AB_ACCOUNT_SPEC *ab_acc, gpointer user_data)
550 {
551     ABInitialInfo *info = user_data;
552     gchar *gnc_name, *ab_name;
553     Account *gnc_acc;
554     GtkTreeIter iter;
555 
556     g_return_val_if_fail(ab_acc && info, NULL);
557 
558     ab_name = ab_account_longname(ab_acc);
559 
560     /* Get corresponding gnucash account */
561     gnc_acc = g_hash_table_lookup(info->gnc_hash, ab_acc);
562 
563     /* Build the text for the gnucash account. */
564     if (gnc_acc)
565         gnc_name = gnc_account_get_full_name(gnc_acc);
566     else
567         gnc_name = g_strdup("");
568 
569     /* Add item to the list store */
570     gtk_list_store_append(info->account_store, &iter);
571     gtk_list_store_set(info->account_store, &iter,
572                        ACCOUNT_LIST_COL_AB_NAME, ab_name,
573                        ACCOUNT_LIST_COL_AB_ACCT, ab_acc,
574                        ACCOUNT_LIST_COL_GNC_NAME, gnc_name,
575                        ACCOUNT_LIST_COL_CHECKED, FALSE,
576                        -1);
577     g_free(gnc_name);
578     g_free(ab_name);
579 
580     return NULL;
581 }
582 
583 static void
update_account_list(ABInitialInfo * info)584 update_account_list(ABInitialInfo *info)
585 {
586     GNC_AB_ACCOUNT_SPEC_LIST *acclist = NULL;
587 
588     g_return_if_fail(info && info->api && info->gnc_hash);
589 
590     /* Detach model from view while updating */
591     g_object_ref(info->account_store);
592     gtk_tree_view_set_model(info->account_view, NULL);
593 
594     /* Refill the list */
595     gtk_list_store_clear(info->account_store);
596 #ifdef AQBANKING6
597     if (AB_Banking_GetAccountSpecList(info->api, &acclist) >= 0 && acclist)
598         AB_AccountSpec_List_ForEach(acclist, update_account_list_acc_cb, info);
599 #else
600     acclist = AB_Banking_GetAccounts(info->api);
601     if (acclist)
602         AB_Account_List2_ForEach(acclist, update_account_list_acc_cb, info);
603 #endif
604     else
605         g_warning("update_account_list: Oops, account list from AB_Banking "
606                   "is NULL");
607 
608     /* Attach model to view again */
609     gtk_tree_view_set_model(info->account_view,
610                             GTK_TREE_MODEL(info->account_store));
611 
612     g_object_unref(info->account_store);
613 }
614 
615 static gboolean
find_gnc_acc_cb(gpointer key,gpointer value,gpointer user_data)616 find_gnc_acc_cb(gpointer key, gpointer value, gpointer user_data)
617 {
618     RevLookupData *data = user_data;
619 
620     g_return_val_if_fail(data, TRUE);
621 
622     if (value == data->gnc_acc)
623     {
624         data->ab_acc = (GNC_AB_ACCOUNT_SPEC*) key;
625         return TRUE;
626     }
627     return FALSE;
628 }
629 
630 static gboolean
clear_line_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)631 clear_line_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
632               gpointer user_data)
633 {
634     RevLookupData *data = user_data;
635     GtkListStore *store = GTK_LIST_STORE(model);
636     gpointer ab_acc;
637 
638     g_return_val_if_fail(data && store, FALSE);
639 
640     gtk_tree_model_get(model, iter, ACCOUNT_LIST_COL_AB_ACCT, &ab_acc, -1);
641 
642 #ifdef AQBANKING6
643     if (aai_ab_account_equal(ab_acc, data->ab_acc))
644 #else
645     if (ab_acc == data->ab_acc)
646 #endif
647     {
648         gtk_list_store_set(store, iter, ACCOUNT_LIST_COL_GNC_NAME, "",
649                            ACCOUNT_LIST_COL_CHECKED, TRUE, -1);
650         return TRUE;
651     }
652     return FALSE;
653 }
654 
655 static void
account_list_clicked_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * col,gpointer user_data)656 account_list_clicked_cb (GtkTreeView *view, GtkTreePath *path,
657                          GtkTreeViewColumn  *col, gpointer user_data)
658 {
659     ABInitialInfo *info = user_data;
660     GtkTreeModel *model;
661     GtkTreeIter iter;
662     GNC_AB_ACCOUNT_SPEC *ab_acc;
663     gchar *longname, *gnc_name;
664     Account *old_value, *gnc_acc;
665     const gchar *currency;
666     gnc_commodity *commodity = NULL;
667     gboolean ok_pressed;
668 
669     g_return_if_fail(info);
670 
671     PINFO("Row has been double-clicked.");
672 
673     model = gtk_tree_view_get_model(view);
674 
675     if (!gtk_tree_model_get_iter(model, &iter, path))
676         return; /* path describes a non-existing row - should not happen */
677 
678     gtk_tree_model_get(model, &iter, ACCOUNT_LIST_COL_AB_ACCT, &ab_acc, -1);
679 
680     if (ab_acc)
681     {
682         old_value = g_hash_table_lookup(info->gnc_hash, ab_acc);
683 
684         longname = ab_account_longname(ab_acc);
685 #ifdef AQBANKING6
686         currency = AB_AccountSpec_GetCurrency(ab_acc);
687 #else
688         currency = AB_Account_GetCurrency(ab_acc);
689 #endif
690         if (currency && *currency)
691         {
692             commodity = gnc_commodity_table_lookup(
693                             gnc_commodity_table_get_table(gnc_get_current_book()),
694                             GNC_COMMODITY_NS_CURRENCY,
695                             currency);
696         }
697 
698         gnc_acc = gnc_import_select_account(info->window, NULL, TRUE,
699                                             longname, commodity, ACCT_TYPE_BANK,
700                                             old_value, &ok_pressed);
701         g_free(longname);
702 
703         if (ok_pressed && old_value != gnc_acc)
704         {
705             if (gnc_acc)
706             {
707                 RevLookupData data;
708 
709                 /* Lookup and clear other mappings to gnc_acc */
710                 data.gnc_acc = gnc_acc;
711                 data.ab_acc = NULL;
712                 g_hash_table_find(info->gnc_hash, (GHRFunc) find_gnc_acc_cb,
713                                   &data);
714                 if (data.ab_acc)
715                     delete_account_match(info, &data);
716 
717                 /* Map ab_acc to gnc_acc */
718                 g_hash_table_insert(info->gnc_hash, ab_acc, gnc_acc);
719                 gnc_name = gnc_account_get_full_name(gnc_acc);
720                 gtk_list_store_set(info->account_store, &iter,
721                                    ACCOUNT_LIST_COL_GNC_NAME, gnc_name,
722                                    ACCOUNT_LIST_COL_CHECKED, TRUE,
723                                    -1);
724                 g_free(gnc_name);
725 
726             }
727             else
728             {
729                 g_hash_table_remove(info->gnc_hash, ab_acc);
730                 gtk_list_store_set(info->account_store, &iter,
731                                    ACCOUNT_LIST_COL_GNC_NAME, "",
732                                    ACCOUNT_LIST_COL_CHECKED, TRUE,
733                                    -1);
734             }
735         }
736     }
737 }
738 
739 static void
clear_kvp_acc_cb(gpointer gnc_acc,gpointer ab_acc,gpointer user_data)740 clear_kvp_acc_cb(gpointer gnc_acc, gpointer ab_acc, gpointer user_data)
741 {
742     g_return_if_fail(gnc_acc);
743     /* Delete "online-id" and complete "hbci..." KVPs for GnuCash account */
744     gnc_account_delete_map_entry((Account *) gnc_acc, "online_id", NULL, NULL, FALSE);
745     gnc_account_delete_map_entry((Account *) gnc_acc, "hbci", NULL, NULL, FALSE);
746 }
747 
748 static void
save_kvp_acc_cb(gpointer key,gpointer value,gpointer user_data)749 save_kvp_acc_cb(gpointer key, gpointer value, gpointer user_data)
750 {
751     GNC_AB_ACCOUNT_SPEC *ab_acc = key;
752     Account *gnc_acc = value;
753     guint32 ab_account_uid;
754     const gchar *ab_accountid, *gnc_accountid;
755     const gchar *ab_bankcode, *gnc_bankcode;
756 #ifdef AQBANKING6
757     gchar *ab_online_id;
758     const gchar *gnc_online_id;
759 #endif
760 
761     g_return_if_fail(ab_acc && gnc_acc);
762 
763 #ifdef AQBANKING6
764     ab_account_uid = AB_AccountSpec_GetUniqueId(ab_acc);
765 #else
766     ab_account_uid = AB_Account_GetUniqueId(ab_acc);
767 #endif
768     if (gnc_ab_get_account_uid(gnc_acc) != ab_account_uid)
769         gnc_ab_set_account_uid(gnc_acc, ab_account_uid);
770 
771 #ifdef AQBANKING6
772     ab_accountid = AB_AccountSpec_GetAccountNumber(ab_acc);
773 #else
774     ab_accountid = AB_Account_GetAccountNumber(ab_acc);
775 #endif
776     gnc_accountid = gnc_ab_get_account_accountid(gnc_acc);
777     if (ab_accountid
778             && (!gnc_accountid
779                 || (strcmp(ab_accountid, gnc_accountid) != 0)))
780         gnc_ab_set_account_accountid(gnc_acc, ab_accountid);
781 
782 #ifdef AQBANKING6
783     ab_bankcode = AB_AccountSpec_GetBankCode(ab_acc);
784 #else
785     ab_bankcode = AB_Account_GetBankCode(ab_acc);
786 #endif
787     gnc_bankcode = gnc_ab_get_account_bankcode(gnc_acc);
788     if (ab_bankcode
789             && (!gnc_bankcode
790                 || (strcmp(gnc_bankcode, ab_bankcode) != 0)))
791         gnc_ab_set_account_bankcode(gnc_acc, ab_bankcode);
792 
793 #ifdef AQBANKING6
794     ab_online_id = gnc_ab_create_online_id(ab_bankcode, ab_accountid);
795     gnc_online_id = gnc_import_get_acc_online_id(gnc_acc);
796     if (ab_online_id && (!gnc_online_id || (strcmp(ab_online_id, gnc_online_id) != 0)))
797         gnc_import_set_acc_online_id(gnc_acc, ab_online_id);
798     g_free(ab_online_id);
799 #endif
800 }
801 
802 static void
aai_close_handler(gpointer user_data)803 aai_close_handler(gpointer user_data)
804 {
805     ABInitialInfo *info = user_data;
806 
807     gnc_save_window_size(GNC_PREFS_GROUP, GTK_WINDOW(info->window));
808     gtk_widget_destroy(info->window);
809 }
810 
aai_on_prepare(GtkAssistant * assistant,GtkWidget * page,gpointer user_data)811 void aai_on_prepare (GtkAssistant  *assistant, GtkWidget *page,
812                      gpointer user_data)
813 {
814     switch (gtk_assistant_get_current_page(assistant))
815     {
816     case 1:
817         /* Current page is wizard button page */
818         aai_wizard_page_prepare (assistant , user_data );
819         break;
820     case 2:
821         /* Current page is match page */
822         aai_match_page_prepare (assistant , user_data );
823         break;
824     }
825 }
826 
827 static ABInitialInfo *
gnc_ab_initial_assistant_new(void)828 gnc_ab_initial_assistant_new(void)
829 {
830     GtkBuilder *builder;
831     GtkTreeViewColumn *column;
832     GtkTreeSelection *selection;
833     gint component_id;
834 
835     ABInitialInfo *info = g_new0(ABInitialInfo, 1);
836     builder = gtk_builder_new();
837     gnc_builder_add_from_file (builder, "assistant-ab-initial.glade", "aqbanking_init_assistant");
838 
839     info->window = GTK_WIDGET(gtk_builder_get_object (builder, "aqbanking_init_assistant"));
840 
841     info->api = gnc_AB_BANKING_new();
842     info->deferred_info = NULL;
843     info->gnc_hash = NULL;
844 
845     info->match_page_prepared = FALSE;
846     info->account_view =
847         GTK_TREE_VIEW(gtk_builder_get_object (builder, "account_page_view"));
848 
849     info->account_store = gtk_list_store_new(NUM_ACCOUNT_LIST_COLS,
850                           G_TYPE_INT, G_TYPE_STRING,
851                           G_TYPE_POINTER, G_TYPE_STRING,
852                           G_TYPE_BOOLEAN);
853     gtk_tree_view_set_model(info->account_view,
854                             GTK_TREE_MODEL(info->account_store));
855     g_object_unref(info->account_store);
856 
857     column = gtk_tree_view_column_new_with_attributes(
858                  _("Online Banking Account Name"), gtk_cell_renderer_text_new(),
859                  "text", ACCOUNT_LIST_COL_AB_NAME, (gchar*) NULL);
860     gtk_tree_view_append_column(info->account_view, column);
861 
862     column = gtk_tree_view_column_new_with_attributes(
863                  _("GnuCash Account Name"), gtk_cell_renderer_text_new(),
864                  "text", ACCOUNT_LIST_COL_GNC_NAME, (gchar*) NULL);
865     gtk_tree_view_column_set_expand(column, TRUE);
866     gtk_tree_view_append_column(info->account_view, column);
867 
868     column = gtk_tree_view_column_new_with_attributes(
869                  _("New?"), gtk_cell_renderer_toggle_new(),
870                  "active", ACCOUNT_LIST_COL_CHECKED, (gchar*) NULL);
871     gtk_tree_view_append_column(info->account_view, column);
872 
873     selection = gtk_tree_view_get_selection(info->account_view);
874     gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
875 
876     gnc_restore_window_size (GNC_PREFS_GROUP,
877                              GTK_WINDOW(info->window), gnc_ui_get_main_window(NULL));
878 
879     g_signal_connect(info->account_view, "row-activated",
880                      G_CALLBACK(account_list_clicked_cb), info);
881 
882     g_signal_connect (G_OBJECT(info->window), "destroy",
883                       G_CALLBACK (aai_destroy_cb), info);
884 
885     gtk_builder_connect_signals(builder, info);
886     g_object_unref(G_OBJECT(builder));
887 
888     component_id = gnc_register_gui_component(ASSISTANT_AB_INITIAL_CM_CLASS,
889                    NULL, aai_close_handler, info);
890 
891     gnc_gui_component_set_session(component_id, gnc_get_current_session());
892     return info;
893 }
894 
895 void
gnc_ab_initial_assistant(void)896 gnc_ab_initial_assistant(void)
897 {
898     if (!single_info)
899         single_info = gnc_ab_initial_assistant_new();
900     gtk_widget_show(single_info->window);
901 }
902 
903