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