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