GnuCash  5.6-150-g038405b370+
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"
60 #include "gnc-tree-view-account.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 
70 static 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 
76 typedef 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 
87 typedef 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 
130 extern "C"
131 {
132 void on_prepare (GtkAssistant *assistant, GtkWidget *page,
133  hierarchy_data *data);
134 
135 void on_cancel (GtkAssistant *gtkassistant, hierarchy_data *data);
136 void on_finish (GtkAssistant *gtkassistant, hierarchy_data *data);
137 
138 void select_all_clicked (GtkButton *button,
139  hierarchy_data *data);
140 void clear_all_clicked (GtkButton *button,
141  hierarchy_data *data);
142 }
143 
144 void on_choose_account_categories_prepare (hierarchy_data *data);
145 void on_final_account_prepare (hierarchy_data *data);
146 void on_select_currency_prepare (hierarchy_data *data);
147 
148 static void add_one_category (GncExampleAccount *acc, hierarchy_data *data);
149 static void categories_page_enable_next (hierarchy_data *data);
150 static void categories_tree_selection_changed (GtkTreeSelection *selection, hierarchy_data *data);
151 // ------------------------------------------------------------
152 
153 static void
154 delete_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 
160 static void
161 destroy_hash_helper (gpointer key, gpointer value, gpointer user_data)
162 {
163  auto balance{static_cast<gnc_numeric*>(value)};
164  g_free (balance);
165 }
166 
167 static void
168 gnc_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 
183 static gnc_numeric
184 get_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 
195 static void
196 set_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  */
217 static char*
218 mac_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
243 static gchar*
244 gnc_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 
304 typedef enum
305 {
306  LANGUAGE_STRING,
307  REGION_STRING,
308  LANG_REG_STRING,
309  REGION_FILTER
310 }GncLanguageRegionCombos;
311 
312 static void
313 region_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);
349  qof_event_resume ();
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 
384 static void
385 region_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 
474 static void
475 update_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 
614 static gboolean
615 account_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 
640 static void
641 categories_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 
659 static void
660 categories_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 
670 static void
671 add_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 
705 static void
706 category_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 
722 static void
723 account_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);
739  qof_event_resume ();
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 
808 void 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 
826 void
827 on_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 
850 static void
851 categories_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 
904 static gboolean
905 select_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 
925 void
926 select_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 
934 void
935 clear_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 
947 static void
948 delete_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 
958 static Account*
959 clone_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 
977 static void
978 add_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 
1013 static void
1014 add_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 
1025 static Account *
1026 hierarchy_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 
1041 static gboolean
1042 accumulate_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 
1063 static GSList *
1064 get_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 
1076 static void
1077 balance_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 ||
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 
1131 static void
1132 balance_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 
1170 static void
1171 placeholder_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 
1206 static void
1207 placeholder_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 
1235 static void
1236 use_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 
1269 void
1270 on_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 
1393 void
1394 on_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 
1407 static void
1408 starting_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 
1422 void
1423 on_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  */
1476 void
1477 on_select_currency_prepare (hierarchy_data *data)
1478 {
1479  /* Set book options based on the user's choices */
1480  if (data->new_book)
1481  {
1482  gnc_book_options_dialog_apply_helper(data->options);
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  */
1508 static void
1509 book_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 
1518 static void
1519 assistant_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 
1555 static GtkWidget *
1556 gnc_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 
1647 GtkWidget*
1648 gnc_ui_hierarchy_assistant(gboolean use_defaults)
1649 {
1650  return gnc_create_hierarchy_assistant(use_defaults, nullptr);
1651 }
1652 
1653 GtkWidget*
1654 gnc_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 
1660 static void
1661 after_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 
1667 static void
1668 gnc_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 
1676 void
1677 gnc_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.
void gnc_option_db_clean(GncOptionDB *odb)
Reset all ui_items to the option value.
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...
void qof_book_load_options(QofBook *book, GncOptionLoad load_cb, GncOptionDB *odb)
Load a GncOptionsDB from KVP data.
Definition: qofbook.cpp:1316
void gnc_account_append_child(Account *new_parent, Account *child)
This function will remove from the child account any pre-existing parent relationship, and will then add the account as a child of the new parent.
Definition: Account.cpp:2807
gint gnc_account_n_descendants(const Account *account)
Return the number of descendants of the specified account.
Definition: Account.cpp:2972
void gnc_currency_edit_set_currency(GNCCurrencyEdit *gce, const gnc_commodity *currency)
Set the widget to display a certain currency name.
GtkWindow * gnc_ui_get_main_window(GtkWidget *widget)
Get a pointer to the final GncMainWindow widget is rooted in.
utility functions for the GnuCash UI
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3237
int xaccAccountGetCommoditySCU(const Account *acc)
Return the SCU for the account.
Definition: Account.cpp:2716
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value...
STRUCTS.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
GHashTable * balance_hash
Map<Account*,gnc_numeric*>
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
C public interface for the Options Database.
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.
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
Functions for adding content to a window.
gnc_commodity * gnc_default_currency(void)
Return the default currency set by the user.
Account used to record multiple commodity transactions.
Definition: Account.h:155
void xaccAccountDestroy(Account *acc)
The xaccAccountDestroy() routine can be used to get rid of an account.
Definition: Account.cpp:1592
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:3063
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
gint gnc_tree_view_append_column(GncTreeView *view, GtkTreeViewColumn *column)
Add a column to a view based upon a GncTreeView.
Currency selection widget.
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.
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:3275
gnc_numeric gnc_numeric_convert(gnc_numeric n, gint64 denom, gint how)
Change the denominator of a gnc_numeric value to the specified denominator under standard arguments &#39;...
void xaccAccountSetPlaceholder(Account *acc, gboolean val)
Set the "placeholder" flag for an account.
Definition: Account.cpp:4080
GtkTreeView implementation for gnucash account tree.
void gnc_account_foreach_child(const Account *acc, AccountCb thunk, gpointer user_data)
This method will traverse the immediate children of this accounts, calling &#39;func&#39; on each account...
Definition: Account.cpp:3195
GtkTreeView * gnc_tree_view_account_new_with_root(Account *root, gboolean show_root)
Create a new account tree view.
void gnc_option_db_load(GncOptionDB *odb, QofBook *book)
Load a book&#39;s options into the GncOptionDB.
void gnc_option_db_destroy(GncOptionDB *odb)
Destruct and release a GncOptionDB.
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.
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_...
gnc_commodity * gnc_currency_edit_get_currency(GNCCurrencyEdit *gce)
Retrieve the displayed currency of the widget.
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_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, but uses fully-qualified names using the given separator.
Definition: Account.cpp:3133
Account * gnc_tree_view_account_get_account_from_path(GncTreeViewAccount *view, GtkTreePath *s_path)
This function returns the account associated with the specified path.
Gnome specific utility functions.
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:1304
gint gnc_account_n_children(const Account *account)
Return the number of children of the specified account.
Definition: Account.cpp:2947
Definition: init.py:1
All type declarations for the whole Gnucash engine.
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
Generic api to store and retrieve preferences.
Functions providing a chart of account page.
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...
void qof_event_suspend(void)
Suspend all engine events.
Definition: qofevent.cpp:145
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition: Account.cpp:1477
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3371
void qof_event_resume(void)
Resume engine event generation.
Definition: qofevent.cpp:156
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.
gboolean xaccAccountGetPlaceholder(const Account *acc)
Get the "placeholder" flag for an account.
Definition: Account.cpp:4074
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
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
Round to the nearest integer, rounding away from zero when there are two equidistant nearest integers...
Definition: gnc-numeric.h:165
Account * xaccMallocAccount(QofBook *book)
Constructor.
Definition: Account.cpp:1273
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
This file contains the functions to present a GUI to select a file or a database connection.
const char * xaccAccountGetName(const Account *acc)
Get the account&#39;s name.
Definition: Account.cpp:3259
Equity account is used to balance the balance sheet.
Definition: Account.h:146
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 gnc_option_db_book_options(GncOptionDB *odb)
Register the standard option set for a QofBook.
void xaccAccountSetCommodity(Account *acc, gnc_commodity *com)
Set the account&#39;s commodity.
Definition: Account.cpp:2649
GncOptionDB * gnc_option_db_new(void)
Create an empty option database.