GnuCash c935c2f+
Loading...
Searching...
No Matches
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
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"
44#include "gnc-ui.h"
45
46static 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
52typedef 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 **************************************************/
69static gpointer test_acct_online_id_match(Account *acct, gpointer data)
70{
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 ************************************************************/
140static void
141build_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 *******************************************************/
179static void
180gnc_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);
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 ************************************************************/
211static void
212show_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 ************************************************************/
228static void
229show_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 *******************************************************/
243static void
244account_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
261static gboolean
262account_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 *******************************************************/
315static void
316account_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 *******************************************************/
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
This file contains the functions to present a gui to the user for creating a new account or editing a...
Commodity handling public routines.
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
GtkTreeView implementation for gnucash account tree.
const char * xaccAccountGetName(const Account *acc)
Get the account's name.
Definition Account.cpp:3289
void xaccAccountSetOnlineID(Account *acc, const char *id)
Set the account's online_id, the identifier (e.g.
Definition Account.cpp:2661
GNCAccountType
The account types are used to determine how the transaction data in the account is displayed.
Definition Account.h:103
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:3305
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 'func' on each...
Definition Account.cpp:3244
const char * xaccAccountGetOnlineID(const Account *acc)
Get the account's online_id (see xaccAccountSetOnlineID).
Definition Account.cpp:3380
@ ACCT_TYPE_NONE
Not a type.
Definition Account.h:105
const char * gnc_commodity_get_fullname(const gnc_commodity *cm)
Retrieve the full name for the specified commodity.
gboolean xaccAccountGetPlaceholder(const Account *acc)
Get the "placeholder" flag for an account.
Definition Account.cpp:4119
const char * xaccAccountGetTypeStr(GNCAccountType type)
The xaccAccountGetTypeStr() routine returns a string suitable for use in the GUI/Interface.
Definition Account.cpp:4357
void gnc_tree_view_configure_columns(GncTreeView *view)
Make all the correct columns visible, respecting their default visibility setting,...
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.
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.
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.
GtkTreeView * gnc_tree_view_account_new(gboolean show_root)
Create a new account tree view.
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.
void gnc_tree_view_account_set_selected_account(GncTreeViewAccount *view, Account *account)
This function selects an account in the account tree view.
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.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
Generic and very flexible account matcher/picker.
STRUCTS.