GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
import-account-matcher.c
1 /********************************************************************\
2  * import-account-matcher.c - flexible account picker/matcher *
3  * *
4  * Copyright (C) 2002 Benoit GrĂ©goire <[email protected]> *
5  * Copyright (C) 2012 Robert Fewell *
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 [email protected] *
23 \********************************************************************/
31 #include "config.h"
32 
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35 
36 #include "import-account-matcher.h"
37 #include "import-utilities.h"
38 #include "dialog-account.h"
39 #include "dialog-utils.h"
40 
41 #include "gnc-commodity.h"
42 #include "gnc-engine.h"
43 #include "gnc-tree-view-account.h"
44 #include "gnc-ui.h"
45 
46 static QofLogModule log_module = GNC_MOD_IMPORT;
47 
48 #define STATE_SECTION "dialogs/import/generic_matcher/account_matcher"
49 
50 /*-******************************************************************\
51  * Functions needed by gnc_import_select_account
52  *
53 \********************************************************************/
57 static AccountPickerDialog* gnc_import_new_account_picker(void)
58 {
59  AccountPickerDialog* picker = g_new(AccountPickerDialog, 1);
60  picker->dialog = NULL;
61  picker->assistant = NULL;
62  picker->account_tree = NULL;
63  picker->account_tree_sw = NULL;
64  picker->auto_create = TRUE;
65  picker->account_human_description = NULL;
66  picker->account_online_id_value = NULL;
67  picker->account_online_id_label = NULL;
68  picker->new_account_default_commodity = NULL;
69  picker->new_account_default_type = 0;
70  picker->default_account = NULL;
71  picker->retAccount = NULL;
72  return picker;
73 }
74 
75 
76 /**************************************************
77  * test_acct_online_id_match
78  *
79  * test for match of kvp_frame of account
80  **************************************************/
81 static gpointer test_acct_online_id_match(Account *acct, gpointer param_online_id)
82 {
83  const gchar * current_online_id = gnc_import_get_acc_online_id(acct);
84  if ( (current_online_id != NULL
85  && param_online_id != NULL )
86  && strcmp( current_online_id, param_online_id ) == 0 )
87  {
88  return (gpointer *) acct;
89  }
90  else
91  {
92  return NULL;
93  }
94 }
95 
96 
97 /***********************************************************
98  * build_acct_tree
99  *
100  * build the account tree with the custome column, online_id
101  ************************************************************/
102 static void
103 build_acct_tree(AccountPickerDialog *picker)
104 {
105  GtkTreeView *account_tree;
106  GtkTreeViewColumn *col;
107 
108  /* Build a new account tree */
109  DEBUG("Begin");
110  account_tree = gnc_tree_view_account_new(FALSE);
111  picker->account_tree = GNC_TREE_VIEW_ACCOUNT(account_tree);
112  gtk_tree_view_set_headers_visible (account_tree, TRUE);
113  col = gnc_tree_view_find_column_by_name(GNC_TREE_VIEW(account_tree), "type");
114  g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
115 
116  /* Add our custom column. */
117  col = gnc_tree_view_account_add_property_column (picker->account_tree,
118  _("Account ID"), "online-id");
119  g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
120 
121  gtk_container_add(GTK_CONTAINER(picker->account_tree_sw),
122  GTK_WIDGET(picker->account_tree));
123 
124  /* Configure the columns */
125  gnc_tree_view_configure_columns (GNC_TREE_VIEW(picker->account_tree));
126  g_object_set(account_tree,
127  "state-section", STATE_SECTION,
128  "show-column-menu", TRUE,
129  (gchar*) NULL);
130 }
131 
132 
133 /*******************************************************
134  * gnc_import_add_account
135  *
136  * Callback for when user clicks to create a new account
137  *******************************************************/
138 static void
139 gnc_import_add_account(GtkWidget *button, AccountPickerDialog *picker)
140 {
141  Account *selected_account, *new_account;
142  GList * valid_types = NULL;
143  /*DEBUG("Begin"); */
144  if (picker->new_account_default_type != ACCT_TYPE_NONE)
145  {
146  /*Yes, this is weird, but we really DO want to pass the value instead of the pointer...*/
147  valid_types = g_list_prepend(valid_types, GINT_TO_POINTER(picker->new_account_default_type));
148  }
149  selected_account = gnc_tree_view_account_get_selected_account(picker->account_tree);
150  new_account = gnc_ui_new_accounts_from_name_with_defaults ( picker->account_human_description,
151  valid_types,
152  picker->new_account_default_commodity,
153  selected_account);
154  g_list_free(valid_types);
155  gnc_tree_view_account_set_selected_account(picker->account_tree, new_account);
156 }
157 
158 
159 /*******************************************************
160  * account_tree_row_activated_cb
161  *
162  * Callback for when user double clicks on an account
163  *******************************************************/
164 static void
165 account_tree_row_activated_cb(GtkTreeView *view, GtkTreePath *path,
166  GtkTreeViewColumn *column,
167  AccountPickerDialog *picker)
168 {
169  const gchar *retval_name = NULL;
170  Account *old_id_acc;
171 
172  /* See if we have a dialog, if not we are an assistant */
173  if (picker->dialog == NULL)
174  {
175  GtkAssistant *assistant = GTK_ASSISTANT(picker->assistant);
176  gint num = gtk_assistant_get_current_page (assistant);
177  GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
178 
179  picker->retAccount = gnc_tree_view_account_get_selected_account(picker->account_tree);
180  if (picker->retAccount)
181  retval_name = xaccAccountGetName(picker->retAccount);
182  if (!retval_name)
183  retval_name = "(null)";
184  DEBUG("Selected account %p, %s", picker->retAccount, retval_name);
185 
186  /* See if the selected account is a placeholder. */
187  if (picker->retAccount && xaccAccountGetPlaceholder (picker->retAccount))
188  {
189  gnc_error_dialog (picker->dialog,
190  _("The account %s is a placeholder account and does not allow "
191  "transactions. Please choose a different account."),
192  retval_name);
193  }
194  else if ( picker->account_online_id_value != NULL)
195  {
196  /* find the old account for this on line id value and reset it */
197  old_id_acc =
198  gnc_account_foreach_descendant_until(gnc_get_current_root_account (),
199  test_acct_online_id_match,
200  /* This argument will only be used as a "const char*" */
201  (void*)picker->account_online_id_value);
202 
203  if (old_id_acc != NULL)
204  gnc_import_set_acc_online_id(old_id_acc, "");
205 
206  gnc_import_set_acc_online_id(picker->retAccount, picker->account_online_id_value);
207  gtk_assistant_set_page_complete (assistant, page, TRUE);
208  }
209  else
210  gtk_assistant_set_page_complete (assistant, page, TRUE);
211  }
212  else
213  {
214  gtk_dialog_response(GTK_DIALOG(picker->dialog), GTK_RESPONSE_OK);
215  }
216 }
217 
218 
219 /*******************************************************
220  * gnc_import_select_account
221  *
222  * Main call for use with a dialog
223  *******************************************************/
224 Account * gnc_import_select_account(GtkWidget *parent,
225  const gchar * account_online_id_value,
226  gboolean auto_create,
227  const gchar * account_human_description,
228  const gnc_commodity * new_account_default_commodity,
229  GNCAccountType new_account_default_type,
230  Account * default_selection,
231  gboolean * ok_pressed)
232 {
233 #define ACCOUNT_DESCRIPTION_MAX_SIZE 255
234  AccountPickerDialog * picker;
235  gint response;
236  Account * retval = NULL;
237  const gchar *retval_name = NULL;
238  GtkBuilder *builder;
239  GtkWidget * online_id_label, *button, *box, *pbox;
240  gchar account_description_text[ACCOUNT_DESCRIPTION_MAX_SIZE] = "";
241  gboolean ok_pressed_retval = FALSE;
242 
243  ENTER("Default commodity received: %s", gnc_commodity_get_fullname( new_account_default_commodity));
244  DEBUG("Default account type received: %s", xaccAccountGetTypeStr( new_account_default_type));
245  picker = g_new0(AccountPickerDialog, 1);
246 
247  picker->account_online_id_value = account_online_id_value;
248  picker->account_human_description = account_human_description;
249  picker->new_account_default_commodity = new_account_default_commodity;
250  picker->new_account_default_type = new_account_default_type;
251 
252  /*DEBUG("Looking for account with online_id: \"%s\"", account_online_id_value);*/
253  if (account_online_id_value != NULL)
254  {
255  retval =
256  gnc_account_foreach_descendant_until(gnc_get_current_root_account (),
257  test_acct_online_id_match,
258  /* This argument will only be used as a "const char*" */
259  (void*)account_online_id_value);
260 
261  /* BEGIN: try again without extra space at the end */
262  /*
263  * libofx, used for file import, generates online_id as
264  * ACCTID + space + ACCTKEY which differs from the online_id
265  * generated by aqbanking for online ofx transfer as ACCTID.
266  *
267  * If a gnucash account has been associated with an online_id
268  * via aqbanking data, it is not possible to construct an OFX
269  * file for gnucash import that matches the same online_id
270  * because even with no ACCTKEY in the file, there will be a
271  * trailing space.
272  *
273  * This is a hack to overcome that problem.
274  */
275  if ((retval == NULL) && g_str_has_suffix(account_online_id_value, " "))
276  {
277  gchar *trimmed = g_strndup(account_online_id_value, strlen(account_online_id_value) - 1);
278  if (trimmed)
279  {
281  gnc_get_current_root_account (),
282  test_acct_online_id_match,
283  (void *)trimmed);
284  }
285  g_free(trimmed);
286  }
287  /* END: try again without extra space at the end */
288  }
289  if (retval == NULL && auto_create != 0)
290  {
291  /* load the interface */
292  builder = gtk_builder_new();
293  gnc_builder_add_from_file (builder, "dialog-import.glade", "account_picker");
294  gnc_builder_add_from_file (builder, "dialog-import.glade", "account_picker_content");
295  /* connect the signals in the interface */
296  if (builder == NULL)
297  {
298  PERR("Error opening the glade builder interface");
299  }
300  picker->dialog = GTK_WIDGET(gtk_builder_get_object (builder, "account_picker"));
301  if (parent)
302  gtk_window_set_transient_for (GTK_WINDOW (picker->dialog),
303  GTK_WINDOW (parent));
304 
305  /* Pack the content into the dialog vbox */
306  pbox = GTK_WIDGET(gtk_builder_get_object (builder, "account_picker_vbox"));
307  box = GTK_WIDGET(gtk_builder_get_object (builder, "account_picker_content"));
308  gtk_box_pack_start( GTK_BOX(pbox), box, TRUE, TRUE, 0);
309 
310  picker->account_tree_sw = GTK_WIDGET(gtk_builder_get_object (builder, "account_tree_sw"));
311  online_id_label = GTK_WIDGET(gtk_builder_get_object (builder, "online_id_label"));
312  button = GTK_WIDGET(gtk_builder_get_object (builder, "newbutton"));
313  gtk_button_set_use_stock (GTK_BUTTON(button), TRUE);
314 
315  //printf("gnc_import_select_account(): Fin get widget\n");
316 
317  if (account_human_description != NULL)
318  {
319  strncat(account_description_text, account_human_description,
320  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
321  strncat(account_description_text, "\n",
322  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
323  }
324  if (account_online_id_value != NULL)
325  {
326  strncat(account_description_text, _("(Full account ID: "),
327  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
328  strncat(account_description_text, account_online_id_value,
329  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
330  strncat(account_description_text, ")",
331  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
332  }
333  gtk_label_set_text((GtkLabel*)online_id_label, account_description_text);
334  build_acct_tree(picker);
335  gnc_tree_view_account_set_selected_account(picker->account_tree, default_selection);
336 
337  gtk_window_set_modal(GTK_WINDOW(picker->dialog), TRUE);
338  g_signal_connect(picker->account_tree, "row-activated",
339  G_CALLBACK(account_tree_row_activated_cb), picker);
340  do
341  {
342  response = gtk_dialog_run(GTK_DIALOG(picker->dialog));
343  switch (response)
344  {
345  case GTK_RESPONSE_OK:
346  retval = gnc_tree_view_account_get_selected_account(picker->account_tree);
347  if (retval)
348  retval_name = xaccAccountGetName(retval);
349  if (!retval_name)
350  retval_name = "(null)";
351  DEBUG("Selected account %p, %s", retval, retval_name);
352 
353  /* See if the selected account is a placeholder. */
354  if (retval && xaccAccountGetPlaceholder (retval))
355  {
356  gnc_error_dialog
357  (picker->dialog,
358  _("The account %s is a placeholder account and does not allow "
359  "transactions. Please choose a different account."),
360  retval_name);
361  response = GNC_RESPONSE_NEW;
362  break;
363  }
364 
365  if ( account_online_id_value != NULL)
366  {
367  gnc_import_set_acc_online_id(retval, account_online_id_value);
368  }
369  ok_pressed_retval = TRUE;
370  break;
371  case GNC_RESPONSE_NEW:
372  gnc_import_add_account(NULL, picker);
373  ok_pressed_retval = TRUE;
374  break;
375  default:
376  ok_pressed_retval = FALSE;
377  break;
378  }
379  }
380  while (response == GNC_RESPONSE_NEW);
381 
382  g_object_unref(G_OBJECT(builder));
383  gtk_widget_destroy(picker->dialog);
384  }
385  else
386  {
387  retval_name = retval ? xaccAccountGetName(retval) : NULL;
388  ok_pressed_retval = TRUE; /* There was no dialog involved, so the computer "pressed" ok */
389  }
390  /*FIXME: DEBUG("WRITEME: gnc_import_select_account() Here we should check if account type is compatible, currency matches, etc.\n"); */
391  g_free(picker);
392  /*DEBUG("Return value: %p%s%s%s",retval,", account name:",xaccAccountGetName(retval),"\n");*/
393  if (ok_pressed != NULL)
394  {
395  *ok_pressed = ok_pressed_retval;
396  }
397  LEAVE("Selected account %p, %s", retval, retval_name ? retval_name : "(null)");
398  return retval;
399 }
400 
401 
402 /**********************************************************************
403  * These are the routines for use with an Assistant page
404  **********************************************************************/
405 
406 /*******************************************************
407  * gnc_import_account_assist_setup
408  *
409  * Main call for page setup in an assistant
410  *******************************************************/
412 {
413  AccountPickerDialog * picker;
414  GtkBuilder *builder;
415  GtkWidget *button, *box, *h_box;
416 
417  /* Init the account picker structure */
418  picker = gnc_import_new_account_picker();
419 
420  /* load the interface */
421  builder = gtk_builder_new();
422  gnc_builder_add_from_file (builder, "dialog-import.glade", "account_picker_content");
423  /* connect the signals in the interface */
424  if (builder == NULL)
425  {
426  PERR("Error opening the glade builder interface");
427  }
428 
429  picker->assistant = gtk_widget_get_parent(parent);
430  /* Pack content into Assistant page widget */
431  box = GTK_WIDGET(gtk_builder_get_object (builder, "account_picker_content"));
432  gtk_box_pack_start( GTK_BOX(parent), box, TRUE, TRUE, 6);
433 
434  picker->account_tree_sw = GTK_WIDGET(gtk_builder_get_object (builder, "account_tree_sw"));
435  picker->account_online_id_label = GTK_WIDGET(gtk_builder_get_object (builder, "online_id_label"));
436 
437  /* Add the New Account Button */
438  button = gtk_button_new_with_mnemonic ("_New Account");
439  h_box = gtk_hbox_new(TRUE, 0);
440  gtk_box_pack_start(GTK_BOX(h_box), button, FALSE, FALSE, 0);
441  gtk_box_pack_start( GTK_BOX(box), h_box, FALSE, FALSE, 6);
442  gtk_button_set_use_stock (GTK_BUTTON(button), TRUE);
443  gtk_widget_show (button);
444  g_signal_connect(button, "clicked",
445  G_CALLBACK(gnc_import_add_account), picker);
446 
447  build_acct_tree(picker);
448 
449  g_signal_connect(picker->account_tree, "row-activated",
450  G_CALLBACK(account_tree_row_activated_cb), picker);
451 
452  g_object_unref(G_OBJECT(builder));
453  return picker;
454 }
455 
456 
457 /*******************************************************
458  * gnc_import_account_assist_update
459  *
460  * updates the page and returns account found.
461  *******************************************************/
463 {
464 #define ACCOUNT_DESCRIPTION_MAX_SIZE 255
465 
466  const gchar *retval_name = NULL;
467  gchar account_description_text[ACCOUNT_DESCRIPTION_MAX_SIZE] = "";
468 
469  ENTER("Default commodity received: %s", gnc_commodity_get_fullname( picker->new_account_default_commodity));
470  DEBUG("Default account type received: %s", xaccAccountGetTypeStr( picker->new_account_default_type));
471 
472  /*DEBUG("Looking for account with online_id: %s", picker->account_online_id_value);*/
473  if (picker->account_online_id_value != NULL)
474  {
475  picker->retAccount =
476  gnc_account_foreach_descendant_until(gnc_get_current_root_account (),
477  test_acct_online_id_match,
478  /* This argument will only be used as a "const char*" */
479  (void*)picker->account_online_id_value);
480  }
481 
482  if (picker->account_human_description != NULL)
483  {
484  strncat(account_description_text, picker->account_human_description,
485  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
486  strncat(account_description_text, "\n",
487  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
488  }
489  if (picker->account_online_id_value != NULL)
490  {
491  strncat(account_description_text, _("(Full account ID: "),
492  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
493  strncat(account_description_text, picker->account_online_id_value,
494  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
495  strncat(account_description_text, ")",
496  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
497  }
498  gtk_label_set_text(GTK_LABEL( picker->account_online_id_label), account_description_text);
499 
500  if (picker->default_account == NULL)
501  gnc_tree_view_account_set_selected_account(picker->account_tree, picker->retAccount);
502  else
503  gnc_tree_view_account_set_selected_account(picker->account_tree, picker->default_account);
504 
505  /*FIXME: DEBUG("WRITEME: Here we should check if an account type is compatible, currency matches, etc.\n"); */
506 
507  /*DEBUG("Return value: %p%s%s%s",picker->retAccount,", account name:",xaccAccountGetName(picker->retAccount),"\n");*/
508  retval_name = picker->retAccount ? xaccAccountGetName(picker->retAccount) : NULL;
509  LEAVE("Selected account %p, %s", picker->retAccount, retval_name ? retval_name : "(null)");
510  return picker->retAccount;
511 }
512 
GtkTreeViewColumn * gnc_tree_view_account_add_property_column(GncTreeViewAccount *view, const gchar *column_title, const gchar *propname)
Dialog for create/edit an account.
#define DEBUG(format, args...)
Definition: qoflog.h:255
gpointer gnc_account_foreach_descendant_until(const Account *acc, AccountCb2 thunk, gpointer user_data)
Definition: Account.c:2979
Account * gnc_import_select_account(GtkWidget *parent, const gchar *account_online_id_value, gboolean auto_create, const gchar *account_human_description, const gnc_commodity *new_account_default_commodity, GNCAccountType new_account_default_type, Account *default_selection, gboolean *ok_pressed)
Generic and very flexible account matcher/picker.
#define PERR(format, args...)
Definition: qoflog.h:237
GtkTreeViewColumn * gnc_tree_view_find_column_by_name(GncTreeView *view, const gchar *wanted)
#define ENTER(format, args...)
Definition: qoflog.h:261
GtkTreeView implementation for gnucash account tree.
GtkTreeView * gnc_tree_view_account_new(gboolean show_root)
void gnc_tree_view_configure_columns(GncTreeView *view)
const char * gnc_commodity_get_fullname(const gnc_commodity *cm)
All type declarations for the whole Gnucash engine.
GNCAccountType
Definition: Account.h:96
void gnc_tree_view_account_set_selected_account(GncTreeViewAccount *view, Account *account)
AccountPickerDialog * gnc_import_account_assist_setup(GtkWidget *parent)
gboolean xaccAccountGetPlaceholder(const Account *acc)
Definition: Account.c:3912
Account * gnc_tree_view_account_get_selected_account(GncTreeViewAccount *view)
Utility functions for writing import modules.
#define LEAVE(format, args...)
Definition: qoflog.h:271
Account * gnc_ui_new_accounts_from_name_with_defaults(const char *name, GList *valid_types, const gnc_commodity *default_commodity, Account *parent)
const char * xaccAccountGetName(const Account *acc)
Definition: Account.c:3031
Account * gnc_import_account_assist_update(AccountPickerDialog *picker)
const char * xaccAccountGetTypeStr(GNCAccountType type)
Definition: Account.c:4137
Commodity handling public routines.
const gchar * QofLogModule
Definition: qofid.h:89