1 /********************************************************************\
2  * dialog-account-picker.c -- window for picking a Gnucash account  *
3  * from the QIF importer.                                           *
4  * Copyright (C) 2000-2001 Bill Gribble <grib@billgribble.com>      *
5  * Copyright (c) 2006 David Hampton <hampton@employees.org>         *
6  *                                                                  *
7  * This program is free software; you can redistribute it and/or    *
8  * modify it under the terms of the GNU General Public License as   *
9  * published by the Free Software Foundation; either version 2 of   *
10  * the License, or (at your option) any later version.              *
11  *                                                                  *
12  * This program is distributed in the hope that it will be useful,  *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
15  * GNU General Public License for more details.                     *
16  *                                                                  *
17  * You should have received a copy of the GNU General Public License*
18  * along with this program; if not, contact:                        *
19  *                                                                  *
20  * Free Software Foundation           Voice:  +1-617-542-5942       *
21  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
22  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
23 \********************************************************************/
24 
25 #include <config.h>
26 
27 #include <gtk/gtk.h>
28 #include <glib/gi18n.h>
29 #include <stdio.h>
30 #include <libguile.h>
31 
32 #include "dialog-account-picker.h"
33 #include "dialog-utils.h"
34 #include "assistant-qif-import.h"
35 #include "gnc-gui-query.h"
36 #include "gnc-prefs.h"
37 #include "gnc-ui-util.h"
38 #include "guile-mappings.h"
39 #include "gnc-guile-utils.h"
40 #include "gnc-ui.h" /* for GNC_RESPONSE_NEW */
41 
42 #define GNC_PREFS_GROUP   "dialogs.import.qif.account-picker"
43 
44 enum account_cols
45 {
46     ACCOUNT_COL_NAME = 0,
47     ACCOUNT_COL_FULLNAME,
48     ACCOUNT_COL_PLACEHOLDER,
49     ACCOUNT_COL_CHECK,
50     NUM_ACCOUNT_COLS
51 };
52 
53 struct _accountpickerdialog
54 {
55     GtkWidget       * dialog;
56     GtkTreeView     * treeview;
57     GtkWidget       * pwhbox;
58     GtkWidget       * pwarning;
59     GtkWidget       * ok_button;
60     QIFImportWindow * qif_wind;
61     SCM               map_entry;
62     gchar           * selected_name;
63 };
64 
65 void gnc_ui_qif_account_picker_new_cb (GtkButton * w, gpointer user_data);
66 
67 /****************************************************************
68  * acct_tree_add_accts
69  *
70  * Given a Scheme list of accounts, this function populates a
71  * GtkTreeStore from them. If the search_name and reference
72  * parameters are provided, and an account is found whose full
73  * name matches search_name, then a GtkTreeRowReference* will be
74  * returned in the reference parameter.
75  ****************************************************************/
76 static void
acct_tree_add_accts(SCM accts,GtkTreeStore * store,GtkTreeIter * parent,const char * base_name,const char * search_name,GtkTreeRowReference ** reference)77 acct_tree_add_accts(SCM accts,
78                     GtkTreeStore *store,
79                     GtkTreeIter *parent,
80                     const char *base_name,
81                     const char *search_name,
82                     GtkTreeRowReference **reference)
83 {
84     GtkTreeIter  iter;
85     char         * compname;
86     char         * acctname;
87     gboolean     leafnode;
88     SCM          current;
89     gboolean     checked;
90     Account      * account;
91 
92     while (!scm_is_null(accts))
93     {
94         gboolean placeholder = FALSE;
95         current = SCM_CAR(accts);
96 
97         if (scm_is_null(current))
98         {
99             g_critical("QIF import: BUG DETECTED in acct_tree_add_accts!");
100             accts = SCM_CDR(accts);
101             continue;
102         }
103 
104         if (scm_is_string(SCM_CAR(current)))
105             compname = gnc_scm_to_utf8_string (SCM_CAR(current));
106         else
107             compname = g_strdup("");
108 
109         if (!scm_is_null(SCM_CADDR(current)))
110         {
111             leafnode = FALSE;
112         }
113         else
114         {
115             leafnode = TRUE;
116         }
117 
118         /* compute full name */
119         if (base_name && *base_name)
120         {
121             acctname = g_strjoin(gnc_get_account_separator_string(),
122                                  base_name, compname, (char *)NULL);
123         }
124         else
125         {
126             acctname = g_strdup(compname);
127         }
128 
129         checked = (SCM_CADR(current) == SCM_BOOL_T);
130 
131         account = gnc_account_lookup_by_full_name (gnc_get_current_root_account(), acctname);
132         if (account)
133             placeholder = xaccAccountGetPlaceholder (account);
134 
135         gtk_tree_store_append(store, &iter, parent);
136         gtk_tree_store_set(store, &iter,
137                            ACCOUNT_COL_NAME, compname,
138                            ACCOUNT_COL_FULLNAME, acctname,
139                            ACCOUNT_COL_PLACEHOLDER, placeholder,
140                            ACCOUNT_COL_CHECK, checked,
141                            -1);
142 
143         if (reference && !*reference &&
144                 search_name && (g_utf8_collate(search_name, acctname) == 0))
145         {
146             GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
147             *reference = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
148             gtk_tree_path_free(path);
149         }
150 
151         if (!leafnode)
152         {
153             acct_tree_add_accts(SCM_CADDR(current), store, &iter, acctname,
154                                 search_name, reference);
155         }
156 
157         g_free(acctname);
158         g_free(compname);
159 
160         accts = SCM_CDR(accts);
161     }
162 }
163 
164 
165 /****************************************************************
166  * build_acct_tree
167  *
168  * This function refreshes the contents of the account tree.
169  ****************************************************************/
170 static void
build_acct_tree(QIFAccountPickerDialog * picker,QIFImportWindow * import)171 build_acct_tree(QIFAccountPickerDialog * picker, QIFImportWindow * import)
172 {
173     SCM  get_accts = scm_c_eval_string("qif-import:get-all-accts");
174     SCM  acct_tree;
175     GtkTreeStore *store;
176     GtkTreePath *path;
177     GtkTreeSelection* selection;
178     GtkTreeRowReference *reference = NULL;
179     gchar *name_to_select;
180 
181     g_return_if_fail(picker && import);
182 
183     /* Get an account tree with all existing and to-be-imported accounts. */
184     acct_tree = scm_call_1(get_accts,
185                            gnc_ui_qif_import_assistant_get_mappings(import));
186 
187     /* Rebuild the store.
188      * NOTE: It is necessary to save a copy of the name to select, because
189      *       when the store is cleared, everything becomes unselected. */
190     name_to_select = g_strdup(picker->selected_name);
191     store = GTK_TREE_STORE(gtk_tree_view_get_model(picker->treeview));
192     gtk_tree_store_clear(store);
193     acct_tree_add_accts(acct_tree, store, NULL, NULL, name_to_select, &reference);
194     g_free(name_to_select);
195 
196     /* Select and display the indicated account (if it was found). */
197     if (reference)
198     {
199         selection = gtk_tree_view_get_selection(picker->treeview);
200         path = gtk_tree_row_reference_get_path(reference);
201         if (path)
202         {
203             gtk_tree_view_expand_to_path(picker->treeview, path);
204             gtk_tree_selection_select_path(selection, path);
205             gtk_tree_view_scroll_to_cell (picker->treeview, path,
206                                           NULL, TRUE, 0.5, 0.0);
207             gtk_tree_path_free(path);
208         }
209         gtk_tree_row_reference_free(reference);
210     }
211 }
212 
213 
214 /****************************************************************
215  * gnc_ui_qif_account_picker_new_cb
216  *
217  * This handler is invoked when the user wishes to create a new
218  * account.
219  ****************************************************************/
220 void
gnc_ui_qif_account_picker_new_cb(GtkButton * w,gpointer user_data)221 gnc_ui_qif_account_picker_new_cb(GtkButton * w, gpointer user_data)
222 {
223     QIFAccountPickerDialog * wind = user_data;
224     SCM name_setter = scm_c_eval_string("qif-map-entry:set-gnc-name!");
225     const gchar *name;
226     int response;
227     gchar *fullname;
228     GtkWidget *dlg, *entry;
229 
230     /* Create a dialog to get the new account name. */
231     dlg = gtk_message_dialog_new(GTK_WINDOW(wind->dialog),
232                                  GTK_DIALOG_DESTROY_WITH_PARENT,
233                                  GTK_MESSAGE_QUESTION,
234                                  GTK_BUTTONS_OK_CANCEL,
235                                  "%s", _("Enter a name for the account"));
236     gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
237     entry = gtk_entry_new();
238     gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
239     gtk_entry_set_max_length(GTK_ENTRY(entry), 250);
240     gtk_widget_show(entry);
241     gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area (GTK_DIALOG(dlg))), entry);
242 
243     /* Run the dialog to get the new account name. */
244     response = gtk_dialog_run(GTK_DIALOG(dlg));
245     name = gtk_entry_get_text(GTK_ENTRY(entry));
246 
247     /* Did the user enter a name and click OK? */
248     if (response == GTK_RESPONSE_OK && name && *name)
249     {
250         /* If an account is selected, this will be a new subaccount. */
251         if (wind->selected_name && *(wind->selected_name))
252             /* We have the short name; determine the full name. */
253             fullname = g_strjoin(gnc_get_account_separator_string(),
254                                  wind->selected_name, name, (char *)NULL);
255         else
256             fullname = g_strdup(name);
257 
258         /* Save the full name and update the map entry. */
259         g_free(wind->selected_name);
260         wind->selected_name = fullname;
261         scm_call_2(name_setter, wind->map_entry, scm_from_utf8_string(fullname));
262     }
263     gtk_widget_destroy(dlg);
264 
265     /* Refresh the tree display and give it the focus. */
266     build_acct_tree(wind, wind->qif_wind);
267     gtk_widget_grab_focus(GTK_WIDGET(wind->treeview));
268 }
269 
270 
271 /****************************************************************
272  * gnc_ui_qif_account_picker_changed_cb
273  *
274  ****************************************************************/
275 static void
gnc_ui_qif_account_picker_changed_cb(GtkTreeSelection * selection,gpointer user_data)276 gnc_ui_qif_account_picker_changed_cb(GtkTreeSelection *selection,
277                                      gpointer          user_data)
278 {
279     QIFAccountPickerDialog * wind = user_data;
280     SCM name_setter = scm_c_eval_string("qif-map-entry:set-gnc-name!");
281     GtkTreeModel *model;
282     GtkTreeIter iter;
283     gboolean placeholder;
284 
285     gtk_widget_set_sensitive (wind->ok_button, TRUE); // enable OK button
286 
287     g_free(wind->selected_name);
288     if (gtk_tree_selection_get_selected(selection, &model, &iter))
289     {
290         gtk_tree_model_get(model, &iter,
291                            ACCOUNT_COL_PLACEHOLDER, &placeholder,
292                            ACCOUNT_COL_FULLNAME, &wind->selected_name,
293                            -1);
294         scm_call_2(name_setter, wind->map_entry,
295                    wind->selected_name ? scm_from_utf8_string(wind->selected_name) : SCM_BOOL_F);
296 
297         if (placeholder)
298         {
299             gchar *text = g_strdup_printf (_("The account %s is a placeholder account and does not allow "
300                                              "transactions. Please choose a different account."), wind->selected_name);
301 
302             gtk_label_set_text (GTK_LABEL(wind->pwarning), text);
303             gnc_label_set_alignment (wind->pwarning, 0.0, 0.5);
304             gtk_widget_show_all (GTK_WIDGET(wind->pwhbox));
305             g_free (text);
306 
307             gtk_widget_set_sensitive (wind->ok_button, FALSE); // disable OK button
308         }
309         else
310             gtk_widget_hide (GTK_WIDGET(wind->pwhbox)); // hide the placeholder warning
311     }
312     else
313     {
314         wind->selected_name = NULL;
315     }
316 }
317 
318 
319 /****************************************************************
320  * gnc_ui_qif_account_picker_row_activated_cb
321  *
322  ****************************************************************/
323 static void
gnc_ui_qif_account_picker_row_activated_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user_data)324 gnc_ui_qif_account_picker_row_activated_cb(GtkTreeView *view,
325         GtkTreePath *path,
326         GtkTreeViewColumn *column,
327         gpointer user_data)
328 {
329     QIFAccountPickerDialog *wind = user_data;
330     g_return_if_fail(wind);
331 
332     gtk_dialog_response(GTK_DIALOG(wind->dialog), GTK_RESPONSE_OK);
333 }
334 
335 
336 /****************************************************************
337  * gnc_ui_qif_account_picker_map_cb
338  *
339  ****************************************************************/
340 static int
gnc_ui_qif_account_picker_map_cb(GtkWidget * w,gpointer user_data)341 gnc_ui_qif_account_picker_map_cb(GtkWidget * w, gpointer user_data)
342 {
343     QIFAccountPickerDialog * wind = user_data;
344 
345     /* update the tree display with all the existing accounts plus all
346      * the ones the QIF importer thinks it will be creating.  this will
347      * also select the map_entry line. */
348     build_acct_tree(wind, wind->qif_wind);
349     return FALSE;
350 }
351 
352 
353 /****************************************************************
354  * dialog_response_cb
355  *
356  ****************************************************************/
357 static void
dialog_response_cb(GtkDialog * dialog,gint response_id,gpointer user_data)358 dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data)
359 {
360     QIFAccountPickerDialog * wind = user_data;
361     GtkTreeModel *model;
362     GtkTreeIter iter;
363     gboolean placeholder;
364 
365     if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection
366                                         (wind->treeview), &model, &iter))
367         gtk_tree_model_get (model, &iter,
368                             ACCOUNT_COL_PLACEHOLDER, &placeholder, -1);
369 
370     if (response_id == GTK_RESPONSE_OK)
371     {
372         if (placeholder)
373             g_signal_stop_emission_by_name (dialog, "response");
374     }
375 }
376 
377 
378 /****************************************************************
379  * qif_account_picker_dialog
380  *
381  * Select an account from the ones that the engine knows about,
382  * plus those that will be created by the QIF import.  If the
383  * user clicks OK, map_entry is changed and TRUE is returned.
384  * If the clicks Cancel instead, FALSE is returned. Modal.
385  ****************************************************************/
386 gboolean
qif_account_picker_dialog(GtkWindow * parent,QIFImportWindow * qif_wind,SCM map_entry)387 qif_account_picker_dialog(GtkWindow *parent, QIFImportWindow * qif_wind, SCM map_entry)
388 {
389     QIFAccountPickerDialog * wind;
390     SCM gnc_name     = scm_c_eval_string("qif-map-entry:gnc-name");
391     SCM set_gnc_name = scm_c_eval_string("qif-map-entry:set-gnc-name!");
392     SCM orig_acct    = scm_call_1(gnc_name, map_entry);
393     int response;
394     GtkBuilder *builder;
395 
396     wind = g_new0(QIFAccountPickerDialog, 1);
397 
398     /* Save the map entry. */
399     wind->map_entry = map_entry;
400     scm_gc_protect_object(wind->map_entry);
401 
402     /* Set the initial account to be selected. */
403     if (scm_is_string(orig_acct))
404         wind->selected_name = gnc_scm_to_utf8_string (orig_acct);
405 
406     builder = gtk_builder_new();
407     gnc_builder_add_from_file (builder, "dialog-account-picker.glade", "qif_import_account_picker_dialog");
408 
409     /* Connect all the signals */
410     gtk_builder_connect_signals (builder, wind);
411 
412     wind->dialog     = GTK_WIDGET(gtk_builder_get_object (builder, "qif_import_account_picker_dialog"));
413     wind->treeview   = GTK_TREE_VIEW(gtk_builder_get_object (builder, "account_tree"));
414     wind->pwhbox     = GTK_WIDGET(gtk_builder_get_object (builder, "placeholder_warning_hbox"));
415     wind->pwarning   = GTK_WIDGET(gtk_builder_get_object (builder, "placeholder_warning_label"));
416     wind->ok_button  = GTK_WIDGET(gtk_builder_get_object (builder, "okbutton"));
417     wind->qif_wind   = qif_wind;
418 
419     gtk_window_set_transient_for (GTK_WINDOW (wind->dialog), parent);
420 
421     {
422         GtkTreeSelection *selection;
423         GtkTreeStore *store;
424         GtkCellRenderer *renderer;
425         GtkTreeViewColumn *column;
426 
427         store = gtk_tree_store_new(NUM_ACCOUNT_COLS, G_TYPE_STRING, G_TYPE_STRING,
428                                    G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
429         gtk_tree_view_set_model(wind->treeview, GTK_TREE_MODEL(store));
430         g_object_unref(store);
431 
432         renderer = gtk_cell_renderer_text_new();
433         column = gtk_tree_view_column_new_with_attributes(_("Account"),
434                  renderer,
435                  "text",
436                  ACCOUNT_COL_NAME,
437                  NULL);
438         g_object_set(column, "expand", TRUE, NULL);
439         gtk_tree_view_append_column(wind->treeview, column);
440 
441         renderer = gtk_cell_renderer_toggle_new();
442         g_object_set(renderer, "activatable", FALSE, NULL);
443         column = gtk_tree_view_column_new_with_attributes(_("Placeholder?"),
444                  renderer,
445                  "active",
446                  ACCOUNT_COL_PLACEHOLDER,
447                  NULL);
448         gtk_tree_view_append_column(wind->treeview, column);
449 
450         renderer = gtk_cell_renderer_toggle_new();
451         g_object_set(renderer, "activatable", FALSE, NULL);
452         column = gtk_tree_view_column_new_with_attributes(_("New?"),
453                  renderer,
454                  "active",
455                  ACCOUNT_COL_CHECK,
456                  NULL);
457         gtk_tree_view_append_column(wind->treeview, column);
458 
459         selection = gtk_tree_view_get_selection(wind->treeview);
460         g_signal_connect(selection, "changed",
461                          G_CALLBACK(gnc_ui_qif_account_picker_changed_cb), wind);
462         g_signal_connect(wind->treeview, "row-activated",
463                          G_CALLBACK(gnc_ui_qif_account_picker_row_activated_cb),
464                          wind);
465     }
466 
467     g_signal_connect_after(wind->dialog, "map",
468                            G_CALLBACK(gnc_ui_qif_account_picker_map_cb),
469                            wind);
470 
471     gnc_restore_window_size (GNC_PREFS_GROUP,
472                              GTK_WINDOW(wind->dialog), parent);
473 
474     /* this is to get the checkmarks set up right.. it will get called
475      * again after the window is mapped. */
476     build_acct_tree(wind, wind->qif_wind);
477 
478     g_signal_connect (wind->dialog, "response",
479                       G_CALLBACK (dialog_response_cb), wind);
480 
481     do
482     {
483         response = gtk_dialog_run(GTK_DIALOG(wind->dialog));
484     }
485     while (response == GNC_RESPONSE_NEW);
486     gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(wind->dialog));
487     gtk_widget_destroy(wind->dialog);
488     g_object_unref(G_OBJECT(builder));
489 
490     scm_gc_unprotect_object(wind->map_entry);
491     g_free(wind->selected_name);
492     g_free(wind);
493 
494     if (response == GTK_RESPONSE_OK)
495         return TRUE;
496 
497     /* Restore the original mapping. */
498     scm_call_2(set_gnc_name, map_entry, orig_acct);
499 
500     return FALSE;
501 }
502