GnuCash c935c2f+
Loading...
Searching...
No Matches
assistant-hierarchy.cpp
1/********************************************************************\
2 * assistant-hierarchy.c -- account hierarchy creation functionality*
3 * Copyright (C) 2001 Gnumatic, Inc. *
4 * Copyright (C) 2006 David Hampton <hampton@employees.org> *
5 * Copyright (C) 2010 Geert Janssens *
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 <gtk/gtk.h>
26#include <glib/gi18n.h>
27#include <glib/gstdio.h>
28#include <dialog-options.hpp>
29#include <gnc-optiondb.h>
30#include <libguile.h>
31
32#include <config.h>
33
34#include <platform.h>
35#if PLATFORM(WINDOWS)
36#include <windows.h>
37#endif
38
39#include <sys/stat.h>
40#include <sys/types.h>
41#include <unistd.h>
42#ifdef MAC_INTEGRATION
43#include <Foundation/Foundation.h>
44#endif
45#include "gnc-account-merge.h"
46#include "dialog-new-user.h"
47#include "dialog-utils.h"
48#include "dialog-file-access.h"
49#include "assistant-hierarchy.h"
50#include "gnc-amount-edit.h"
51#include "gnc-currency-edit.h"
52#include "gnc-exp-parser.h"
53#include "gnc-general-select.h"
54#include "gnc-gnome-utils.h"
55#include "gnc-prefs.h"
56#include "gnc-hooks.h"
57#include "gnc-component-manager.h"
58#include "gnc-path.h"
59#include "gnc-gui-query.h"
61#include "gnc-ui.h"
62#include "gnc-ui-util.h"
63#include "io-example-account.h"
64#include "top-level.h"
65#include "gnc-main-window.h"
67
68#include "gnc-engine.h"
69
70static QofLogModule log_module = GNC_MOD_IMPORT;
71
72#define GNC_PREFS_GROUP "dialogs.new-hierarchy"
73#define GNC_PREF_SHOW_ON_NEW_FILE "show-on-new-file"
74#define DIALOG_BOOK_OPTIONS_CM_CLASS "dialog-book-options"
75
76typedef enum
77{
78 COL_CHECKED,
79 COL_TITLE,
80 COL_SHORT_DESCRIPTION,
81 COL_LONG_DESCRIPTION,
82 COL_ACCOUNT,
83 NUM_COLUMNS
84} ColumnNames;
85
86
87typedef struct
88{
89 GtkWidget *dialog;
90 GtkWidget *assistant;
91 gboolean next_ok;
92
93 GtkWidget *currency_selector;
94 GtkWidget *currency_selector_label;
95
96 GtkWidget *language_combo;
97 GtkWidget *region_combo;
98 GtkWidget *region_label;
99
100 gchar *gnc_accounts_dir;
101
102 GtkTreeView *categories_tree;
103 GtkTreeRowReference *initial_category;
104 GtkTextView *category_description;
105 GtkWidget *category_accounts_container;
106 GtkLabel *category_accounts_label;
107 GtkTreeView *category_accounts_tree;
108 gboolean category_set_changed;
109
110 GncTreeViewAccount *final_account_tree;
111 GtkWidget *final_account_tree_container;
112 Account *selected_account;
114 GHashTable *balance_hash;
115
116 Account *our_account_tree;
117 QofBook *temporary;
118
119 gboolean account_list_added;
120 gboolean use_defaults;
121 gboolean new_book; /* presumably only used for new book creation but we check*/
122
123 GncOptionDB *options;
124 GncOptionsDialog *optionwin;
125
126 GncHierarchyAssistantFinishedCallback when_completed;
127
129
130extern "C"
131{
132void on_prepare (GtkAssistant *assistant, GtkWidget *page,
133 hierarchy_data *data);
134
135void on_cancel (GtkAssistant *gtkassistant, hierarchy_data *data);
136void on_finish (GtkAssistant *gtkassistant, hierarchy_data *data);
137
138void select_all_clicked (GtkButton *button,
139 hierarchy_data *data);
140void clear_all_clicked (GtkButton *button,
141 hierarchy_data *data);
142}
143
144void on_choose_account_categories_prepare (hierarchy_data *data);
145void on_final_account_prepare (hierarchy_data *data);
146void on_select_currency_prepare (hierarchy_data *data);
147
148static void add_one_category (GncExampleAccount *acc, hierarchy_data *data);
149static void categories_page_enable_next (hierarchy_data *data);
150static void categories_tree_selection_changed (GtkTreeSelection *selection, hierarchy_data *data);
151// ------------------------------------------------------------
152
153static void
154delete_hierarchy_dialog (hierarchy_data *data)
155{
156 gnc_save_window_size(GNC_PREFS_GROUP, GTK_WINDOW(data->dialog));
157 gtk_widget_destroy (data->dialog);
158}
159
160static void
161destroy_hash_helper (gpointer key, gpointer value, gpointer user_data)
162{
163 auto balance{static_cast<gnc_numeric*>(value)};
164 g_free (balance);
165}
166
167static void
168gnc_hierarchy_destroy_cb (GtkWidget *obj, hierarchy_data *data)
169{
170 GHashTable *hash;
171
172 hash = data->balance_hash;
173 if (hash)
174 {
175 g_hash_table_foreach (hash, destroy_hash_helper, nullptr);
176 g_hash_table_destroy (hash);
177 data->balance_hash = nullptr;
178 }
179
180 g_free (data->gnc_accounts_dir);
181}
182
183static gnc_numeric
184get_final_balance (GHashTable *hash, Account *account)
185{
186 if (!hash || !account)
187 return gnc_numeric_zero ();
188
189 auto balance{static_cast<gnc_numeric*>(g_hash_table_lookup(hash, account))};
190 if (balance)
191 return *balance;
192 return gnc_numeric_zero ();
193}
194
195static void
196set_final_balance (GHashTable *hash, Account *account, gnc_numeric in_balance)
197{
198 if (!hash || !account)
199 return;
200
201 auto balance{static_cast<gnc_numeric*>(g_hash_table_lookup(hash, account))};
202 if (balance)
203 {
204 *balance = in_balance;
205 return;
206 }
207
208 balance = g_new (gnc_numeric, 1);
209 *balance = in_balance;
210 g_hash_table_insert (hash, account, balance);
211}
212
213#ifdef MAC_INTEGRATION
214/* Repeat retrieving the locale from defaults in case it was overridden in
215 * gnucash-bin because it wasn't a supported POSIX locale.
216 */
217static char*
218mac_locale()
219{
220 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
221 NSLocale* locale = [NSLocale currentLocale];
222 NSString* locale_str;
223 char *retval = nullptr;
224 @try
225 {
226 locale_str =[[[locale objectForKey: NSLocaleLanguageCode]
227 stringByAppendingString: @"_"]
228 stringByAppendingString:
229 [locale objectForKey: NSLocaleCountryCode]];
230 }
231 @catch (NSException *err)
232 {
233 locale_str = @"_";
234 }
235/* If we didn't get a valid current locale, the string will be just "_" */
236 if ([locale_str isEqualToString: @"_"])
237 locale_str = @"en_US";
238 retval = g_strdup([locale_str UTF8String]);
239 [pool drain];
240 return retval;
241}
242#endif
243static gchar*
244gnc_get_ea_locale_dir(const char *top_dir)
245{
246 static const char* default_locale = "C";
247 gchar *ret;
248 gchar *locale;
249 GStatBuf buf;
250 int i;
251
252#if PLATFORM(WINDOWS)
253 /* On win32, setlocale() doesn't say anything useful, so we check
254 * g_win32_getlocale(). Unfortunately it checks the value of $LANG first,
255 * and the user might have worked around the absence of sv in gettext's
256 * Microsoft Conversion Array by setting it to "Swedish_Sweden", so first
257 * check that.
258 */
259 const gchar *env_locale;
260 env_locale = g_getenv("LANG");
261 if (g_strcmp0(env_locale, "Swedish_Sweden") == 0)
262 locale = g_strdup("sv_SE");
263 else if (g_strcmp0(env_locale, "Swedish_Finland") == 0)
264 locale =g_strdup("sv_FI");
265 else if (g_strcmp0(env_locale, "Swedish_Ă…land Islands") == 0)
266 locale =g_strdup("sv_AX");
267 else
268 {
269 locale = g_win32_getlocale();
270 if (!locale)
271 {
272 PWARN ("Couldn't retrieve locale. Falling back to default one.");
273 locale = g_strdup ("C");
274 }
275 }
276#elif defined MAC_INTEGRATION
277 locale = mac_locale();
278# else
279 locale = g_strdup(setlocale(LC_MESSAGES, nullptr));
280#endif
281
282 i = strlen(locale);
283 ret = g_build_filename(top_dir, locale, (char *)nullptr);
284
285 while (g_stat(ret, &buf) != 0)
286 {
287 i--;
288 if (i < 1)
289 {
290 g_free(ret);
291 ret = g_build_filename(top_dir, default_locale, (char *)nullptr);
292 break;
293 }
294 locale[i] = '\0';
295 g_free(ret);
296 ret = g_build_filename(top_dir, locale, (char *)nullptr);
297 }
298
299 g_free(locale);
300
301 return ret;
302}
303
304typedef enum
305{
306 LANGUAGE_STRING,
307 REGION_STRING,
308 LANG_REG_STRING,
309 REGION_FILTER
310}GncLanguageRegionCombos;
311
312static void
313region_combo_changed_cb (GtkComboBox *widget, hierarchy_data *data)
314{
315 GtkTreeModel *filter_model = gtk_combo_box_get_model (GTK_COMBO_BOX(data->region_combo));
316 GtkTreeModel *region_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
317 GtkTreeIter filter_iter, region_iter;
318 gchar *lang_reg = nullptr;
319 gchar *account_path = nullptr;
320
321 if (gtk_combo_box_get_active_iter (widget, &filter_iter))
322 {
323 GtkListStore *cat_list = GTK_LIST_STORE(gtk_tree_view_get_model (data->categories_tree));
324 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(data->categories_tree));
325 GSList *list;
326 GtkTreePath *path;
327
328 gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER(filter_model),
329 &region_iter,
330 &filter_iter);
331
332 gtk_tree_model_get (region_model, &region_iter, LANG_REG_STRING, &lang_reg, -1);
333
334 gnc_suspend_gui_refresh ();
335
336 /* Remove the old account tree */
337 if (data->category_accounts_tree)
338 gtk_widget_destroy(GTK_WIDGET(data->category_accounts_tree));
339 data->category_accounts_tree = nullptr;
340
341 // clear the categories list store in prep for new load
342 if (cat_list)
343 gtk_list_store_clear (cat_list);
344
345 account_path = g_build_filename (data->gnc_accounts_dir, lang_reg, nullptr);
346
348 list = gnc_load_example_account_list (account_path);
350
351 if (data->initial_category)
352 {
353 gtk_tree_row_reference_free (data->initial_category);
354 data->initial_category = nullptr;
355 }
356
357 // repopulate the category list
358 g_slist_foreach (list, (GFunc)add_one_category, data);
359
360 if (data->initial_category)
361 {
362 path = gtk_tree_row_reference_get_path (data->initial_category);
363 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(data->categories_tree),
364 path, nullptr, TRUE, 0.5, 0.5);
365 }
366 else
367 path = gtk_tree_path_new_first ();
368
369 gtk_tree_selection_select_path (selection, path);
370 gtk_tree_path_free (path);
371
372 // now load the account tree
373 categories_tree_selection_changed (selection, data);
374
375 gnc_resume_gui_refresh ();
376
377 g_slist_free (list);
378 }
379 g_free (account_path);
380 g_free (lang_reg);
381}
382
383
384static void
385region_combo_change_filter_cb (GtkComboBox *widget, hierarchy_data *data)
386{
387 GtkTreeModel *filter_model = gtk_combo_box_get_model (GTK_COMBO_BOX(data->region_combo));
388 GtkTreeModel *region_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
389 GtkTreeIter language_iter, region_iter, sorted_iter;
390 gboolean have_one_region = FALSE;
391
392 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX(data->language_combo), &sorted_iter))
393 {
394 GtkTreeModel *sort_model = gtk_combo_box_get_model (GTK_COMBO_BOX(data->language_combo));
395 GtkTreeModel *language_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT(sort_model));
396 GtkTreeIter *iter = nullptr;
397 gchar *language = nullptr;
398 gint count = 0;
399 gboolean valid;
400
401 gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT(sort_model),
402 &language_iter,
403 &sorted_iter);
404
405 gtk_tree_model_get (language_model, &language_iter, LANGUAGE_STRING, &language, -1);
406 valid = gtk_tree_model_get_iter_first (region_model, &region_iter);
407
408 // loop through the regions and filter any out that are not linked to language setting
409 while (valid)
410 {
411 gchar *region_test = nullptr;
412
413 gtk_tree_model_get (region_model, &region_iter,
414 LANGUAGE_STRING, &region_test, -1);
415
416 if (g_strcmp0 (language, region_test) == 0)
417 {
418 gtk_list_store_set (GTK_LIST_STORE(region_model),
419 &region_iter, REGION_FILTER, TRUE, -1);
420 if (count == 0)
421 iter = gtk_tree_iter_copy (&region_iter);
422 count++;
423 }
424 else
425 gtk_list_store_set (GTK_LIST_STORE(region_model),
426 &region_iter, REGION_FILTER, FALSE, -1);
427
428 g_free (region_test);
429
430 valid = gtk_tree_model_iter_next (region_model, &region_iter);
431 }
432
433 // if we only have a language or just one region activate it
434 if (count == 1)
435 {
436 gchar *region_label = nullptr;
437 GtkTreeIter filter_iter;
438 gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER(filter_model),
439 &filter_iter,
440 iter);
441
442 gtk_combo_box_set_active_iter (GTK_COMBO_BOX(data->region_combo), &filter_iter);
443
444 have_one_region = TRUE;
445
446 gtk_tree_model_get (region_model, iter, REGION_STRING, &region_label, -1);
447
448 gtk_label_set_text (GTK_LABEL(data->region_label), region_label);
449 g_free (region_label);
450 }
451 else
452 {
453 // if the combo is not already active, set it to first on in filtered list
454 if (gtk_combo_box_get_active (GTK_COMBO_BOX(data->region_combo)) == -1)
455 {
456 GtkTreeIter filter_iter;
457
458 gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER(filter_model),
459 &filter_iter,
460 iter);
461
462 gtk_combo_box_set_active_iter (GTK_COMBO_BOX(data->region_combo), &filter_iter);
463 }
464 }
465 gtk_widget_set_visible (GTK_WIDGET(data->region_label), have_one_region);
466 gtk_widget_set_visible (GTK_WIDGET(data->region_combo), !have_one_region);
467
468 gtk_tree_iter_free (iter);
469 g_free (language);
470 }
471}
472
473
474static void
475update_language_region_combos (hierarchy_data *data, const gchar *locale_dir)
476{
477 GtkListStore *language_store = gtk_list_store_new (1, G_TYPE_STRING);
478 GtkListStore *region_store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
479 GtkTreeModel *filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL(region_store), nullptr);
480 GtkTreeModel *sort_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(language_store));
481 GtkTreeIter language_iter, region_iter;
482 gchar *start_region = nullptr;
483 gboolean valid;
484
485 // set sort order
486 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sort_model), LANGUAGE_STRING, GTK_SORT_ASCENDING);
487
488 gtk_combo_box_set_model (GTK_COMBO_BOX(data->language_combo), GTK_TREE_MODEL(sort_model));
489 gtk_combo_box_set_model (GTK_COMBO_BOX(data->region_combo), GTK_TREE_MODEL(filter_model));
490
491 gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER(filter_model), REGION_FILTER);
492
493 g_signal_connect (data->language_combo, "changed",
494 G_CALLBACK(region_combo_change_filter_cb), (gpointer)data);
495
496 if (g_file_test (data->gnc_accounts_dir, G_FILE_TEST_IS_DIR))
497 {
498 GHashTable *testhash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, nullptr);
499 GDir *acct_dir = g_dir_open (data->gnc_accounts_dir, 0, nullptr);
500 const gchar *name = "a";
501
502 while (name != nullptr)
503 {
504 name = g_dir_read_name (acct_dir);
505
506 if (name)
507 {
508 gchar **parts = g_strsplit (name, "_", -1);
509 gchar *lang_name;
510
511 gtk_list_store_append (region_store, &region_iter);
512 gtk_list_store_set (region_store, &region_iter, LANG_REG_STRING, name,
513 LANGUAGE_STRING, parts[0], REGION_FILTER, TRUE, -1);
514
515 // set the region combo to the default region
516 if (g_str_has_suffix (locale_dir, name))
517 {
518 GtkTreeIter filter_iter;
519 gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER(filter_model),
520 &filter_iter,
521 &region_iter);
522
523 gtk_combo_box_set_active_iter (GTK_COMBO_BOX(data->region_combo), &filter_iter);
524 start_region = g_strdup (parts[0]);
525 }
526 // add the region part to the region model store
527 if (parts[1] != nullptr)
528 gtk_list_store_set (region_store, &region_iter, REGION_STRING, parts[1], -1);
529 else
530 gtk_list_store_set (region_store, &region_iter, REGION_STRING, "--", -1);
531
532 // to make it less confusing to non-programmers, change C to en_US
533 if (g_strcmp0 (name, "C") == 0)
534 {
535 gtk_list_store_set (region_store, &region_iter, LANGUAGE_STRING, "en", REGION_STRING, "US", -1);
536 lang_name = g_strdup ("en");
537
538 if (g_str_has_suffix (locale_dir, name))
539 {
540 g_free (start_region);
541 start_region = g_strdup (lang_name);
542 }
543 }
544 else
545 lang_name = g_strdup (parts[0]);
546
547 // see if language is in hash table so we only add it once.
548 if (g_hash_table_lookup (testhash, lang_name) == nullptr)
549 {
550 static const char* t_str{"test"};
551 gtk_list_store_append (language_store, &language_iter);
552 gtk_list_store_set (language_store, &language_iter, LANGUAGE_STRING, lang_name, -1);
553
554 g_hash_table_insert (testhash, g_strdup (lang_name), &t_str);
555 }
556 g_strfreev (parts);
557 g_free (lang_name);
558 }
559 }
560 g_hash_table_destroy (testhash);
561 g_dir_close (acct_dir);
562 }
563
564 // now try and set the language combo to the default language
565 valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(language_store), &language_iter);
566 while (valid)
567 {
568 gchar *language_test = nullptr;
569
570 gtk_tree_model_get (GTK_TREE_MODEL(language_store), &language_iter, LANGUAGE_STRING, &language_test, -1);
571
572 if (g_strcmp0 (language_test, start_region) == 0)
573 {
574 GtkTreeIter sort_iter;
575 gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT(sort_model),
576 &sort_iter,
577 &language_iter);
578
579 gtk_combo_box_set_active_iter (GTK_COMBO_BOX(data->language_combo), &sort_iter);
580 }
581 g_free (language_test);
582
583 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL(language_store), &language_iter);
584 }
585 g_signal_connect (data->region_combo, "changed",
586 G_CALLBACK(region_combo_changed_cb), (gpointer)data);
587
588 g_object_unref (language_store);
589 g_object_unref (region_store);
590 g_free (start_region);
591}
592
593/************************************************************
594 * Choose Categories Page *
595 ************************************************************/
596
614static gboolean
615account_set_checked_helper (GtkListStore *store,
616 GtkTreePath *path,
617 GtkTreeIter *iter,
618 gboolean *result)
619{
620 gboolean checked;
621
622 g_return_val_if_fail(GTK_IS_LIST_STORE(store), FALSE);
623
624 gtk_tree_model_get (GTK_TREE_MODEL(store), iter, COL_CHECKED, &checked, -1);
625 if (checked)
626 {
627 *result = TRUE;
628 return TRUE; /* Stop tree walk. */
629 }
630
631 return FALSE;
632}
633
640static void
641categories_page_enable_next (hierarchy_data *data)
642{
643 gint currentpagenum;
644 GtkWidget *currentpage;
645 GtkAssistant *assistant = GTK_ASSISTANT(data->dialog);
646
647 data->next_ok = FALSE;
648 gtk_tree_model_foreach (gtk_tree_view_get_model (data->categories_tree),
649 (GtkTreeModelForeachFunc)account_set_checked_helper,
650 &data->next_ok);
651
652 currentpagenum = gtk_assistant_get_current_page(assistant);
653 currentpage = gtk_assistant_get_nth_page(assistant, currentpagenum);
654
655 gtk_assistant_set_page_complete(assistant, currentpage, data->next_ok);
656}
657
658
659static void
660categories_selection_changed (GtkTreeModel *treemodel,
661 GtkTreePath *arg1,
662 GtkTreeIter *arg2,
663 hierarchy_data *data)
664{
665 data->category_set_changed = TRUE;
666 categories_page_enable_next(data);
667}
668
669
670static void
671add_one_category (GncExampleAccount *acc,
672 hierarchy_data *data)
673{
674 GtkTreeView *view;
675 GtkListStore *store;
676 GtkTreeIter iter;
677 GtkTreePath* path;
678 gboolean use_defaults;
679
680 g_return_if_fail(acc != nullptr);
681 g_return_if_fail(data != nullptr);
682
683 view = data->categories_tree;
684 store = GTK_LIST_STORE(gtk_tree_view_get_model(view));
685 use_defaults = data->use_defaults && acc->start_selected;
686
687 gtk_list_store_append(store, &iter);
688 gtk_list_store_set(store, &iter,
689 COL_CHECKED, use_defaults,
690 COL_TITLE, acc->title,
691 COL_SHORT_DESCRIPTION, acc->short_description,
692 COL_LONG_DESCRIPTION, acc->long_description,
693 COL_ACCOUNT, acc,
694 -1);
695
696 if (use_defaults)
697 {
698 data->category_set_changed = TRUE;
699 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
700 data->initial_category = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
701 gtk_tree_path_free(path);
702 }
703}
704
705static void
706category_checkbox_toggled (GtkCellRendererToggle *toggle,
707 gchar *path,
708 GtkListStore *store)
709{
710 GtkTreeIter iter;
711 gboolean active;
712
713 if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store),
714 &iter, path))
715 return;
716
717 /* Get current state of the category */
718 active = gtk_cell_renderer_toggle_get_active(toggle);
719 gtk_list_store_set(store, &iter, COL_CHECKED, !active, -1);
720}
721
722static void
723account_categories_tree_view_prepare (hierarchy_data *data)
724{
725 GSList *list;
726 gchar *locale_dir;
727 GtkTreeView *tree_view;
728 GtkListStore *model;
729 GtkTreeViewColumn *column;
730 GtkCellRenderer *renderer;
731 GtkTreeSelection *selection;
732 GtkTreePath *path;
733
734 data->gnc_accounts_dir = gnc_path_get_accountsdir ();
735 locale_dir = gnc_get_ea_locale_dir (data->gnc_accounts_dir);
736
738 list = gnc_load_example_account_list (locale_dir);
740
741 update_language_region_combos (data, locale_dir);
742
743 g_free (locale_dir);
744
745 /* Prepare the account_categories GtkTreeView with a model and with some columns */
746 tree_view = data->categories_tree;
747 model = gtk_list_store_new(NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING,
748 G_TYPE_STRING, G_TYPE_POINTER);
749 gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL(model));
750 g_object_unref (model);
751
752 g_slist_foreach(list, (GFunc)add_one_category, data);
753
754 g_signal_connect (G_OBJECT (model), "row_changed",
755 G_CALLBACK (categories_selection_changed),
756 data);
757
758 renderer = gtk_cell_renderer_toggle_new ();
759 g_object_set (G_OBJECT (renderer), "activatable", TRUE, nullptr);
760 column = gtk_tree_view_column_new_with_attributes (_("Selected"),
761 renderer,
762 "active", COL_CHECKED,
763 nullptr);
764 gtk_tree_view_append_column (tree_view, column);
765 gtk_tree_view_column_set_sort_column_id (column, COL_CHECKED);
766 g_signal_connect (G_OBJECT (renderer), "toggled",
767 G_CALLBACK (category_checkbox_toggled),
768 model);
769
770
771 renderer = gtk_cell_renderer_text_new ();
772 column = gtk_tree_view_column_new_with_attributes (_("Account Types"),
773 renderer,
774 "text", COL_TITLE,
775 nullptr);
776 gtk_tree_view_append_column (tree_view, column);
777 gtk_tree_view_column_set_sort_column_id (column, COL_TITLE);
778
779// renderer = gtk_cell_renderer_text_new ();
780// column = gtk_tree_view_column_new_with_attributes (_("Description"),
781// renderer,
782// "text", COL_SHORT_DESCRIPTION,
783// nullptr);
784// gtk_tree_view_append_column (tree_view, column);
785// gtk_tree_view_column_set_sort_column_id (column, COL_SHORT_DESCRIPTION);
786
787 gtk_tree_view_set_headers_clickable(tree_view, TRUE);
788 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(model),
789 COL_TITLE,
790 GTK_SORT_ASCENDING);
791
792 selection = gtk_tree_view_get_selection (tree_view);
793
794 if (data->initial_category)
795 {
796 path = gtk_tree_row_reference_get_path (data->initial_category);
797 gtk_tree_view_scroll_to_cell (tree_view, path, nullptr, TRUE, 0.5, 0.5);
798 }
799 else
800 path = gtk_tree_path_new_first ();
801
802 gtk_tree_selection_select_path (selection, path);
803 gtk_tree_path_free (path);
804
805 g_slist_free (list);
806}
807
808void on_prepare (GtkAssistant *assistant, GtkWidget *page,
809 hierarchy_data *data)
810{
811 const int currency_page = data->new_book ? 2 : 1;
812 const int selection_page = data->new_book ? 3 : 2;
813 const int final_page = data->new_book ? 4 : 3;
814 const int current_page = gtk_assistant_get_current_page (assistant);
815
816 if (current_page == currency_page)
817 on_select_currency_prepare (data);
818
819 if (current_page == selection_page)
820 on_choose_account_categories_prepare (data);
821
822 if (current_page == final_page)
823 on_final_account_prepare (data);
824}
825
826void
827on_choose_account_categories_prepare (hierarchy_data *data)
828{
829 GtkTextBuffer* buffer;
830
831 if (!data->account_list_added)
832 {
833 /* clear out the description/tree */
834 if (data->category_accounts_tree)
835 gtk_widget_destroy(GTK_WIDGET(data->category_accounts_tree));
836 data->category_accounts_tree = nullptr;
837 buffer = gtk_text_view_get_buffer(data->category_description);
838 gtk_text_buffer_set_text(buffer, "", -1);
839
840 data->account_list_added = TRUE;
841
842 /* Build the categories tree if necessary */
843 gnc_suspend_gui_refresh ();
844 account_categories_tree_view_prepare (data);
845 gnc_resume_gui_refresh ();
846 }
847 categories_page_enable_next(data);
848}
849
850static void
851categories_tree_selection_changed (GtkTreeSelection *selection,
852 hierarchy_data *data)
853{
854 GtkTreeView *tree_view;
855 GtkTreeModel *model;
856 GtkTreeViewColumn *column;
857 GtkTreeIter iter;
858 GncExampleAccount *gea;
859 GtkTextBuffer* buffer;
860 gchar *text;
861
862 /* Remove the old account tree */
863 if (data->category_accounts_tree)
864 gtk_widget_destroy(GTK_WIDGET(data->category_accounts_tree));
865 data->category_accounts_tree = nullptr;
866
867 /* Add a new one if something selected */
868 if (gtk_tree_selection_get_selected (selection, &model, &iter))
869 {
870 gchar *text2;
871 gtk_tree_model_get (model, &iter, COL_ACCOUNT, &gea, -1);
872 /* Translators: '%s' is the name of the selected account hierarchy template. */
873 text2 = g_strdup_printf(_("Accounts in '%s'"), gea->title);
874 text = g_strdup_printf("<b>%s</b>", text2);
875 gtk_label_set_markup(data->category_accounts_label, text);
876 g_free(text2);
877 g_free(text);
878 buffer = gtk_text_view_get_buffer(data->category_description);
879 gtk_text_buffer_set_text(buffer, gea->long_description ?
880 gea->long_description :
881 _("No description provided."), -1);
882
883 tree_view = gnc_tree_view_account_new_with_root (gea->root, FALSE);
884 /* Override the normal fixed (user settable) sizing */
885 column = gtk_tree_view_get_column(GTK_TREE_VIEW(tree_view), 0);
886 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
887
888 data->category_accounts_tree = tree_view;
889 gtk_tree_view_expand_all (tree_view);
890 gtk_container_add(GTK_CONTAINER(data->category_accounts_container), GTK_WIDGET(tree_view));
891 gtk_widget_show(GTK_WIDGET(tree_view));
892 }
893 else
894 {
895 gchar *text;
896 text = g_strdup_printf ("<b>%s</b>", _("Accounts in Category"));
897 gtk_label_set_markup(data->category_accounts_label, text);
898 g_free (text);
899 buffer = gtk_text_view_get_buffer(data->category_description);
900 gtk_text_buffer_set_text(buffer, "", -1);
901 }
902}
903
904static gboolean
905select_helper (GtkListStore *store,
906 GtkTreePath *path,
907 GtkTreeIter *iter,
908 gpointer data)
909{
910 GncExampleAccount *gea;
911
912 g_return_val_if_fail(GTK_IS_LIST_STORE(store), FALSE);
913
914 gtk_tree_model_get (GTK_TREE_MODEL(store), iter, COL_ACCOUNT, &gea, -1);
915 if ((gea != nullptr) && !gea->exclude_from_select_all)
916 {
917 gtk_list_store_set(store, iter,
918 COL_CHECKED, GPOINTER_TO_INT(data),
919 -1);
920 }
921
922 return FALSE; /* Run entire tree */
923}
924
925void
926select_all_clicked (GtkButton *button,
927 hierarchy_data *data)
928{
929 gtk_tree_model_foreach (gtk_tree_view_get_model (data->categories_tree),
930 (GtkTreeModelForeachFunc)select_helper,
931 GINT_TO_POINTER(TRUE));
932}
933
934void
935clear_all_clicked (GtkButton *button,
936 hierarchy_data *data)
937{
938 gtk_tree_model_foreach (gtk_tree_view_get_model (data->categories_tree),
939 (GtkTreeModelForeachFunc)select_helper,
940 GINT_TO_POINTER(FALSE));
941}
942
943/************************************************************
944 * Opening Balances Page *
945 ************************************************************/
946
947static void
948delete_our_account_tree (hierarchy_data *data)
949{
950 if (data->our_account_tree != nullptr)
951 {
952 xaccAccountBeginEdit (data->our_account_tree);
953 xaccAccountDestroy (data->our_account_tree);
954 data->our_account_tree = nullptr;
955 }
956}
957
958static Account*
959clone_account (const Account* from, gnc_commodity *com)
960{
961 Account *ret;
962
963 ret = xaccCloneAccount (from, gnc_get_current_book ());
964
965 xaccAccountSetCommodity (ret, com);
966
967 return ret;
968}
969
971{
972 Account *to;
973 Account *parent;
974 gnc_commodity *com;
975};
976
977static void
978add_groups_for_each (Account *toadd, gpointer data)
979{
980 auto dadata{static_cast<struct add_group_data_struct*>(data)};
981 Account *foundact;
982
983 foundact = gnc_account_lookup_by_name(dadata->to, xaccAccountGetName(toadd));
984
985 if (!foundact)
986 {
987 foundact = clone_account (toadd, dadata->com);
988
989 if (dadata->to)
990 gnc_account_append_child (dadata->to, foundact);
991 else if (dadata->parent)
992 gnc_account_append_child (dadata->parent, foundact);
993 else
994 {
995 g_warning ("add_groups_for_each: no valid parent");
996 }
997 }
998
999 {
1000 if (gnc_account_n_children(toadd) > 0)
1001 {
1002 struct add_group_data_struct downdata;
1003
1004 downdata.to = foundact;
1005 downdata.parent = foundact;
1006 downdata.com = dadata->com;
1007
1008 gnc_account_foreach_child (toadd, add_groups_for_each, &downdata);
1009 }
1010 }
1011}
1012
1013static void
1014add_new_accounts_with_random_guids (Account *into, Account *from,
1015 gnc_commodity *com)
1016{
1017 struct add_group_data_struct data;
1018 data.to = into;
1019 data.parent = nullptr;
1020 data.com = com;
1021
1022 gnc_account_foreach_child (from, add_groups_for_each, &data);
1023}
1024
1025static Account *
1026hierarchy_merge_accounts (GSList *dalist, gnc_commodity *com)
1027{
1028 GSList *mark;
1029 Account *ret = xaccMallocAccount (gnc_get_current_book ());
1030
1031 for (mark = dalist; mark; mark = mark->next)
1032 {
1033 auto xea{static_cast<GncExampleAccount*>(mark->data)};
1034
1035 add_new_accounts_with_random_guids (ret, xea->root, com);
1036 }
1037
1038 return ret;
1039}
1040
1041static gboolean
1042accumulate_accounts (GtkListStore *store,
1043 GtkTreePath *path,
1044 GtkTreeIter *iter,
1045 GSList **list)
1046{
1047 GncExampleAccount *gea;
1048 gboolean active;
1049
1050 g_return_val_if_fail(GTK_IS_LIST_STORE(store), FALSE);
1051
1052 gtk_tree_model_get (GTK_TREE_MODEL(store), iter,
1053 COL_CHECKED, &active,
1054 COL_ACCOUNT, &gea,
1055 -1);
1056 if (active && gea)
1057 *list = g_slist_prepend(*list, gea);
1058
1059 return FALSE; /* Run entire list */
1060}
1061
1062
1063static GSList *
1064get_selected_account_list (GtkTreeView *tree_view)
1065{
1066 GSList *actlist = nullptr;
1067 GtkTreeModel *model;
1068
1069 model = gtk_tree_view_get_model (tree_view);
1070 gtk_tree_model_foreach (model,
1071 (GtkTreeModelForeachFunc)accumulate_accounts,
1072 &actlist);
1073 return actlist;
1074}
1075
1076static void
1077balance_cell_data_func (GtkTreeViewColumn *tree_column,
1078 GtkCellRenderer *cell,
1079 GtkTreeModel *model,
1080 GtkTreeIter *iter,
1081 gpointer user_data)
1082{
1083 Account *account;
1084 gnc_numeric balance;
1085 const gchar *string;
1086 GNCPrintAmountInfo print_info;
1087 hierarchy_data *data = (hierarchy_data *)user_data;
1088 gboolean allow_value;
1089
1090 g_return_if_fail (GTK_TREE_MODEL (model));
1091 account = gnc_tree_view_account_get_account_from_iter (model, iter);
1092
1093 balance = get_final_balance (data->balance_hash, account);
1094 if (gnc_numeric_zero_p (balance))
1095 {
1096 string = "";
1097 }
1098 else
1099 {
1100 print_info = gnc_account_print_info (account, FALSE);
1101 string = xaccPrintAmount (balance, print_info);
1102 }
1103
1104 if (xaccAccountGetType(account) == ACCT_TYPE_EQUITY ||
1105 xaccAccountGetType(account) == ACCT_TYPE_TRADING)
1106 {
1107 allow_value = FALSE;
1108 string = _("zero");
1109 }
1110 else
1111 {
1112 GncAccountMergeDisposition disp;
1113 disp = determine_merge_disposition(gnc_book_get_root_account(gnc_get_current_book()), account);
1114 if (disp == GNC_ACCOUNT_MERGE_DISPOSITION_CREATE_NEW)
1115 {
1116 allow_value = !xaccAccountGetPlaceholder(account);
1117 }
1118 else
1119 {
1120 allow_value = FALSE;
1121 string = _("existing account");
1122 }
1123 }
1124 g_object_set (G_OBJECT (cell),
1125 "text", string,
1126 "editable", allow_value,
1127 "sensitive", allow_value,
1128 nullptr);
1129}
1130
1131static void
1132balance_cell_edited (GtkCellRendererText *cell,
1133 gchar *path,
1134 gchar *new_text,
1135 gpointer user_data)
1136{
1137 Account *account;
1138 char *error_loc;
1139 gnc_numeric amount;
1140 hierarchy_data *data = (hierarchy_data *)user_data;
1141
1142 g_return_if_fail(data != nullptr);
1143
1144 account = gnc_tree_view_account_get_selected_account(data->final_account_tree);
1145 if (account == nullptr)
1146 {
1147 g_critical("account is null");
1148 return;
1149 }
1150
1151 error_loc = nullptr;
1152 if (!gnc_exp_parser_parse (new_text, &amount, &error_loc))
1153 {
1154 amount = gnc_numeric_zero();
1155 g_object_set (G_OBJECT(cell), "text", "", nullptr);
1156 }
1157 /* Bug#348364: Emulating price-cell, we need to ensure the denominator of
1158 * the amount is in the SCU of the account's commodity (so
1159 * gnc-ui-util.c:is_decimal_fraction() on the remainder denom for
1160 * fractional values will be a "decimal").
1161 */
1162 {
1163 int account_cmdty_fraction = xaccAccountGetCommoditySCU(account);
1164 amount = gnc_numeric_convert(amount, account_cmdty_fraction, GNC_HOW_RND_ROUND_HALF_UP);
1165 }
1166 set_final_balance (data->balance_hash, account, amount);
1167 qof_event_gen (QOF_INSTANCE(account), QOF_EVENT_MODIFY, nullptr);
1168}
1169
1170static void
1171placeholder_cell_data_func (GtkTreeViewColumn *tree_column,
1172 GtkCellRenderer *cell,
1173 GtkTreeModel *model,
1174 GtkTreeIter *iter,
1175 gpointer user_data)
1176{
1177 Account *account, *root;
1178 gboolean willbe_placeholder = FALSE;
1179 GncAccountMergeDisposition disp;
1180
1181 g_return_if_fail (GTK_TREE_MODEL (model));
1182 account = gnc_tree_view_account_get_account_from_iter (model, iter);
1183 root = gnc_book_get_root_account(gnc_get_current_book());
1184 disp = determine_merge_disposition(root, account);
1185 switch (disp)
1186 {
1187 case GNC_ACCOUNT_MERGE_DISPOSITION_USE_EXISTING:
1188 {
1189 /* find the existing account, do whatever it is. */
1190 gchar *full_name;
1191 Account *existing_acct;
1192 full_name = gnc_account_get_full_name(account);
1193 existing_acct = gnc_account_lookup_by_full_name(root, full_name);
1194 willbe_placeholder = xaccAccountGetPlaceholder(existing_acct);
1195 g_free(full_name);
1196 }
1197 break;
1198 case GNC_ACCOUNT_MERGE_DISPOSITION_CREATE_NEW:
1199 willbe_placeholder = xaccAccountGetPlaceholder(account);
1200 break;
1201 }
1202
1203 gtk_cell_renderer_toggle_set_active(GTK_CELL_RENDERER_TOGGLE(cell), willbe_placeholder);
1204}
1205
1206static void
1207placeholder_cell_toggled (GtkCellRendererToggle *cell_renderer,
1208 gchar *path, gpointer user_data)
1209{
1210 gboolean state;
1211 Account *account;
1212 GtkTreePath *treepath;
1213 hierarchy_data *data = (hierarchy_data *)user_data;
1214
1215 g_return_if_fail(data != nullptr);
1216
1217 treepath = gtk_tree_path_new_from_string (path);
1218
1219 account = gnc_tree_view_account_get_account_from_path (data->final_account_tree, treepath);
1220
1221 state = gtk_cell_renderer_toggle_get_active (cell_renderer);
1222
1223 if (account)
1224 xaccAccountSetPlaceholder (account, !state);
1225
1226 // if placeholder set, set balance to zero
1227 if (!state)
1228 {
1229 set_final_balance (data->balance_hash, account, gnc_numeric_zero());
1230 qof_event_gen (QOF_INSTANCE(account), QOF_EVENT_MODIFY, nullptr);
1231 }
1232 gtk_tree_path_free (treepath);
1233}
1234
1235static void
1236use_existing_account_data_func(GtkTreeViewColumn *tree_column,
1237 GtkCellRenderer *cell,
1238 GtkTreeModel *tree_model,
1239 GtkTreeIter *iter,
1240 gpointer user_data)
1241{
1242 Account *real_root;
1243 GncAccountMergeDisposition disposition;
1244 auto to_user{"(error; unknown condition)"};
1245
1246 g_return_if_fail (GTK_TREE_MODEL (tree_model));
1247 auto new_acct{static_cast<Account*>(gnc_tree_view_account_get_account_from_iter(tree_model, iter))};
1248 if (!new_acct)
1249 {
1250 g_object_set (G_OBJECT(cell), "text", "(null account)", nullptr);
1251 return;
1252 }
1253
1254 real_root = gnc_book_get_root_account(gnc_get_current_book());
1255 disposition = determine_merge_disposition(real_root, new_acct);
1256 switch (disposition)
1257 {
1258 case GNC_ACCOUNT_MERGE_DISPOSITION_USE_EXISTING:
1259 to_user = _("Yes");
1260 break;
1261 case GNC_ACCOUNT_MERGE_DISPOSITION_CREATE_NEW:
1262 to_user = _("No");
1263 break;
1264 }
1265
1266 g_object_set(G_OBJECT(cell), "text", to_user, nullptr);
1267}
1268
1269void
1270on_final_account_prepare (hierarchy_data *data)
1271{
1272 GSList *actlist;
1273 GtkTreeView *tree_view;
1274 GtkTreeSelection *selection;
1275 GtkCellRenderer *renderer;
1276 GtkTreeViewColumn *column;
1277 gnc_commodity *com;
1278
1279 /* Anything to do? */
1280 if (!data->category_set_changed)
1281 return;
1282 data->category_set_changed = FALSE;
1283
1284 gnc_suspend_gui_refresh ();
1285
1286 /* Delete any existing account tree */
1287 if (data->final_account_tree)
1288 {
1289 gtk_widget_destroy(GTK_WIDGET(data->final_account_tree));
1290 data->final_account_tree = nullptr;
1291 }
1292 delete_our_account_tree (data);
1293
1294
1295 /* Build a new account list */
1296 actlist = get_selected_account_list (data->categories_tree);
1297 com = gnc_currency_edit_get_currency (GNC_CURRENCY_EDIT(data->currency_selector));
1298 data->our_account_tree = hierarchy_merge_accounts (actlist, com);
1299
1300
1301 /* Now build a new account tree */
1302 data->final_account_tree
1303 = GNC_TREE_VIEW_ACCOUNT(gnc_tree_view_account_new_with_root (data->our_account_tree, FALSE));
1304 tree_view = GTK_TREE_VIEW(data->final_account_tree);
1305 gnc_tree_view_account_set_name_edited(data->final_account_tree,
1306 gnc_tree_view_account_name_edited_cb);
1307 gnc_tree_view_account_set_code_edited(data->final_account_tree,
1308 gnc_tree_view_account_code_edited_cb);
1309 gnc_tree_view_account_set_description_edited(data->final_account_tree,
1310 gnc_tree_view_account_description_edited_cb);
1311 gnc_tree_view_account_set_notes_edited(data->final_account_tree,
1312 gnc_tree_view_account_notes_edited_cb);
1313
1314 gtk_tree_view_set_headers_visible (tree_view, TRUE);
1316 GNC_TREE_VIEW(data->final_account_tree), "type");
1317 g_object_set_data(G_OBJECT(column), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
1318 gnc_tree_view_configure_columns (GNC_TREE_VIEW(data->final_account_tree));
1319 gnc_tree_view_set_show_column_menu (GNC_TREE_VIEW(data->final_account_tree),
1320 FALSE);
1321
1322 selection = gtk_tree_view_get_selection (tree_view);
1323 gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
1324
1325 // This is a re-definition of the placeholder that the account-tree model
1326 // provides, reflecting the to-be-created state of the account tree
1327 // post-merge.
1328 {
1329 renderer = gtk_cell_renderer_toggle_new();
1330 g_object_set(G_OBJECT (renderer),
1331 "activatable", TRUE,
1332 "sensitive", TRUE,
1333 nullptr);
1334
1335 g_signal_connect (G_OBJECT (renderer), "toggled",
1336 G_CALLBACK (placeholder_cell_toggled),
1337 data);
1338
1339 column = gtk_tree_view_column_new_with_attributes(_("Placeholder"),
1340 renderer, nullptr);
1341 gtk_tree_view_column_set_cell_data_func (column, renderer,
1342 placeholder_cell_data_func,
1343 (gpointer)data, nullptr);
1344 gnc_tree_view_append_column (GNC_TREE_VIEW(tree_view), column);
1345 }
1346
1347
1348 {
1349 renderer = gtk_cell_renderer_text_new ();
1350 g_object_set (G_OBJECT (renderer),
1351 "xalign", 1.0,
1352 (char *)nullptr);
1353 g_signal_connect (G_OBJECT (renderer), "edited",
1354 G_CALLBACK (balance_cell_edited),
1355 data);
1356 column = gtk_tree_view_column_new_with_attributes (_("Opening Balance"),
1357 renderer,
1358 nullptr);
1359 gtk_tree_view_column_set_cell_data_func (column, renderer,
1360 balance_cell_data_func,
1361 (gpointer)data, nullptr);
1362 gnc_tree_view_append_column (GNC_TREE_VIEW(tree_view), column);
1363 }
1364
1365 // only in the case where there *are* existing accounts...
1366 if (gnc_account_n_descendants(gnc_book_get_root_account(gnc_get_current_book())) > 0)
1367 {
1368 GList *renderers;
1369 column = gnc_tree_view_add_text_column(GNC_TREE_VIEW(tree_view),
1370 _("Use Existing"),
1371 nullptr,
1372 nullptr,
1373 "yes",
1374 GNC_TREE_VIEW_COLUMN_DATA_NONE,
1375 GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS,
1376 nullptr);
1377 renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1378 g_object_set(G_OBJECT(renderer), "xalign", 1.0, (char*)nullptr);
1379 gtk_tree_view_column_set_cell_data_func(column, GTK_CELL_RENDERER(renderers->data),
1380 use_existing_account_data_func, (gpointer)data, nullptr);
1381 g_list_free(renderers);
1382 }
1383
1384 gtk_container_add(GTK_CONTAINER(data->final_account_tree_container),
1385 GTK_WIDGET(data->final_account_tree));
1386
1387 /* Expand the entire tree */
1388 gtk_tree_view_expand_all (tree_view);
1389 gtk_widget_show(GTK_WIDGET(data->final_account_tree));
1390 gnc_resume_gui_refresh ();
1391}
1392
1393void
1394on_cancel (GtkAssistant *gtkassistant,
1395 hierarchy_data *data)
1396{
1397 gnc_suspend_gui_refresh ();
1398 if (data->new_book)
1399 delete data->optionwin;
1400
1401 delete_hierarchy_dialog (data);
1402 delete_our_account_tree (data);
1403 g_free(data);
1404 gnc_resume_gui_refresh ();
1405}
1406
1407static void
1408starting_balance_helper (Account *account, hierarchy_data *data)
1409{
1410 gnc_numeric balance;
1411
1412 balance = get_final_balance (data->balance_hash, account);
1413 if (gnc_reverse_balance(account))
1414 balance = gnc_numeric_neg(balance);
1415 if (!gnc_numeric_zero_p (balance) &&
1417 gnc_account_create_opening_balance (account, balance,
1418 gnc_time (nullptr),
1419 gnc_get_current_book ());
1420}
1421
1422void
1423on_finish (GtkAssistant *gtkassistant,
1424 hierarchy_data *data)
1425{
1426 GncHierarchyAssistantFinishedCallback when_completed;
1427 gnc_commodity *com;
1428 Account * root;
1429 ENTER (" ");
1430 com = gnc_currency_edit_get_currency (GNC_CURRENCY_EDIT(data->currency_selector));
1431
1432 if (!gnc_using_equity_type_opening_balance_account (gnc_get_current_book()))
1433 gnc_set_use_equity_type_opening_balance_account (gnc_get_current_book());
1434
1435 if (data->our_account_tree)
1436 {
1437 gnc_account_foreach_descendant (data->our_account_tree,
1438 (AccountCb)starting_balance_helper,
1439 data);
1440 }
1441
1442 // delete before we suspend GUI events, and then muck with the model,
1443 // because the model doesn't seem to handle this correctly.
1444 if (data->initial_category)
1445 gtk_tree_row_reference_free(data->initial_category);
1446 delete_hierarchy_dialog (data);
1447
1448 gnc_suspend_gui_refresh ();
1449 if (data->new_book)
1450 delete data->optionwin;
1451
1452 account_trees_merge(gnc_get_current_root_account(), data->our_account_tree);
1453
1454 delete_our_account_tree (data);
1455
1456 when_completed = data->when_completed;
1457 g_free(data);
1458
1459 root = gnc_get_current_root_account();
1460 xaccAccountSetCommodity(root, com);
1461
1462 gnc_resume_gui_refresh ();
1463
1464 if (when_completed)
1465 {
1466 (*when_completed)();
1467 }
1468
1469 LEAVE (" ");
1470}
1471
1472/* If a book currency is selected in prior page, set the currency_selector to
1473 * the book currency, make insensitive and modify text. Otherwise, restore the
1474 * selector to original condition
1475 */
1476void
1477on_select_currency_prepare (hierarchy_data *data)
1478{
1479 /* Set book options based on the user's choices */
1480 if (data->new_book)
1481 {
1483
1484 gnc_currency_edit_set_currency (GNC_CURRENCY_EDIT(data->currency_selector),
1486 gtk_label_set_text (GTK_LABEL(data->currency_selector_label),
1487 ( _("Please choose the currency to use for new accounts.") ));
1488 gtk_widget_set_sensitive(data->currency_selector, TRUE);
1489 }
1490}
1491
1492/********************************************************
1493 * For a new book the assistant will also allow the user
1494 * to set default book options, because this impacts how
1495 * transactions are created.
1496 * Ideally, the book options code can cleanly provide us
1497 * with a page to insert in the assistant and be done with
1498 * it. Unfortunately this is not possible without a serious
1499 * rewrite of the options dialog code.
1500 * So instead the following hack is used:
1501 * we create the complete dialog, but only use the notebook
1502 * part of it to create a new page.
1503 * To make sure this dialog is cleaned up properly
1504 * when the assistant closes, the close callback is set up anyway
1505 * and at the finish we'll send a "close" response signal to the
1506 * dialog to make it clean up after itself.
1507 */
1508static void
1509book_options_dialog_close_cb(GncOptionsDialog *optionwin,
1510 gpointer user_data)
1511{
1512 auto options{static_cast<GncOptionDB*>(user_data)};
1513
1514 delete optionwin;
1515 gnc_option_db_destroy(options);
1516}
1517
1518static void
1519assistant_insert_book_options_page (hierarchy_data *data)
1520{
1521 GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1522 gtk_box_set_homogeneous (GTK_BOX (vbox), FALSE);
1523
1524 data->options = gnc_option_db_new();
1525 gnc_option_db_book_options(data->options);
1526 qof_book_load_options (gnc_get_current_book (),
1527 gnc_option_db_load, data->options);
1528 gnc_option_db_clean (data->options);
1529
1530 /* The options dialog gets added to the notebook so it doesn't need a parent.*/
1531 data->optionwin = new GncOptionsDialog(true, _("New Book Options"),
1532 DIALOG_BOOK_OPTIONS_CM_CLASS,
1533 nullptr);
1534 data->optionwin->build_contents(data->options, false);
1535
1536 data->optionwin->set_close_cb(book_options_dialog_close_cb,
1537 (gpointer)data->options);
1539
1540 auto options = data->optionwin->get_notebook();
1541 auto parent = gtk_widget_get_parent (options);
1542
1543 g_object_ref (options);
1544 gtk_container_remove (GTK_CONTAINER(parent), options);
1545 gtk_container_add (GTK_CONTAINER(vbox), options);
1546 g_object_unref (options);
1547
1548 gtk_widget_show_all (vbox);
1549 gtk_assistant_insert_page (GTK_ASSISTANT(data->dialog), vbox, 1);
1550 gtk_assistant_set_page_title (GTK_ASSISTANT(data->dialog), vbox, _("New Book Options"));
1551 gtk_assistant_set_page_complete (GTK_ASSISTANT(data->dialog), vbox, TRUE);
1552
1553}
1554
1555static GtkWidget *
1556gnc_create_hierarchy_assistant (gboolean use_defaults, GncHierarchyAssistantFinishedCallback when_completed)
1557{
1558 hierarchy_data *data;
1559 GtkWidget *dialog;
1560 GtkTreeView *tree_view;
1561 GtkWidget *box;
1562 GtkBuilder *builder;
1563
1564 data = g_new0 (hierarchy_data, 1);
1565
1566 /* Presumably this assistant is only used to create a new book but we check.
1567 * When gnucash is started with --nofile, there is initially no session (and
1568 * no book), but by the time we get here, one could have been created (for
1569 * example, if an empty account tree tab is opened, a session is created
1570 * which creates a new, but empty, book). */
1571 data->new_book = gnc_is_new_book();
1572
1573 builder = gtk_builder_new();
1574 gnc_builder_add_from_file (builder, "assistant-hierarchy.glade", "hierarchy_assistant");
1575
1576 dialog = GTK_WIDGET(gtk_builder_get_object (builder, "hierarchy_assistant"));
1577 data->dialog = dialog;
1578
1579 // Set the name for this assistant so it can be easily manipulated with css
1580 gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-assistant-account-hierarchy");
1581
1582 /* Enable buttons on first and last page. */
1583 gtk_assistant_set_page_complete (GTK_ASSISTANT (dialog),
1584 GTK_WIDGET(gtk_builder_get_object(builder, "intro_page_label")),
1585 TRUE);
1586 gtk_assistant_set_page_complete (GTK_ASSISTANT (dialog),
1587 GTK_WIDGET(gtk_builder_get_object(builder, "currency_book_option_page_vbox")),
1588 TRUE);
1589 gtk_assistant_set_page_complete (GTK_ASSISTANT (dialog),
1590 GTK_WIDGET(gtk_builder_get_object(builder, "final_account_vbox")),
1591 TRUE);
1592 gtk_assistant_set_page_complete (GTK_ASSISTANT (dialog),
1593 GTK_WIDGET(gtk_builder_get_object(builder, "finish_page_label")),
1594 TRUE);
1595
1596 /* Currency Page */
1597 data->currency_selector = gnc_currency_edit_new();
1598 gnc_currency_edit_set_currency (GNC_CURRENCY_EDIT(data->currency_selector),
1600 gtk_widget_show (data->currency_selector);
1601 box = GTK_WIDGET(gtk_builder_get_object (builder, "currency_chooser_hbox"));
1602 data->currency_selector_label = GTK_WIDGET(gtk_builder_get_object (builder,
1603 "choose_currency_label"));
1604 gtk_box_pack_start(GTK_BOX(box), data->currency_selector, TRUE, TRUE, 0);
1605
1606 /* Categories Page */
1607 tree_view = GTK_TREE_VIEW(gtk_builder_get_object (builder, "account_categories_tree_view"));
1608 g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (tree_view)), "changed",
1609 G_CALLBACK (categories_tree_selection_changed), data);
1610 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view), GTK_SELECTION_SINGLE);
1611 data->categories_tree = tree_view;
1612
1613 data->language_combo = GTK_WIDGET(gtk_builder_get_object (builder, "language_combo"));
1614 data->region_combo = GTK_WIDGET(gtk_builder_get_object (builder, "region_combo"));
1615 data->region_label = GTK_WIDGET(gtk_builder_get_object (builder, "region_label"));
1616
1617 data->category_accounts_label = GTK_LABEL(gtk_builder_get_object (builder, "accounts_in_category_label"));
1618 data->category_accounts_container = GTK_WIDGET(gtk_builder_get_object (builder, "accounts_in_category"));
1619 data->category_description = GTK_TEXT_VIEW(gtk_builder_get_object (builder, "account_types_description"));
1620 data->account_list_added = FALSE;
1621
1622 /* Book options page - only on new books */
1623 if (data->new_book)
1624 assistant_insert_book_options_page (data);
1625
1626 /* Final Accounts Page */
1627 data->final_account_tree_container = GTK_WIDGET(gtk_builder_get_object (builder, "final_account_tree_box"));
1628 data->final_account_tree = nullptr;
1629
1630 data->balance_hash = g_hash_table_new(nullptr, nullptr);
1631
1632 gnc_restore_window_size (GNC_PREFS_GROUP,
1633 GTK_WINDOW(data->dialog), gnc_ui_get_main_window(nullptr));
1634
1635 g_signal_connect (G_OBJECT(dialog), "destroy",
1636 G_CALLBACK (gnc_hierarchy_destroy_cb), data);
1637
1638 gtk_builder_connect_signals(builder, data);
1639 g_object_unref(G_OBJECT(builder));
1640
1641 data->when_completed = when_completed;
1642 data->use_defaults = use_defaults;
1643 gtk_widget_show_all (dialog);
1644 return dialog;
1645}
1646
1647GtkWidget*
1648gnc_ui_hierarchy_assistant(gboolean use_defaults)
1649{
1650 return gnc_create_hierarchy_assistant(use_defaults, nullptr);
1651}
1652
1653GtkWidget*
1654gnc_ui_hierarchy_assistant_with_callback(gboolean use_defaults,
1655 GncHierarchyAssistantFinishedCallback when_finished)
1656{
1657 return gnc_create_hierarchy_assistant(use_defaults, when_finished);
1658}
1659
1660static void
1661after_assistant(void)
1662{
1663 qof_book_mark_session_dirty(gnc_get_current_book());
1664 gnc_ui_file_access_for_save_as (gnc_ui_get_main_window (nullptr));
1665}
1666
1667static void
1668gnc_ui_hierarchy_assistant_hook (void)
1669{
1670 if (gnc_prefs_get_bool(GNC_PREFS_GROUP, GNC_PREF_SHOW_ON_NEW_FILE))
1671 {
1672 gnc_ui_hierarchy_assistant_with_callback(TRUE, after_assistant);
1673 }
1674}
1675
1676void
1677gnc_ui_hierarchy_assistant_initialize (void)
1678{
1679 gnc_hook_add_dangler(HOOK_NEW_BOOK,
1680 (GFunc)gnc_ui_hierarchy_assistant_hook,
1681 nullptr, nullptr);
1682}
Holds all of the options for a book, report, or stylesheet, organized by GncOptionSections.
This file contains the functions to present a GUI to select a file or a database connection.
Currency selection widget.
All type declarations for the whole Gnucash engine.
Gnome specific utility functions.
Functions for adding content to a window.
C public interface for the Options Database.
Functions providing a chart of account page.
Generic api to store and retrieve preferences.
GtkTreeView implementation for gnucash account tree.
utility functions for the GnuCash UI
const char * xaccAccountGetName(const Account *acc)
Get the account's name.
Definition Account.cpp:3289
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition Account.cpp:1475
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
void gnc_account_append_child(Account *new_parent, Account *child)
This function will remove from the child account any pre-existing parent relationship,...
Definition Account.cpp:2836
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account's account type.
Definition Account.cpp:3267
int xaccAccountGetCommoditySCU(const Account *acc)
Return the SCU for the account.
Definition Account.cpp:2745
void xaccAccountDestroy(Account *acc)
The xaccAccountDestroy() routine can be used to get rid of an account.
Definition Account.cpp:1590
Account * xaccCloneAccount(const Account *from, QofBook *book)
The xaccCloneAccount() routine makes a simple copy of the indicated account, placing it in the indica...
Definition Account.cpp:1301
Account * xaccMallocAccount(QofBook *book)
Constructor.
Definition Account.cpp:1270
void gnc_account_foreach_child(const Account *acc, AccountCb thunk, gpointer user_data)
This method will traverse the immediate children of this accounts, calling 'func' on each account.
Definition Account.cpp:3225
gint gnc_account_n_descendants(const Account *account)
Return the number of descendants of the specified account.
Definition Account.cpp:3002
gint gnc_account_n_children(const Account *account)
Return the number of children of the specified account.
Definition Account.cpp:2976
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
void xaccAccountSetCommodity(Account *acc, gnc_commodity *com)
Set the account's commodity.
Definition Account.cpp:2678
void qof_book_mark_session_dirty(QofBook *book)
The qof_book_mark_dirty() routine marks the book as having been modified.
Definition qofbook.cpp:397
gboolean gnc_commodity_is_currency(const gnc_commodity *cm)
Checks to see if the specified commodity is an ISO 4217 recognized currency or a legacy currency.
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
gboolean xaccAccountGetPlaceholder(const Account *acc)
Get the "placeholder" flag for an account.
Definition Account.cpp:4119
Account * gnc_account_lookup_by_name(const Account *parent, const char *name)
The gnc_account_lookup_by_name() subroutine fetches the account by name from the descendants of the s...
Definition Account.cpp:3093
void xaccAccountSetPlaceholder(Account *acc, gboolean val)
Set the "placeholder" flag for an account.
Definition Account.cpp:4125
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,...
Definition Account.cpp:3163
void qof_event_resume(void)
Resume engine event generation.
Definition qofevent.cpp:156
void qof_event_gen(QofInstance *entity, QofEventId event_id, gpointer event_data)
Invoke all registered event handlers using the given arguments.
Definition qofevent.cpp:231
void qof_event_suspend(void)
Suspend all engine events.
Definition qofevent.cpp:145
void gnc_currency_edit_set_currency(GNCCurrencyEdit *gce, const gnc_commodity *currency)
Set the widget to display a certain currency name.
GtkWidget * gnc_currency_edit_new(void)
Create a new GNCCurrencyEdit widget which can be used to provide an easy way to enter ISO currency co...
gnc_commodity * gnc_currency_edit_get_currency(GNCCurrencyEdit *gce)
Retrieve the displayed currency of the widget.
gboolean gnc_book_options_dialog_apply_helper(GncOptionDB *options)
Processes selected options in the Book Options dialog: checks book_currency and use_split_action_for_...
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
void gnc_tree_view_configure_columns(GncTreeView *view)
Make all the correct columns visible, respecting their default visibility setting,...
GtkTreeViewColumn * gnc_tree_view_add_text_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *icon_name, const gchar *sizing_text, gint model_data_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn)
This function adds a new text column to a GncTreeView base view.
void gnc_tree_view_set_show_column_menu(GncTreeView *view, gboolean visible)
This function is called to set the "show-column-menu" property on this view.
gint gnc_tree_view_append_column(GncTreeView *view, GtkTreeViewColumn *column)
Add a column to a view based upon a GncTreeView.
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.
void gnc_options_dialog_set_new_book_option_values(GncOptionDB *odb)
Set the initial values of new book options to values specified in user preferences.
GtkTreeView * gnc_tree_view_account_new_with_root(Account *root, gboolean show_root)
Create a new account tree view.
Account * gnc_tree_view_account_get_account_from_path(GncTreeViewAccount *view, GtkTreePath *s_path)
This function returns the account associated with the specified path.
Account * gnc_tree_view_account_get_account_from_iter(GtkTreeModel *s_model, GtkTreeIter *s_iter)
This function returns the account associated with the specified iter.
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.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
gnc_commodity * gnc_default_currency(void)
Return the default currency set by the user.
void qof_book_load_options(QofBook *book, GncOptionLoad load_cb, GncOptionDB *odb)
Load a GncOptionsDB from KVP data.
Definition qofbook.cpp:1334
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value.
GncOptionDB * gnc_option_db_new(void)
Create an empty option database.
void gnc_option_db_clean(GncOptionDB *odb)
Reset all ui_items to the option value.
void gnc_option_db_book_options(GncOptionDB *odb)
Register the standard option set for a QofBook.
void gnc_option_db_destroy(GncOptionDB *odb)
Destruct and release a GncOptionDB.
void gnc_option_db_load(GncOptionDB *odb, QofBook *book)
Load a book's options into the GncOptionDB.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
STRUCTS.
QofBook reference.
Definition qofbook-p.hpp:47
GHashTable * balance_hash
Map<Account*,gnc_numeric*>