GnuCash  5.6-150-g038405b370+
import-account-matcher.cpp
1 /********************************************************************\
2  * import-account-matcher.c - flexible account picker/matcher *
3  * *
4  * Copyright (C) 2002 Benoit GrĂ©goire <bock@step.polymtl.ca> *
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 gnu@gnu.org *
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-prefs.h"
44 #include "gnc-tree-view-account.h"
45 #include "gnc-ui.h"
46 
47 static QofLogModule log_module = GNC_MOD_IMPORT;
48 
49 #define STATE_SECTION "dialogs/import/generic_matcher/account_matcher"
50 
51 #define GNC_PREFS_GROUP "dialogs.import.generic.account-picker"
52 
53 typedef struct
54 {
55  Account* partial_match;
56  int count;
57  const char* online_id;
59 
60 /*-******************************************************************\
61  * Functions needed by gnc_import_select_account
62  *
63 \********************************************************************/
64 
65 /**************************************************
66  * test_acct_online_id_match
67  *
68  * test for match of account online_ids.
69  **************************************************/
70 static gpointer test_acct_online_id_match(Account *acct, gpointer data)
71 {
72  AccountOnlineMatch *match = (AccountOnlineMatch*)data;
73  char *acct_online_id = gnc_import_get_acc_online_id(acct);
74  int acct_len, match_len;
75 
76  if (acct_online_id == NULL || match->online_id == NULL)
77  {
78  if (acct_online_id)
79  g_free (acct_online_id);
80  return NULL;
81  }
82 
83  acct_len = strlen(acct_online_id);
84  match_len = strlen(match->online_id);
85 
86  if (acct_online_id[acct_len - 1] == ' ')
87  --acct_len;
88  if (match->online_id[match_len - 1] == ' ')
89  --match_len;
90 
91  if (strncmp (acct_online_id, match->online_id, acct_len) == 0)
92  {
93  if (strncmp(acct_online_id, match->online_id, match_len) == 0)
94  {
95  g_free (acct_online_id);
96  return (gpointer *) acct;
97  }
98  if (match->partial_match == NULL)
99  {
100  match->partial_match = acct;
101  ++match->count;
102  }
103  else
104  {
105  char *partial_online_id =
106  gnc_import_get_acc_online_id(match->partial_match);
107  int partial_len = strlen(partial_online_id);
108  if (partial_online_id[partial_len - 1] == ' ')
109  --partial_len;
110  /* Both partial_online_id and acct_online_id are substrings of
111  * match->online_id, but whichever is longer is the better match.
112  * Reset match->count to 1 just in case there was ambiguity on the
113  * shorter partial match.
114  */
115  if (partial_len < acct_len)
116  {
117  match->partial_match = acct;
118  match->count = 1;
119  }
120  /* If they're the same size then there are two accounts with the
121  * same online id and we don't know which one to select. Increment
122  * match->count to dissuade gnc_import_find_account from using
123  * match->online_id and log an error.
124  */
125  else if (partial_len == acct_len)
126  {
127  gchar *name1, *name2;
128  ++match->count;
129  name1 = gnc_account_get_full_name (match->partial_match);
130  name2 = gnc_account_get_full_name (acct);
131  PERR("Accounts %s and %s have the same online-id %s",
132  name1, name2, partial_online_id);
133  g_free (name1);
134  g_free (name2);
135  }
136  g_free (partial_online_id);
137  }
138  }
139 
140  g_free (acct_online_id);
141  return NULL;
142 }
143 
144 
145 /***********************************************************
146  * build_acct_tree
147  *
148  * build the account tree with the custom column, online_id
149  ************************************************************/
150 static void
151 build_acct_tree(AccountPickerDialog *picker)
152 {
153  GtkTreeView *account_tree;
154  GtkTreeViewColumn *col;
155 
156  /* Build a new account tree */
157  DEBUG("Begin");
158  account_tree = gnc_tree_view_account_new(FALSE);
159  picker->account_tree = GNC_TREE_VIEW_ACCOUNT(account_tree);
160  gtk_tree_view_set_headers_visible (account_tree, TRUE);
161  col = gnc_tree_view_find_column_by_name(GNC_TREE_VIEW(account_tree), "type");
162  g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
163 
164  /* Add our custom column. */
165  col = gnc_tree_view_account_add_property_column (picker->account_tree,
166  _("Account ID"), "online-id");
167  g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
168 
169  // the color background data function is part of the add_property_column
170  // function which will color the background based on preference setting
171 
172  gtk_container_add(GTK_CONTAINER(picker->account_tree_sw),
173  GTK_WIDGET(picker->account_tree));
174 
175  /* Configure the columns */
176  gnc_tree_view_configure_columns (GNC_TREE_VIEW(picker->account_tree));
177  g_object_set(account_tree,
178  "state-section", STATE_SECTION,
179  "show-column-menu", TRUE,
180  (gchar*) NULL);
181 }
182 
183 
184 /*******************************************************
185  * gnc_import_add_account
186  *
187  * Callback for when user clicks to create a new account
188  *******************************************************/
189 static void
190 gnc_import_add_account(GtkWidget *button, AccountPickerDialog *picker)
191 {
192  Account *selected_account, *new_account;
193  GList * valid_types = NULL;
194  GtkWindow *parent = NULL;
195 
196  if (picker->dialog != NULL)
197  parent = GTK_WINDOW (picker->dialog);
198 
199  /*DEBUG("Begin"); */
200  if (picker->new_account_default_type != ACCT_TYPE_NONE)
201  {
202  /*Yes, this is weird, but we really DO want to pass the value instead of the pointer...*/
203  valid_types = g_list_prepend(valid_types, GINT_TO_POINTER(picker->new_account_default_type));
204  }
205  selected_account = gnc_tree_view_account_get_selected_account(picker->account_tree);
206  new_account = gnc_ui_new_accounts_from_name_with_defaults (parent,
207  picker->account_human_description,
208  valid_types,
209  picker->new_account_default_commodity,
210  selected_account);
211  g_list_free(valid_types);
212  gnc_tree_view_account_set_selected_account(picker->account_tree, new_account);
213 }
214 
215 
216 /***********************************************************
217  * show_warning
218  *
219  * show the warning and disable OK button
220  ************************************************************/
221 static void
222 show_warning (AccountPickerDialog *picker, gchar *text)
223 {
224  gtk_label_set_text (GTK_LABEL(picker->warning), text);
225  gnc_label_set_alignment (picker->warning, 0.0, 0.5);
226  gtk_widget_show_all (GTK_WIDGET(picker->whbox));
227  g_free (text);
228 
229  gtk_widget_set_sensitive (picker->ok_button, FALSE); // disable OK button
230 }
231 
232 
233 /***********************************************************
234  * show_placeholder_warning
235  *
236  * show the warning when account is a place holder
237  ************************************************************/
238 static void
239 show_placeholder_warning (AccountPickerDialog *picker, const gchar *name)
240 {
241  gchar *text = g_strdup_printf (_("The account '%s' is a placeholder account and does not allow "
242  "transactions. Please choose a different account."), name);
243 
244  show_warning (picker, text);
245 }
246 
247 
248 /*******************************************************
249  * account_tree_row_changed_cb
250  *
251  * Callback for when user selects a different row
252  *******************************************************/
253 static void
254 account_tree_row_changed_cb (GtkTreeSelection *selection,
255  AccountPickerDialog *picker)
256 {
257  Account *sel_account = gnc_tree_view_account_get_selected_account (picker->account_tree);
258 
259  /* Reset buttons and warnings */
260  gtk_widget_hide (GTK_WIDGET(picker->whbox));
261  gtk_widget_set_sensitive (picker->ok_button, (sel_account != NULL));
262 
263  /* See if the selected account is a placeholder. */
264  if (sel_account && xaccAccountGetPlaceholder (sel_account))
265  {
266  const gchar *retval_name = xaccAccountGetName (sel_account);
267  show_placeholder_warning (picker, retval_name);
268  }
269 }
270 
271 static gboolean
272 account_tree_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
273 {
274  // Expand the tree when the user starts typing, this will allow sub-accounts to be found.
275  if (event->length == 0)
276  return FALSE;
277 
278  switch (event->keyval)
279  {
280  case GDK_KEY_plus:
281  case GDK_KEY_minus:
282  case GDK_KEY_asterisk:
283  case GDK_KEY_slash:
284  case GDK_KEY_KP_Add:
285  case GDK_KEY_KP_Subtract:
286  case GDK_KEY_KP_Multiply:
287  case GDK_KEY_KP_Divide:
288  case GDK_KEY_Up:
289  case GDK_KEY_KP_Up:
290  case GDK_KEY_Down:
291  case GDK_KEY_KP_Down:
292  case GDK_KEY_Home:
293  case GDK_KEY_KP_Home:
294  case GDK_KEY_End:
295  case GDK_KEY_KP_End:
296  case GDK_KEY_Page_Up:
297  case GDK_KEY_KP_Page_Up:
298  case GDK_KEY_Page_Down:
299  case GDK_KEY_KP_Page_Down:
300  case GDK_KEY_Right:
301  case GDK_KEY_Left:
302  case GDK_KEY_KP_Right:
303  case GDK_KEY_KP_Left:
304  case GDK_KEY_space:
305  case GDK_KEY_KP_Space:
306  case GDK_KEY_backslash:
307  case GDK_KEY_Return:
308  case GDK_KEY_ISO_Enter:
309  case GDK_KEY_KP_Enter:
310  return FALSE;
311  break;
312  default:
313  gtk_tree_view_expand_all (GTK_TREE_VIEW(user_data));
314  return FALSE;
315  }
316  return FALSE;
317 }
318 
319 
320 /*******************************************************
321  * account_tree_row_activated_cb
322  *
323  * Callback for when user double clicks on an account
324  *******************************************************/
325 static void
326 account_tree_row_activated_cb(GtkTreeView *view, GtkTreePath *path,
327  GtkTreeViewColumn *column,
328  AccountPickerDialog *picker)
329 {
330  gtk_dialog_response(GTK_DIALOG(picker->dialog), GTK_RESPONSE_OK);
331 }
332 
333 
334 /*******************************************************
335  * gnc_import_select_account
336  *
337  * Main call for use with a dialog
338  *******************************************************/
339 Account * gnc_import_select_account(GtkWidget *parent,
340  const gchar * account_online_id_value,
341  gboolean prompt_on_no_match,
342  const gchar * account_human_description,
343  const gnc_commodity * new_account_default_commodity,
344  GNCAccountType new_account_default_type,
345  Account * default_selection,
346  gboolean * ok_pressed)
347 {
348 #define ACCOUNT_DESCRIPTION_MAX_SIZE 255
349  AccountPickerDialog * picker;
350  gint response;
351  Account * retval = NULL;
352  const gchar *retval_name = NULL;
353  GtkBuilder *builder;
354  GtkTreeSelection *selection;
355  GtkWidget * online_id_label;
356  gchar account_description_text[ACCOUNT_DESCRIPTION_MAX_SIZE + 1] = "";
357  gboolean ok_pressed_retval = FALSE;
358 
359  ENTER("Default commodity received: %s", gnc_commodity_get_fullname( new_account_default_commodity));
360  DEBUG("Default account type received: %s", xaccAccountGetTypeStr( new_account_default_type));
361  picker = g_new0(AccountPickerDialog, 1);
362 
363  picker->account_human_description = account_human_description;
364  picker->new_account_default_commodity = new_account_default_commodity;
365  picker->new_account_default_type = new_account_default_type;
366 
367  /*DEBUG("Looking for account with online_id: \"%s\"", account_online_id_value);*/
368  if (account_online_id_value)
369  {
370  AccountOnlineMatch match = {NULL, 0, account_online_id_value};
371  retval = static_cast<Account*>(gnc_account_foreach_descendant_until (gnc_get_current_root_account (),
372  test_acct_online_id_match,
373  (void*)&match));
374  if (!retval && match.count == 1 &&
375  new_account_default_type == ACCT_TYPE_NONE)
376  retval = match.partial_match;
377  }
378  if (!retval && prompt_on_no_match)
379  {
380  /* load the interface */
381  builder = gtk_builder_new();
382  gnc_builder_add_from_file (builder, "dialog-import.glade", "account_new_icon");
383  gnc_builder_add_from_file (builder, "dialog-import.glade", "account_picker_dialog");
384  /* connect the signals in the interface */
385  if (builder == NULL)
386  {
387  PERR("Error opening the glade builder interface");
388  }
389  picker->dialog = GTK_WIDGET(gtk_builder_get_object (builder, "account_picker_dialog"));
390  picker->whbox = GTK_WIDGET(gtk_builder_get_object (builder, "warning_hbox"));
391  picker->warning = GTK_WIDGET(gtk_builder_get_object (builder, "warning_label"));
392  picker->ok_button = GTK_WIDGET(gtk_builder_get_object (builder, "okbutton"));
393 
394  // Set the name for this dialog so it can be easily manipulated with css
395  gtk_widget_set_name (GTK_WIDGET(picker->dialog), "gnc-id-import-account-picker");
396  gnc_widget_style_context_add_class (GTK_WIDGET(picker->dialog), "gnc-class-imports");
397 
398  if (parent)
399  gtk_window_set_transient_for (GTK_WINDOW (picker->dialog),
400  GTK_WINDOW (parent));
401 
402  gnc_restore_window_size (GNC_PREFS_GROUP,
403  GTK_WINDOW(picker->dialog), GTK_WINDOW (parent));
404 
405  picker->account_tree_sw = GTK_WIDGET(gtk_builder_get_object (builder, "account_tree_sw"));
406  online_id_label = GTK_WIDGET(gtk_builder_get_object (builder, "online_id_label"));
407 
408  //printf("gnc_import_select_account(): Fin get widget\n");
409 
410  if (account_human_description != NULL)
411  {
412  strncat(account_description_text, account_human_description,
413  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
414  strncat(account_description_text, "\n",
415  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
416  }
417  if (account_online_id_value != NULL)
418  {
419  strncat(account_description_text, _("(Full account ID: "),
420  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
421  strncat(account_description_text, account_online_id_value,
422  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
423  strncat(account_description_text, ")",
424  ACCOUNT_DESCRIPTION_MAX_SIZE - strlen(account_description_text));
425  }
426  gtk_label_set_text((GtkLabel*)online_id_label, account_description_text);
427  build_acct_tree(picker);
428  gtk_window_set_modal(GTK_WINDOW(picker->dialog), TRUE);
429  g_signal_connect(picker->account_tree, "row-activated",
430  G_CALLBACK(account_tree_row_activated_cb), picker);
431 
432  /* Connect key press event so we can expand the tree when the user starts typing, allowing
433  * any subaccount to match */
434  g_signal_connect (picker->account_tree, "key-press-event", G_CALLBACK (account_tree_key_press_cb), picker->account_tree);
435 
436  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(picker->account_tree));
437  g_signal_connect(selection, "changed",
438  G_CALLBACK(account_tree_row_changed_cb), picker);
439 
440  gnc_tree_view_account_set_selected_account(picker->account_tree, default_selection);
441 
442  do
443  {
444  response = gtk_dialog_run(GTK_DIALOG(picker->dialog));
445  switch (response)
446  {
447  case GNC_RESPONSE_NEW:
448  gnc_import_add_account(NULL, picker);
449  response = GTK_RESPONSE_OK;
450  /* no break */
451 
452  case GTK_RESPONSE_OK:
453  retval = gnc_tree_view_account_get_selected_account(picker->account_tree);
454  if (!retval)
455  {
456  response = GNC_RESPONSE_NEW;
457  break;
458  }
459  retval_name = xaccAccountGetName(retval);
460  DEBUG("Selected account %p, %s", retval, retval_name ? retval_name : "(null)");
461 
462  /* See if the selected account is a placeholder. */
463  if (retval && xaccAccountGetPlaceholder (retval))
464  {
465  show_placeholder_warning (picker, retval_name);
466  response = GNC_RESPONSE_NEW;
467  break;
468  }
469 
470  if (account_online_id_value)
471  {
472  gnc_import_set_acc_online_id(retval, account_online_id_value);
473  }
474  ok_pressed_retval = TRUE;
475  break;
476 
477  default:
478  ok_pressed_retval = FALSE;
479  break;
480  }
481  }
482  while (response == GNC_RESPONSE_NEW);
483 
484  g_object_unref(G_OBJECT(builder));
485  gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(picker->dialog));
486  gtk_widget_destroy(picker->dialog);
487  }
488  else
489  {
490  retval_name = retval ? xaccAccountGetName(retval) : NULL;
491  ok_pressed_retval = TRUE; /* There was no dialog involved, so the computer "pressed" ok */
492  }
493  /*FIXME: DEBUG("WRITEME: gnc_import_select_account() Here we should check if account type is compatible, currency matches, etc.\n"); */
494  g_free(picker);
495  /*DEBUG("Return value: %p%s%s%s",retval,", account name:",xaccAccountGetName(retval),"\n");*/
496  if (ok_pressed != NULL)
497  {
498  *ok_pressed = ok_pressed_retval;
499  }
500  LEAVE("Selected account %p, %s", retval, retval_name ? retval_name : "(null)");
501  return retval;
502 }
503 
GtkTreeViewColumn * gnc_tree_view_account_add_property_column(GncTreeViewAccount *view, const gchar *column_title, const gchar *propname)
Add a new column to the set of columns in an account tree view.
This file contains the functions to present a gui to the user for creating a new account or editing a...
STRUCTS.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
gpointer gnc_account_foreach_descendant_until(const Account *acc, AccountCb2 thunk, gpointer user_data)
This method will traverse all children of this accounts and their descendants, calling &#39;func&#39; on each...
Definition: Account.cpp:3189
Account * gnc_import_select_account(GtkWidget *parent, const gchar *account_online_id_value, gboolean prompt_on_no_match, const gchar *account_human_description, const gnc_commodity *new_account_default_commodity, GNCAccountType new_account_default_type, Account *default_selection, gboolean *ok_pressed)
Must be called with a string containing a unique identifier for the account.
Generic and very flexible account matcher/picker.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
GtkTreeViewColumn * gnc_tree_view_find_column_by_name(GncTreeView *view, const gchar *wanted)
Find a tree column given the "pref name" used with saved state.
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
gchar * gnc_account_get_full_name(const Account *account)
The gnc_account_get_full_name routine returns the fully qualified name of the account using the given...
Definition: Account.cpp:3241
GtkTreeView implementation for gnucash account tree.
GtkTreeView * gnc_tree_view_account_new(gboolean show_root)
Create a new account tree view.
void gnc_tree_view_configure_columns(GncTreeView *view)
Make all the correct columns visible, respecting their default visibility setting, their "always" visibility setting, and the last saved state if available.
const char * gnc_commodity_get_fullname(const gnc_commodity *cm)
Retrieve the full name for the specified commodity.
All type declarations for the whole Gnucash engine.
GNCAccountType
The account types are used to determine how the transaction data in the account is displayed...
Definition: Account.h:101
Generic api to store and retrieve preferences.
void gnc_tree_view_account_set_selected_account(GncTreeViewAccount *view, Account *account)
This function selects an account in the account tree view.
Account * gnc_ui_new_accounts_from_name_with_defaults(GtkWindow *parent, const char *name, GList *valid_types, const gnc_commodity *default_commodity, Account *parent_acct)
Display a modal window for creating a new account.
gboolean xaccAccountGetPlaceholder(const Account *acc)
Get the "placeholder" flag for an account.
Definition: Account.cpp:4179
Account * gnc_tree_view_account_get_selected_account(GncTreeViewAccount *view)
This function returns the account associated with the selected item in the account tree view...
Utility functions for writing import modules.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
const char * xaccAccountGetName(const Account *acc)
Get the account&#39;s name.
Definition: Account.cpp:3234
const char * xaccAccountGetTypeStr(GNCAccountType type)
The xaccAccountGetTypeStr() routine returns a string suitable for use in the GUI/Interface.
Definition: Account.cpp:4430
Not a type.
Definition: Account.h:105
Commodity handling public routines.