GnuCash  5.6-150-g038405b370+
dialog-account-picker.c
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 
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
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
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
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
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
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
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
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 = TRUE;
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
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 }
utility functions for the GnuCash UI
STRUCTS.
Account * gnc_account_lookup_by_full_name(const Account *any_acc, const gchar *name)
The gnc_account_lookup_full_name() subroutine works like gnc_account_lookup_by_name, but uses fully-qualified names using the given separator.
Definition: Account.cpp:3133
Generic api to store and retrieve preferences.
gboolean xaccAccountGetPlaceholder(const Account *acc)
Get the "placeholder" flag for an account.
Definition: Account.cpp:4074
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition: Account.cpp:205