GnuCash  5.6-150-g038405b370+
gnc-ui-util.cpp
1 /********************************************************************\
2  * gnc-ui-util.c -- utility functions for the GnuCash UI *
3  * Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA gnu@gnu.org *
21 \********************************************************************/
22 
23 #include <config.h>
24 
25 #ifdef __MINGW32__
26 #define __USE_MINGW_ANSI_STDIO 1
27 #endif
28 #include "gnc-ui-util.h"
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <gio/gio.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <limits.h>
35 #include <locale.h>
36 #include <math.h>
37 #if defined(G_OS_WIN32) && !defined(_MSC_VER)
38 # include <pow.h>
39 #endif
40 #include <stdarg.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <string.h>
44 #include <cinttypes>
45 #include <unicode/uchar.h>
46 #include <unicode/utf8.h>
47 #include <unicode/listformatter.h>
48 
49 #include "qof.h"
50 #include "gnc-prefs.h"
51 #include "Account.h"
52 #include "Transaction.h"
53 #include "gnc-engine.h"
54 #include "gnc-features.h"
55 #include "gnc-hooks.h"
56 #include "gnc-session.h"
57 #include "engine-helpers.h"
58 #include "gnc-locale-utils.h"
59 
60 #define GNC_PREF_CURRENCY_CHOICE_LOCALE "currency-choice-locale"
61 #define GNC_PREF_CURRENCY_CHOICE_OTHER "currency-choice-other"
62 #define GNC_PREF_CURRENCY_OTHER "currency-other"
63 #define GNC_PREF_REVERSED_ACCTS_NONE "reversed-accounts-none"
64 #define GNC_PREF_REVERSED_ACCTS_CREDIT "reversed-accounts-credit"
65 #define GNC_PREF_REVERSED_ACCTS_INC_EXP "reversed-accounts-incomeexpense"
66 #define GNC_PREF_PRICES_FORCE_DECIMAL "force-price-decimal"
67 
68 using UniStr = icu::UnicodeString;
69 
70 static QofLogModule log_module = GNC_MOD_GUI;
71 
72 static bool auto_decimal_enabled = false;
73 static int auto_decimal_places = 2; /* default, can be changed */
74 
75 static bool reverse_balance_inited = false;
76 static bool reverse_type[NUM_ACCOUNT_TYPES];
77 
78 /* Cache currency ISO codes and only look them up in the settings when
79  * absolutely necessary. Can't cache a pointer to the data structure
80  * as that will change any time the book changes. */
81 static char* user_default_currency = nullptr;
82 static char* user_report_currency = nullptr;
83 constexpr int maximum_decimals = 15;
84 constexpr int64_t pow_10[] = {1, 10, 100, 1000, 10000, 100000, 1000000,
85  10000000, 100000000, 1000000000, 10000000000,
86  100000000000, 1000000000000, 10000000000000,
87  100000000000000, 1000000000000000};
88 
89 char*
90 gnc_normalize_account_separator (const gchar* separator)
91 {
92  char* new_sep=nullptr;
93 
94  if (!separator || !*separator || g_strcmp0(separator, "colon") == 0)
95  new_sep = g_strdup (":");
96  else if (g_strcmp0(separator, "slash") == 0)
97  new_sep = g_strdup ("/");
98  else if (g_strcmp0(separator, "backslash") == 0)
99  new_sep = g_strdup ("\\");
100  else if (g_strcmp0(separator, "dash") == 0)
101  new_sep = g_strdup ("-");
102  else if (g_strcmp0(separator, "period") == 0)
103  new_sep = g_strdup (".");
104  else
105  new_sep = g_strdup (separator);
106 
107  return new_sep;
108 }
109 /********************************************************************\
110  * gnc_configure_account_separator *
111  * updates the current account separator character *
112  * *
113  * Args: none *
114  \*******************************************************************/
115 static void
116 gnc_configure_account_separator (void)
117 {
118  auto string = gnc_prefs_get_string(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNT_SEPARATOR);
119  auto separator = gnc_normalize_account_separator (string);
120 
121  gnc_set_account_separator(separator);
122 
123  g_free(string);
124  g_free(separator);
125 }
126 
127 
128 static void
129 gnc_configure_reverse_balance (void)
130 {
131  for (auto i = 0; i < NUM_ACCOUNT_TYPES; i++)
132  reverse_type[i] = false;
133 
134  if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_INC_EXP))
135  {
136  reverse_type[ACCT_TYPE_INCOME] = true;
137  reverse_type[ACCT_TYPE_EXPENSE] = true;
138  }
139  else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_CREDIT))
140  {
141  reverse_type[ACCT_TYPE_LIABILITY] = true;
142  reverse_type[ACCT_TYPE_PAYABLE] = true;
143  reverse_type[ACCT_TYPE_EQUITY] = true;
144  reverse_type[ACCT_TYPE_INCOME] = true;
145  reverse_type[ACCT_TYPE_CREDIT] = true;
146  }
147  else if (!gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_NONE))
148  PWARN("no reversed account preference set, using none");
149 
150 }
151 
152 static void
153 gnc_reverse_balance_init (void)
154 {
155  gnc_configure_reverse_balance ();
156  reverse_balance_inited = TRUE;
157 }
158 
159 gboolean
160 gnc_reverse_balance (const Account *account)
161 {
162  if (account == nullptr)
163  return FALSE;
164 
165  auto type = xaccAccountGetType (account);
166  if ((type < 0) || (type >= NUM_ACCOUNT_TYPES))
167  return FALSE;
168 
169  if (!reverse_balance_inited)
170  gnc_reverse_balance_init ();
171 
172  return reverse_type[type];
173 }
174 
175 gboolean gnc_using_equity_type_opening_balance_account (QofBook* book)
176 {
177  return gnc_features_check_used (book, GNC_FEATURE_EQUITY_TYPE_OPENING_BALANCE);
178 }
179 
180 void gnc_set_use_equity_type_opening_balance_account (QofBook* book)
181 {
182  gnc_features_set_used (book, GNC_FEATURE_EQUITY_TYPE_OPENING_BALANCE);
183 }
184 
185 char*
186 gnc_get_default_directory (const char* section)
187 {
188  auto dir = gnc_prefs_get_string (section, GNC_PREF_LAST_PATH);
189  if (!(dir && *dir))
190  {
191  g_free (dir); // if it's ""
192 #ifdef G_OS_WIN32
193  dir = g_strdup (g_get_user_data_dir ()); /* equivalent of "My Documents" */
194 #else
195  dir = g_strdup (g_get_home_dir ());
196 #endif
197  }
198  return dir;
199 }
200 
201 void
202 gnc_set_default_directory (const char* section, const char* directory)
203 {
204  gnc_prefs_set_string(section, GNC_PREF_LAST_PATH, directory);
205 }
206 
207 QofBook *
208 gnc_get_current_book (void)
209 {
210  return qof_session_get_book (gnc_get_current_session ());
211 }
212 
213 /* If there is no current session, there is no book and we must be dealing
214  * with a new book. When gnucash is started with --nofile, there is
215  * initially no session (and no book), but by the time we check, one
216  * could have been created (for example, if an empty account tree tab is
217  * opened, a session is created which creates a new, but empty, book).
218  * A session is created and a book is loaded from a backend when gnucash is
219  * started with a file, but selecting 'new file' keeps a session open. So we
220  * need to check as well for a book with no accounts (root with no children). */
221 gboolean
222 gnc_is_new_book (void)
223 {
224  return (!gnc_current_session_exist() ||
225  gnc_account_n_children (gnc_book_get_root_account (gnc_get_current_book ())) == 0);
226 }
227 
228 #define OPTION_TAXUS_NAME "tax_US/name"
229 #define OPTION_TAXUS_TYPE "tax_US/type"
230 #define OLD_OPTION_TAXUS_NAME "book/tax_US/name"
231 #define OLD_OPTION_TAXUS_TYPE "book/tax_US/type"
232 
233 void
234 gnc_set_current_book_tax_name_type (gboolean name_changed, const char* tax_name,
235  gboolean type_changed, const char* tax_type)
236 {
237  if (name_changed)
238  {
239  if (type_changed)
240  {
241  auto book = gnc_get_current_book();
242  if ((g_strcmp0 (tax_name, "") == 0) ||
243  (tax_name == nullptr))
244  { /* change to no name */
245  if ((g_strcmp0 (tax_type, "Other") == 0) ||
246  (g_strcmp0 (tax_type, "") == 0) ||
247  (tax_type == nullptr))
248  { /* need to delete both name and type and the "tax_US" frame */
249  qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
250  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
251  qof_book_option_frame_delete(book, "tax_US");
252  }
253  else
254  { /* delete the name & change the type; keep the "tax_US" frame */
255  qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
256  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
257  }
258  }
259  else /* new name */
260  {
261  if ((g_strcmp0 (tax_type, "Other") == 0) ||
262  (g_strcmp0 (tax_type, "") == 0) ||
263  (tax_type == nullptr))
264  { /* delete the type & change the name; keep the "tax_US" frame */
265  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
266  qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
267  }
268  else /* and new type */
269  { /* change the name & change the type */
270  qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
271  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
272  }
273  }
274  }
275  else /* no type change but name changed */
276  {
277  auto book = gnc_get_current_book();
278  if ((g_strcmp0 (tax_name, "") == 0) ||
279  (tax_name == nullptr))
280  { /* change to no name */
281  if ((g_strcmp0 (tax_type, "Other") == 0) ||
282  (g_strcmp0 (tax_type, "") == 0) ||
283  (tax_type == nullptr))
284  { /* delete the name; there is no type; deleted the "tax_US" frame */
285  qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
286  qof_book_option_frame_delete(book, "tax_US");
287  }
288  else
289  { /* need to delete the name and keep "tax_US" frame */
290  qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
291  }
292  }
293  else
294  { /* change the name & keep "tax_US" frame */
295  qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
296  }
297  }
298  }
299  else /* no name change */
300  {
301  if (type_changed)
302  {
303  auto book = gnc_get_current_book();
304  if ((g_strcmp0 (tax_type, "Other") == 0) ||
305  (g_strcmp0 (tax_type, "") == 0) ||
306  (tax_type == nullptr))
307  {
308  if ((g_strcmp0 (tax_name, "") == 0) ||
309  (tax_name == nullptr))
310  {/* delete the type; there is no name; delete the "tax_US" frame */
311  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
312  qof_book_option_frame_delete(book, "tax_US");
313  }
314  else
315  { /* need to delete the type and keep "tax_US" frame */
316  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
317  }
318  }
319  else
320  { /* change the type & keep "tax_US" frame */
321  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
322  }
323  } /*else no name and no type change - do nothing */
324  }
325 }
326 
327 const char*
328 gnc_get_current_book_tax_name (void)
329 {
330  auto book = gnc_get_current_book();
331  auto tax_name = qof_book_get_string_option(book, OPTION_TAXUS_NAME);
332  if (tax_name)
333  {
334  return tax_name;
335  }
336  else
337  {
338  const char* old_option_taxus_name =
339  qof_book_get_string_option(book, OLD_OPTION_TAXUS_NAME);
340  if (old_option_taxus_name)
341  {
342  char* taxus_name = g_strdup(old_option_taxus_name);
343  const char* old_option_taxus_type =
344  qof_book_get_string_option(book, OLD_OPTION_TAXUS_TYPE);
345  if (old_option_taxus_type)
346  { /* switch both name and type and remove unused frames */
347  char* taxus_type = g_strdup(old_option_taxus_type);
348  qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
349  qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
350  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
351  qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
352  qof_book_option_frame_delete(book, "book/tax_US");
353  qof_book_option_frame_delete(book, "book");
354  g_free (taxus_type);
355  }
356  else
357  { /* switch just name and remove unused frames */
358  qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
359  qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
360  qof_book_option_frame_delete(book, "book/tax_US");
361  qof_book_option_frame_delete(book, "book");
362  }
363  g_free (taxus_name);
364  return qof_book_get_string_option(book, OPTION_TAXUS_NAME);
365  }
366  return nullptr;
367  }
368 }
369 
370 const char*
371 gnc_get_current_book_tax_type (void)
372 {
373  auto book = gnc_get_current_book();
374  auto tax_type =
375  qof_book_get_string_option(book, OPTION_TAXUS_TYPE);
376  if (tax_type)
377  {
378  return tax_type;
379  }
380  else
381  {
382  auto old_option_taxus_type =
383  qof_book_get_string_option(book, OLD_OPTION_TAXUS_TYPE);
384  if (old_option_taxus_type)
385  {
386  auto taxus_type = g_strdup(old_option_taxus_type);
387  auto old_option_taxus_name =
388  qof_book_get_string_option(book, OLD_OPTION_TAXUS_NAME);
389  if (old_option_taxus_name)
390  { /* switch both name and type and remove unused frames */
391  auto taxus_name = g_strdup(old_option_taxus_name);
392  qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
393  qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
394  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
395  qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
396  qof_book_option_frame_delete(book, "book/tax_US");
397  qof_book_option_frame_delete(book, "book");
398  g_free (taxus_name);
399  }
400  else
401  { /* switch just type and remove unused frames */
402  qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
403  qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
404  qof_book_option_frame_delete(book, "book/tax_US");
405  qof_book_option_frame_delete(book, "book");
406  }
407  g_free (taxus_type);
408  return qof_book_get_string_option(book, OPTION_TAXUS_TYPE);
409  }
410  return nullptr;
411  }
412 }
413 
414 Account *
415 gnc_get_current_root_account (void)
416 {
417  return gnc_book_get_root_account (gnc_get_current_book ());
418 }
419 
420 gnc_commodity_table *
421 gnc_get_current_commodities (void)
422 {
423  if (gnc_current_session_exist())
424  return gnc_commodity_table_get_table (gnc_get_current_book ());
425  return nullptr;
426 }
427 
428 char*
430  gboolean show_leaf_accounts)
431 {
432  if (show_leaf_accounts)
433  return g_strdup (xaccAccountGetName (account));
434  else
435  return gnc_account_get_full_name (account);
436 }
437 
438 char*
440 {
441  auto show_leaf_accounts =
442  gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL_REGISTER,
443  GNC_PREF_SHOW_LEAF_ACCT_NAMES);
444 
445  return gnc_get_account_name_for_split_register(account, show_leaf_accounts);
446 }
447 
448 Account *
449 gnc_account_lookup_for_register(const Account *base_account, const char* name)
450 {
451  auto show_leaf_accounts =
452  gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL_REGISTER,
453  GNC_PREF_SHOW_LEAF_ACCT_NAMES);
454 
455  if (show_leaf_accounts)
456  return gnc_account_lookup_by_name (base_account, name);
457  else
458  return gnc_account_lookup_by_full_name (base_account, name);
459 }
460 
461 /********************************************************************\
462  * gnc_get_reconcile_str *
463  * return the i18n'd string for the given reconciled flag *
464  * *
465  * Args: reconciled_flag - the flag to convert into a string *
466  * Returns: the i18n'd reconciled string *
467 \********************************************************************/
468 const char*
469 gnc_get_reconcile_str (char reconciled_flag)
470 {
471  switch (reconciled_flag)
472  {
473  case NREC:
474  return C_("Reconciled flag 'not cleared'", "n");
475  case CREC:
476  return C_("Reconciled flag 'cleared'", "c");
477  case YREC:
478  return C_("Reconciled flag 'reconciled'", "y");
479  case FREC:
480  return C_("Reconciled flag 'frozen'", "f");
481  case VREC:
482  return C_("Reconciled flag 'void'", "v");
483  default:
484  PERR("Bad reconciled flag\n");
485  return nullptr;
486  }
487 }
488 
489 /********************************************************************\
490  * gnc_get_reconcile_valid_flags *
491  * return a string containing the list of reconciled flags *
492  * *
493  * Returns: the i18n'd reconciled flags string *
494 \********************************************************************/
495 const char*
496 gnc_get_reconcile_valid_flags (void)
497 {
498  static const char flags[] = { NREC, CREC, YREC, FREC, VREC, 0 };
499  return flags;
500 }
501 
502 /********************************************************************\
503  * gnc_get_reconcile_flag_order *
504  * return a string containing the reconciled-flag change order *
505  * *
506  * Args: reconciled_flag - the flag to convert into a string *
507  * Returns: the i18n'd reconciled string *
508 \********************************************************************/
509 const char*
510 gnc_get_reconcile_flag_order (void)
511 {
512  static const char flags[] = { NREC, CREC, 0 };
513  return flags;
514 }
515 
516 const char*
517 gnc_get_doclink_str (char link_flag)
518 {
519  switch (link_flag)
520  {
521  case WLINK:
522  return C_("Document Link flag for 'web'", "w");
523  case FLINK:
524  return C_("Document Link flag for 'file'", "f");
525  case ' ':
526  return " ";
527  default:
528  PERR("Bad link flag");
529  return nullptr;
530  }
531 }
532 
533 const char*
535 {
536  static const char flags[] = { FLINK, WLINK, ' ', 0 };
537  return flags;
538 }
539 
540 const char*
542 {
543  static const char flags[] = { FLINK, WLINK, ' ', 0 };
544  return flags;
545 }
546 
547 static const char*
548 equity_base_name (GNCEquityType equity_type)
549 {
550  switch (equity_type)
551  {
552  case EQUITY_OPENING_BALANCE:
553  return N_("Opening Balances");
554 
555  case EQUITY_RETAINED_EARNINGS:
556  return N_("Retained Earnings");
557 
558  default:
559  return nullptr;
560  }
561 }
562 
563 Account *
564 gnc_find_or_create_equity_account (Account *root,
565  GNCEquityType equity_type,
566  gnc_commodity *currency)
567 {
568  g_return_val_if_fail (equity_type >= 0, nullptr);
569  g_return_val_if_fail (equity_type < NUM_EQUITY_TYPES, nullptr);
570  g_return_val_if_fail (currency != nullptr, nullptr);
571  g_return_val_if_fail (root != nullptr, nullptr);
572  g_return_val_if_fail (gnc_commodity_is_currency(currency), nullptr);
573 
574  auto use_eq_op_feature = equity_type == EQUITY_OPENING_BALANCE && gnc_using_equity_type_opening_balance_account (gnc_get_current_book ());
575 
576  if (use_eq_op_feature)
577  {
578  auto account = gnc_account_lookup_by_opening_balance (root, currency);
579  if (account)
580  return account;
581  }
582 
583  auto base_name = equity_base_name (equity_type);
584 
585  auto account = gnc_account_lookup_by_name(root, base_name);
586  if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
587  account = nullptr;
588 
589  if (!account)
590  {
591  base_name = base_name && *base_name ? _(base_name) : "";
592 
593  account = gnc_account_lookup_by_name(root, base_name);
594  if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
595  account = nullptr;
596  }
597 
598  auto base_name_exists = (account != nullptr);
599 
600  if (account &&
601  gnc_commodity_equiv (currency, xaccAccountGetCommodity (account)))
602  {
603  if (use_eq_op_feature)
604  xaccAccountSetIsOpeningBalance (account, TRUE);
605  return account;
606  }
607 
608  auto name = g_strconcat (base_name, " - ",
609  gnc_commodity_get_mnemonic (currency), nullptr);
610  account = gnc_account_lookup_by_name(root, name);
611  if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
612  account = nullptr;
613 
614  auto name_exists = (account != nullptr);
615 
616  if (account &&
617  gnc_commodity_equiv (currency, xaccAccountGetCommodity (account)))
618  {
619  if (use_eq_op_feature)
620  xaccAccountSetIsOpeningBalance (account, TRUE);
621  return account;
622  }
623 
624  /* Couldn't find one, so create it */
625  if (name_exists && base_name_exists)
626  {
627  PWARN ("equity account with unexpected currency");
628  g_free (name);
629  return nullptr;
630  }
631 
632  if (!base_name_exists &&
634  {
635  g_free (name);
636  name = g_strdup (base_name);
637  }
638 
639  auto parent = gnc_account_lookup_by_name(root, _("Equity"));
640  if (!parent || xaccAccountGetType (parent) != ACCT_TYPE_EQUITY)
641  parent = root;
642  g_assert(parent);
643 
644  account = xaccMallocAccount (gnc_account_get_book(root));
645 
646  xaccAccountBeginEdit (account);
647 
648  xaccAccountSetName (account, name);
650  xaccAccountSetCommodity (account, currency);
651 
652  if (use_eq_op_feature)
653  xaccAccountSetIsOpeningBalance (account, TRUE);
654 
655  xaccAccountBeginEdit (parent);
656  gnc_account_append_child (parent, account);
657  xaccAccountCommitEdit (parent);
658 
659  xaccAccountCommitEdit (account);
660 
661  g_free (name);
662 
663  return account;
664 }
665 
666 gboolean
667 gnc_account_create_opening_balance (Account *account,
668  gnc_numeric balance,
669  time64 date,
670  QofBook *book)
671 {
672  if (gnc_numeric_zero_p (balance))
673  return TRUE;
674 
675  g_return_val_if_fail (account != nullptr, FALSE);
676  auto commodity = xaccAccountGetCommodity (account);
677  g_return_val_if_fail (gnc_commodity_is_currency (commodity), FALSE);
678 
679  auto equity_account =
680  gnc_find_or_create_equity_account (gnc_account_get_root(account),
681  EQUITY_OPENING_BALANCE,
682  commodity);
683  if (!equity_account)
684  return FALSE;
685 
686  xaccAccountBeginEdit (account);
687  xaccAccountBeginEdit (equity_account);
688 
689  auto trans = xaccMallocTransaction (book);
690 
691  xaccTransBeginEdit (trans);
692 
693  xaccTransSetCurrency (trans, gnc_account_or_default_currency (account, nullptr));
695  xaccTransSetDescription (trans, _("Opening Balance"));
696 
697  auto split = xaccMallocSplit (book);
698 
699  xaccTransAppendSplit (trans, split);
700  xaccAccountInsertSplit (account, split);
701 
702  xaccSplitSetAmount (split, balance);
703  xaccSplitSetValue (split, balance);
704 
705  balance = gnc_numeric_neg (balance);
706 
707  split = xaccMallocSplit (book);
708 
709  xaccTransAppendSplit (trans, split);
710  xaccAccountInsertSplit (equity_account, split);
711 
712  xaccSplitSetAmount (split, balance);
713  xaccSplitSetValue (split, balance);
714 
715  xaccTransCommitEdit (trans);
716  xaccAccountCommitEdit (equity_account);
717  xaccAccountCommitEdit (account);
718 
719  return TRUE;
720 }
721 
722 
723 gnc_commodity *
725 {
726  auto table = gnc_get_current_commodities ();
727  auto code = gnc_locale_default_iso_currency_code ();
728 
729  auto currency = gnc_commodity_table_lookup (table,
730  GNC_COMMODITY_NS_CURRENCY,
731  code);
732 
733  return (currency ? currency : nullptr);
734 }
735 
736 gnc_commodity*
738 {
739  auto currency = gnc_locale_default_currency_nodefault ();
740 
741  return (currency ? currency :
742  gnc_commodity_table_lookup (gnc_get_current_commodities (),
743  GNC_COMMODITY_NS_CURRENCY, "USD"));
744 }
745 
746 
747 static gnc_commodity*
748 gnc_default_currency_common (char* requested_currency,
749  const char* section)
750 {
751  gnc_commodity *currency = nullptr;
752 
753  if (requested_currency)
754  return gnc_commodity_table_lookup(gnc_get_current_commodities(),
755  GNC_COMMODITY_NS_CURRENCY,
756  requested_currency);
757 
758  if (gnc_current_session_exist() &&
759  gnc_prefs_get_bool (section, GNC_PREF_CURRENCY_CHOICE_OTHER))
760  {
761  auto mnemonic = gnc_prefs_get_string(section, GNC_PREF_CURRENCY_OTHER);
762  currency = gnc_commodity_table_lookup(gnc_get_current_commodities(),
763  GNC_COMMODITY_NS_CURRENCY,
764  mnemonic);
765  DEBUG("mnemonic %s, result %p",
766  mnemonic && *mnemonic ? mnemonic : "(null)", currency);
767  g_free(mnemonic);
768  }
769 
770  if (!currency)
771  currency = gnc_locale_default_currency ();
772 
773  if (currency)
774  {
775  g_free (requested_currency);
776  }
777 
778  return currency;
779 }
780 
781 gnc_commodity*
783 {
784  return gnc_default_currency_common (user_default_currency,
785  GNC_PREFS_GROUP_GENERAL);
786 }
787 
788 gnc_commodity*
790  gboolean * currency_from_account_found)
791 {
792  gnc_commodity *currency;
793  if (!account)
794  {
795  if (currency_from_account_found)
796  *currency_from_account_found = FALSE;
797  return gnc_default_currency();
798  }
799 
800  currency = gnc_account_get_currency_or_parent(account);
801  if (currency)
802  {
803  if (currency_from_account_found)
804  *currency_from_account_found = TRUE;
805  }
806  else
807  {
808  if (currency_from_account_found)
809  *currency_from_account_found = FALSE;
810  currency = gnc_default_currency();
811  }
812  return currency;
813 }
814 
815 
816 
817 gnc_commodity*
819 {
820  return gnc_default_currency_common (user_report_currency,
821  GNC_PREFS_GROUP_GENERAL_REPORT);
822 }
823 
824 static void
825 gnc_currency_changed_cb (GSettings *settings, char* key, gpointer user_data)
826 {
827  user_default_currency = nullptr;
828  user_report_currency = nullptr;
829  gnc_hook_run(HOOK_CURRENCY_CHANGED, nullptr);
830 }
831 
832 
834 gnc_default_print_info (gboolean use_symbol)
835 {
836  static GNCPrintAmountInfo info;
837  static bool got_it = false;
838 
839  /* These must be updated each time. */
840  info.use_symbol = use_symbol ? 1 : 0;
841  info.commodity = gnc_default_currency ();
842 
843  if (got_it)
844  return info;
845 
846  auto lc = gnc_localeconv ();
847 
848  info.max_decimal_places = lc->frac_digits;
849  info.min_decimal_places = lc->frac_digits;
850 
851  info.use_separators = 1;
852  info.use_locale = 1;
853  info.monetary = 1;
854  info.force_fit = 0;
855  info.round = 0;
856 
857  got_it = TRUE;
858 
859  return info;
860 }
861 
862 static bool
863 is_decimal_fraction (int fraction, uint8_t *max_decimal_places_p)
864 {
865  uint8_t max_decimal_places = 0;
866 
867  if (fraction <= 0)
868  return false;
869 
870  while (fraction != 1)
871  {
872  if (fraction % 10 != 0)
873  return false;
874 
875  fraction = fraction / 10;
876  max_decimal_places += 1;
877  }
878 
879  if (max_decimal_places_p)
880  *max_decimal_places_p = max_decimal_places;
881 
882  return true;
883 }
884 
886 gnc_commodity_print_info (const gnc_commodity *commodity,
887  gboolean use_symbol)
888 {
889  GNCPrintAmountInfo info;
890 
891  if (commodity == nullptr)
892  return gnc_default_print_info (use_symbol);
893 
894  info.commodity = commodity;
895 
896  auto is_iso = gnc_commodity_is_iso (commodity);
897 
898  if (is_decimal_fraction (gnc_commodity_get_fraction (commodity),
899  &info.max_decimal_places))
900  {
901  if (is_iso)
902  info.min_decimal_places = info.max_decimal_places;
903  else
904  info.min_decimal_places = 0;
905  }
906  else
907  info.max_decimal_places = info.min_decimal_places = 0;
908 
909  info.use_separators = 1;
910  info.use_symbol = use_symbol ? 1 : 0;
911  info.use_locale = is_iso ? 1 : 0;
912  info.monetary = 1;
913  info.force_fit = 0;
914  info.round = 0;
915 
916  return info;
917 }
918 
919 static GNCPrintAmountInfo
920 gnc_account_print_info_helper(const Account *account, gboolean use_symbol,
921  gnc_commodity * (*efffunc)(const Account *),
922  int (*scufunc)(const Account*))
923 {
924  GNCPrintAmountInfo info;
925 
926  if (account == nullptr)
927  return gnc_default_print_info (use_symbol);
928 
929  info.commodity = efffunc (account);
930 
931  auto is_iso = gnc_commodity_is_iso (info.commodity);
932 
933  auto scu = scufunc (account);
934 
935  if (is_decimal_fraction (scu, &info.max_decimal_places))
936  {
937  if (is_iso)
938  info.min_decimal_places = info.max_decimal_places;
939  else
940  info.min_decimal_places = 0;
941  }
942  else
943  info.max_decimal_places = info.min_decimal_places = 0;
944 
945  info.use_separators = 1;
946  info.use_symbol = use_symbol ? 1 : 0;
947  info.use_locale = is_iso ? 1 : 0;
948  info.monetary = 1;
949  info.force_fit = 0;
950  info.round = 0;
951 
952  return info;
953 }
954 
956 gnc_account_print_info (const Account *account, gboolean use_symbol)
957 {
958  return gnc_account_print_info_helper(account, use_symbol,
961 }
962 
964 gnc_split_amount_print_info (Split *split, gboolean use_symbol)
965 {
966  if (!split)
967  {
968  GNCPrintAmountInfo info = gnc_default_share_print_info ();
969  info.use_symbol = use_symbol;
970  return info;
971  }
972 
973  return gnc_account_print_info (xaccSplitGetAccount (split), use_symbol);
974 }
975 
976 static GNCPrintAmountInfo
977 gnc_default_print_info_helper (int decplaces)
978 {
979  GNCPrintAmountInfo info;
980 
981  info.commodity = nullptr;
982 
983  info.max_decimal_places = decplaces;
984  info.min_decimal_places = 0;
985 
986  info.use_separators = 1;
987  info.use_symbol = 0;
988  info.use_locale = 1;
989  info.monetary = 1;
990  info.force_fit = 0;
991  info.round = 0;
992 
993  return info;
994 }
995 
997 gnc_default_share_print_info (void)
998 {
999  static GNCPrintAmountInfo info;
1000  static bool got_it = false;
1001 
1002  if (!got_it)
1003  {
1004  info = gnc_default_print_info_helper (5);
1005  info.monetary = 0;
1006  got_it = TRUE;
1007  }
1008 
1009  return info;
1010 }
1011 
1013 gnc_share_print_info_places (int decplaces)
1014 {
1015  GNCPrintAmountInfo info;
1016 
1017  info = gnc_default_share_print_info ();
1018  info.max_decimal_places = decplaces;
1019  info.min_decimal_places = decplaces;
1020  info.force_fit = 1;
1021  info.round = 1;
1022  return info;
1023 }
1024 
1026 gnc_price_print_info (const gnc_commodity *curr, gboolean use_symbol)
1027 {
1028  GNCPrintAmountInfo info;
1029  auto force = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL,
1030  GNC_PREF_PRICES_FORCE_DECIMAL);
1031  info.commodity = curr;
1032 
1033  if (info.commodity)
1034  {
1035  int frac = gnc_commodity_get_fraction (curr);
1036  guint8 decplaces = 0;
1037  while (frac != 1 && (frac % 10) == 0 && (frac /= 10)) ++decplaces;
1038  if (force)
1039  decplaces += 2;
1040  info.max_decimal_places = decplaces;
1041  info.min_decimal_places = decplaces;
1042  }
1043  else
1044  {
1045  info.max_decimal_places = 6;
1046  info.min_decimal_places = 0;
1047  }
1048 
1049  info.use_separators = 1;
1050  info.use_symbol = use_symbol ? 1 : 0;
1051  info.use_locale = 1;
1052  info.monetary = 1;
1053 
1054  info.force_fit = force;
1055  info.round = force;
1056  return info;
1057 }
1058 
1060 gnc_default_price_print_info (const gnc_commodity *curr)
1061 {
1062  return gnc_price_print_info (curr, FALSE);
1063 }
1064 
1065 
1067 gnc_integral_print_info (void)
1068 {
1069  static GNCPrintAmountInfo info;
1070  static bool got_it = false;
1071 
1072  if (!got_it)
1073  {
1074  info = gnc_default_print_info_helper (0);
1075  got_it = TRUE;
1076  }
1077 
1078  return info;
1079 }
1080 
1081 /* Utility function for printing non-negative amounts */
1082 static int
1083 PrintAmountInternal(char* buf, gnc_numeric val, const GNCPrintAmountInfo *info)
1084 {
1085  auto *lc = gnc_localeconv();
1086  constexpr size_t buf_size = 128;
1087  char temp_buf[buf_size];
1088 
1089  g_return_val_if_fail (info != nullptr, 0);
1090 
1091  if (gnc_numeric_check (val))
1092  {
1093  PWARN ("Bad numeric: %s.",
1095  *buf = '\0';
1096  return 0;
1097  }
1098 
1099  /* Print the absolute value, but remember sign */
1100  auto value_is_negative = gnc_numeric_negative_p (val);
1101  val = gnc_numeric_abs (val);
1102 
1103  /* Try to print as decimal. */
1104  auto value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
1105  if (!value_is_decimal && info->force_fit && info->round)
1106  {
1107  /* if there's a commodity use 100x the commodity's fraction. N.B. This
1108  * assumes that commodity fractions are multiples of 10, a reasonable
1109  * assumption in 2018. Otherwise, if there's a reasonable
1110  * max_decimal_places, use that.
1111  */
1112  const int64_t denom = info->commodity ?
1113  gnc_commodity_get_fraction(info->commodity) * 100 :
1114  (info->max_decimal_places &&
1115  info->max_decimal_places <= maximum_decimals) ?
1116  pow_10[info->max_decimal_places] : pow_10[maximum_decimals];
1118  value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
1119  }
1120  auto min_dp = info->min_decimal_places;
1121  auto max_dp = info->max_decimal_places;
1122 
1123  /* Don to limit the number of decimal places _UNLESS_ force_fit is
1124  * true. */
1125  if (!info->force_fit)
1126  max_dp = 99;
1127 
1128  /* rounding? -- can only ROUND if force_fit is also true */
1129  if (value_is_decimal && info->round && info->force_fit)
1130  {
1131  /* Limit the denom to 10^13 ~= 2^44, leaving max at ~524288 */
1132  gnc_numeric rounding{5, pow_10[max_dp + 1]};
1133 
1134  val = gnc_numeric_add(val, rounding, val.denom,
1136 
1137  if (gnc_numeric_check(val))
1138  {
1139  PWARN ("Bad numeric from rounding: %s.",
1141  *buf = '\0';
1142  return 0;
1143  }
1144  }
1145 
1146  /* calculate the integer part and the remainder */
1147  auto whole = gnc_numeric_convert(val, 1, GNC_HOW_RND_TRUNC);
1148  val = gnc_numeric_sub (val, whole, GNC_DENOM_AUTO, GNC_HOW_RND_NEVER);
1149  if (gnc_numeric_check (val))
1150  {
1151  PWARN ("Problem with remainder: %s.",
1153  *buf = '\0';
1154  return 0;
1155  }
1156 
1157  // Value may now be decimal, for example if the factional part is zero
1158  value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
1159  /* print the integer part without separators */
1160  snprintf(temp_buf, buf_size, "%" G_GINT64_FORMAT, whole.num);
1161  auto num_whole_digits = strlen (temp_buf);
1162 
1163  if (!info->use_separators)
1164  strcpy (buf, temp_buf);
1165  else
1166  {
1167  char* separator;
1168  char* group;
1169 
1170  if (info->monetary)
1171  {
1172  separator = lc->mon_thousands_sep;
1173  group = lc->mon_grouping;
1174  }
1175  else
1176  {
1177  separator = lc->thousands_sep;
1178  group = lc->grouping;
1179  }
1180 
1181  auto buf_ptr = buf;
1182  auto temp_ptr = &temp_buf[num_whole_digits - 1];
1183  auto group_count = 0;
1184 
1185  while (temp_ptr != temp_buf)
1186  {
1187  *buf_ptr++ = *temp_ptr--;
1188 
1189  if (*group != CHAR_MAX)
1190  {
1191  group_count++;
1192 
1193  if (group_count == *group)
1194  {
1195  g_utf8_strncpy(buf_ptr, separator, 1);
1196  buf_ptr = g_utf8_find_next_char(buf_ptr, nullptr);
1197  group_count = 0;
1198 
1199  /* Peek ahead at the next group code */
1200  switch (group[1])
1201  {
1202  /* A null char means repeat the last group indefinitely */
1203  case '\0':
1204  break;
1205  /* CHAR_MAX means no more grouping allowed */
1206  case CHAR_MAX:
1207  /* fall through */
1208  /* Anything else means another group size */
1209  default:
1210  group++;
1211  break;
1212  }
1213  }
1214  }
1215  }
1216 
1217  /* We built the string backwards, now reverse */
1218  *buf_ptr++ = *temp_ptr;
1219  *buf_ptr = '\0';
1220  auto rev_buf = g_utf8_strreverse(buf, -1);
1221  strcpy (buf, rev_buf);
1222  g_free(rev_buf);
1223  } /* endif */
1224 
1225  /* at this point, buf contains the whole part of the number */
1226 
1227  /* If it's not decimal, print the fraction as an expression. */
1228  if (!value_is_decimal)
1229  {
1230  val = gnc_numeric_reduce (val);
1231 
1232  if (val.denom > 0)
1233  snprintf (temp_buf, buf_size, "%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
1234  val.num, val.denom);
1235  else
1236  snprintf (temp_buf, buf_size, "%" G_GINT64_FORMAT " * %" G_GINT64_FORMAT,
1237  val.num, -val.denom);
1238 
1239  if (whole.num == 0)
1240  *buf = '\0';
1241  else if (value_is_negative)
1242  strcat(buf, " - ");
1243  else
1244  strcat(buf, " + ");
1245 
1246  strcat (buf, temp_buf);
1247  }
1248  else
1249  {
1250  guint8 num_decimal_places = 0;
1251  char* temp_ptr = temp_buf;
1252 
1253  auto decimal_point = info->monetary
1254  ? lc->mon_decimal_point
1255  : lc->decimal_point;
1256  g_utf8_strncpy(temp_ptr, decimal_point, 1);
1257  temp_ptr = g_utf8_find_next_char(temp_ptr, nullptr);
1258 
1259  while (!gnc_numeric_zero_p (val)
1260  && (val.denom != 1)
1261  && (num_decimal_places < max_dp))
1262  {
1263  gint64 digit;
1264 
1265  val.denom = val.denom / 10;
1266 
1267  digit = val.num / val.denom;
1268 
1269  *temp_ptr++ = digit + '0';
1270  num_decimal_places++;
1271 
1272  val.num = val.num - (digit * val.denom);
1273  }
1274 
1275  while (num_decimal_places < min_dp)
1276  {
1277  *temp_ptr++ = '0';
1278  num_decimal_places++;
1279  }
1280 
1281  /* cap the end and move to the last character */
1282  *temp_ptr-- = '\0';
1283 
1284  /* Here we strip off trailing decimal zeros per the argument. */
1285  while (*temp_ptr == '0' && num_decimal_places > min_dp)
1286  {
1287  *temp_ptr-- = '\0';
1288  num_decimal_places--;
1289  }
1290 
1291  if (num_decimal_places > max_dp)
1292  {
1293  PWARN ("max_decimal_places too small; limit %d, value %s%s",
1294  info->max_decimal_places, buf, temp_buf);
1295  }
1296 
1297  strcat (buf, temp_buf);
1298  }
1299 
1300  return strlen(buf);
1301 }
1302 
1306 int
1307 xaccSPrintAmount (char* bufp, gnc_numeric val, GNCPrintAmountInfo info)
1308 {
1309  auto orig_bufp = bufp;
1310  auto currency_symbol = "";
1311  const char* sign;
1312 
1313  char cs_precedes;
1314  char sep_by_space;
1315  char sign_posn;
1316 
1317  bool print_sign = true;
1318  bool print_absolute = false;
1319 
1320  if (!bufp)
1321  return 0;
1322 
1323  auto lc = gnc_localeconv();
1324  if (info.use_locale)
1325  if (gnc_numeric_negative_p (val))
1326  {
1327  cs_precedes = lc->n_cs_precedes;
1328  sep_by_space = lc->n_sep_by_space;
1329  }
1330  else
1331  {
1332  cs_precedes = lc->p_cs_precedes;
1333  sep_by_space = lc->p_sep_by_space;
1334  }
1335  else
1336  {
1337  cs_precedes = TRUE;
1338  sep_by_space = TRUE;
1339  }
1340 
1341  if (info.commodity && info.use_symbol)
1342  {
1343  currency_symbol = gnc_commodity_get_nice_symbol (info.commodity);
1344  if (!gnc_commodity_is_iso (info.commodity))
1345  {
1346  cs_precedes = FALSE;
1347  sep_by_space = TRUE;
1348  }
1349  }
1350 
1351  if (gnc_numeric_negative_p (val))
1352  {
1353  sign = lc->negative_sign;
1354  sign_posn = lc->n_sign_posn;
1355  }
1356  else
1357  {
1358  sign = lc->positive_sign;
1359  sign_posn = lc->p_sign_posn;
1360  }
1361 
1362  if (gnc_numeric_zero_p (val) || (sign == nullptr) || (sign[0] == 0))
1363  print_sign = FALSE;
1364 
1365  /* See if we print sign now */
1366  if (print_sign && (sign_posn == 1))
1367  bufp = g_stpcpy(bufp, sign);
1368 
1369  /* Now see if we print currency */
1370  if (cs_precedes)
1371  {
1372  /* See if we print sign now */
1373  if (print_sign && (sign_posn == 3))
1374  bufp = g_stpcpy(bufp, sign);
1375 
1376  if (info.use_symbol)
1377  {
1378  bufp = g_stpcpy(bufp, currency_symbol);
1379  if (sep_by_space)
1380  bufp = g_stpcpy(bufp, " ");
1381  }
1382 
1383  /* See if we print sign now */
1384  if (print_sign && (sign_posn == 4))
1385  bufp = g_stpcpy(bufp, sign);
1386  }
1387 
1388  /* Now see if we print parentheses */
1389  if (print_sign && (sign_posn == 0))
1390  {
1391  bufp = g_stpcpy(bufp, "(");
1392  print_absolute = TRUE;
1393  }
1394 
1395  /* Now print the value */
1396  bufp += PrintAmountInternal(bufp,
1397  print_absolute ? gnc_numeric_abs(val) : val,
1398  &info);
1399 
1400  /* Now see if we print parentheses */
1401  if (print_sign && (sign_posn == 0))
1402  bufp = g_stpcpy(bufp, ")");
1403 
1404  /* Now see if we print currency */
1405  if (!cs_precedes)
1406  {
1407  /* See if we print sign now */
1408  if (print_sign && (sign_posn == 3))
1409  bufp = g_stpcpy(bufp, sign);
1410 
1411  if (info.use_symbol)
1412  {
1413  if (sep_by_space)
1414  bufp = g_stpcpy(bufp, " ");
1415  bufp = g_stpcpy(bufp, currency_symbol);
1416  }
1417 
1418  /* See if we print sign now */
1419  if (print_sign && (sign_posn == 4))
1420  bufp = g_stpcpy(bufp, sign);
1421  }
1422 
1423  /* See if we print sign now */
1424  if (print_sign && (sign_posn == 2))
1425  bufp = g_stpcpy(bufp, sign);
1426 
1427  /* return length of printed string */
1428  return (bufp - orig_bufp);
1429 }
1430 
1431 #define BUFLEN 1024
1432 
1433 const char*
1434 xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info)
1435 {
1436  /* hack alert -- this is not thread safe ... */
1437  static char buf[BUFLEN];
1438 
1439  if (!xaccSPrintAmount (buf, val, info))
1440  buf[0] = '\0';
1441 
1442  /* its OK to return buf, since we declared it static */
1443  return buf;
1444 }
1445 
1446 const char*
1448 {
1449  /* hack alert -- this is not thread safe ... */
1450  static char buf[BUFLEN];
1451  static const char ltr_isolate[] = { '\xe2', '\x81', '\xa6' };
1452  static const char ltr_pop_isolate[] = { '\xe2', '\x81', '\xa9' };
1453  auto offset = info.use_symbol ? 3 : 0;
1454 
1455  if (!gnc_commodity_is_currency (info.commodity))
1456  offset = 0;
1457 
1458  memset (buf, 0, BUFLEN);
1459  if (!xaccSPrintAmount (buf + offset, val, info))
1460  {
1461  buf[0] = '\0';
1462  return buf;
1463  };
1464 
1465  if (offset == 0)
1466  return buf;
1467 
1468  memcpy (buf, ltr_isolate, 3);
1469 
1470  if (buf[BUFLEN - 4] == '\0')
1471  {
1472  auto length = strlen (buf);
1473  memcpy (buf + length, ltr_pop_isolate, 3);
1474  }
1475  else
1476  {
1477  buf[BUFLEN - 1] = '\0';
1478  memcpy (buf + BUFLEN - 4, ltr_pop_isolate, 3);
1479 
1480  PWARN("buffer length %d exceeded, string truncated was %s", BUFLEN, buf);
1481  }
1482  /* its OK to return buf, since we declared it static
1483  and is immediately g_strdup'd */
1484  return buf;
1485 }
1486 
1487 char*
1489 {
1490  static const char* ltr = "\u2066"; // ltr isolate
1491  static const char* pop = "\u2069"; // pop directional formatting
1492 
1493  if (!text)
1494  return nullptr;
1495 
1496  return g_strconcat (ltr, text, pop, nullptr);
1497 }
1498 
1499 /********************************************************************\
1500  ********************************************************************/
1501 
1502 #define FUDGE .00001
1503 
1504 /* This function is basically untranslatable. I'd
1505  guess out of the 29 translations we have, 20 will have their number
1506  wordings in a totally different way than English has (not to
1507  mention gender-dependent number endings). Which means this
1508  word-by-word translation will be useless or even plain
1509  wrong. For this reason, we don't even start to pretend a
1510  word-by-word translation would be of any use, so we don't mark any
1511  of these strings for translation. cstim, 2007-04-15. */
1512 static const char* small_numbers[] =
1513 {
1514  /* Translators: This section is for generating the "amount, in
1515  words" field when printing a check. This function gets the
1516  wording right for English, but unfortunately not for most other
1517  languages. Decide for yourself whether the check printing is
1518  actually needed in your language; if not, you can safely skip the
1519  translation of all of these strings. */
1520  "Zero", "One", "Two", "Three", "Four",
1521  "Five", "Six", "Seven", "Eight", "Nine",
1522  "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
1523  "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen",
1524  "Twenty"
1525 };
1526 static const char* medium_numbers[] =
1527 {
1528  "Zero", "Ten", "Twenty", "Thirty", "Forty",
1529  "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"
1530 };
1531 static const char* big_numbers[] =
1532 {
1533  /* Translators: This is the word for the number 10^2 */
1534  "Hundred",
1535  /* Translators: This is the word for the number 10^3 */
1536  "Thousand",
1537  /* Translators: This is the word for the number 10^6, one thousand
1538  thousands. */
1539  "Million",
1540  /* Translators: This is the word for the number 10^9, one thousand
1541  millions. WATCH OUT: In British English and many other languages
1542  this word is used for 10^12 which is one million millions! In
1543  contrast to this, here in GnuCash this is used in the American
1544  English meaning of 10^9. */
1545  "Billion",
1546  /* Translators: This is the word for the number 10^12, one million
1547  millions. */
1548  "Trillion",
1549  /* Translators: This is the word for the number 10^15 */
1550  "Quadrillion",
1551  /* Translators: This is the word for the number 10^18 */
1552  "Quintillion"
1553 };
1554 
1555 static char*
1556 integer_to_words(gint64 val)
1557 {
1558  if (val == 0)
1559  return g_strdup("zero");
1560  if (val < 0)
1561  val = -val;
1562 
1563  auto result = g_string_sized_new(100);
1564 
1565  while (val >= 1000)
1566  {
1567  int log_val = log10(val) / 3 + FUDGE;
1568  int pow_val = exp(log_val * 3 * G_LN10) + FUDGE;
1569  int this_part = val / pow_val;
1570  val -= this_part * pow_val;
1571  auto tmp = integer_to_words(this_part);
1572  g_string_append_printf(result, "%s %s ", tmp, gettext(big_numbers[log_val]));
1573  g_free(tmp);
1574  }
1575 
1576  if (val >= 100)
1577  {
1578  int this_part = val / 100;
1579  val -= this_part * 100;
1580  g_string_append_printf(result, "%s %s ",
1581  gettext(small_numbers[this_part]),
1582  gettext(big_numbers[0]));
1583  }
1584 
1585  if (val > 20)
1586  {
1587  int this_part = val / 10;
1588  val -= this_part * 10;
1589  g_string_append(result, gettext(medium_numbers[this_part]));
1590  g_string_append_c(result, ' ');
1591  }
1592 
1593  if (val > 0)
1594  {
1595  int this_part = val;
1596  g_string_append(result, gettext(small_numbers[this_part]));
1597  g_string_append_c(result, ' ');
1598  }
1599 
1600  result = g_string_truncate(result, result->len - 1);
1601  return g_string_free(result, FALSE);
1602 }
1603 
1604 #ifdef _MSC_VER
1605 static double round(double x)
1606 {
1607  // A simple round() implementation because MSVC doesn't seem to have that
1608  return floor(x + 0.5);
1609 }
1610 #endif
1611 
1612 char*
1613 number_to_words(double val, int64_t denom)
1614 {
1615  if (val < 0) val = -val;
1616  if (denom < 0) denom = -denom;
1617 
1618  auto int_part = floor(val);
1619  auto frac_part = static_cast<int64_t>(round((val - int_part) * denom));
1620 
1621  auto int_string = integer_to_words(int_part);
1622  /* Inside of the gettext macro _(...) we must not use any macros but
1623  only plain string literals. For this reason, convert the strings
1624  separately. */
1625  auto nomin_string = g_strdup_printf("%02" PRId64, frac_part);
1626  auto denom_string = g_strdup_printf("%" PRId64, denom);
1627  auto full_string =
1628  /* Translators: This is for the "amount, in words" field in check
1629  printing. The first %s is the integer amount of dollars (or
1630  whatever currency), the second and third %s the cent amount as
1631  a fraction, e.g. 47/100. */
1632  g_strdup_printf("%s and %s/%s",
1633  int_string, nomin_string, denom_string);
1634  g_free(int_string);
1635  g_free(nomin_string);
1636  g_free(denom_string);
1637  return full_string;
1638 }
1639 
1640 char*
1641 numeric_to_words(gnc_numeric val)
1642 {
1643  return number_to_words(gnc_numeric_to_double(val),
1644  gnc_numeric_denom(val));
1645 }
1646 
1647 const char*
1648 printable_value (double val, int denom)
1649 {
1650  auto num = gnc_numeric_create(round(val * denom), denom);
1651  auto info = gnc_share_print_info_places(log10(denom));
1652  return xaccPrintAmount (num, info);
1653 }
1654 
1655 /********************************************************************\
1656  * xaccParseAmount *
1657  * parses amount strings using locale data *
1658  * *
1659  * Args: in_str -- pointer to string rep of num *
1660  * monetary -- boolean indicating whether value is monetary *
1661  * result -- pointer to result location, may be nullptr *
1662  * endstr -- used to store first digit not used in parsing *
1663  * Return: gboolean -- TRUE if a number found and parsed *
1664  * If FALSE, result is not changed *
1665 \********************************************************************/
1666 
1667 /* Parsing state machine states */
1668 enum ParseState
1669 {
1670  START_ST, /* Parsing initial whitespace */
1671  NEG_ST, /* Parsed a negative sign or a left paren */
1672  NUMER_ST, /* Parsing digits before grouping and decimal characters */
1673  FRAC_ST, /* Parsing the fractional portion of a number */
1674  DONE_ST, /* Finished, number is correct module grouping constraints */
1675  NO_NUM_ST /* Finished, number was malformed */
1676 };
1677 
1678 #define done_state(state) (((state) == DONE_ST) || ((state) == NO_NUM_ST))
1679 
1680 static inline int64_t
1681 multiplier (int num_decimals)
1682 {
1683  switch (num_decimals)
1684  {
1685  case 12:
1686  return 1000000000000;
1687  case 11:
1688  return 100000000000;
1689  case 10:
1690  return 10000000000;
1691  case 9:
1692  return 1000000000;
1693  case 8:
1694  return 100000000;
1695  case 7:
1696  return 10000000;
1697  case 6:
1698  return 1000000;
1699  case 5:
1700  return 100000;
1701  case 4:
1702  return 10000;
1703  case 3:
1704  return 1000;
1705  case 2:
1706  return 100;
1707  case 1:
1708  return 10;
1709  case 0:
1710  return 1;
1711  default:
1712  PERR("bad fraction length");
1713  g_assert_not_reached();
1714  break;
1715  }
1716 
1717  return 1;
1718 }
1719 
1720 static gboolean
1721 xaccParseAmountInternal (const char* in_str, gboolean monetary,
1722  gunichar negative_sign, gunichar decimal_point,
1723  gunichar group_separator, const char* ignore_list,
1724  gboolean use_auto_decimal,
1725  gnc_numeric *result, char** endstr)
1726 {
1727  /* Initialize *endstr to in_str */
1728  if (endstr)
1729  *endstr = (char* ) in_str;
1730 
1731  if (!in_str)
1732  return FALSE;
1733 
1734  const char* in;
1735  if (!g_utf8_validate(in_str, -1, &in))
1736  {
1737  printf("Invalid utf8 string '%s'. Bad character at position %ld.\n",
1738  in_str, g_utf8_pointer_to_offset (in_str, in));
1739  return FALSE;
1740  }
1741 
1742  /* 'out_str' will be used to store digits for numeric conversion.
1743  * 'out' will be used to traverse out_str. */
1744  auto out_str = g_new(gchar, strlen(in_str) + 128);
1745  auto out = out_str;
1746 
1747  /* 'in' is used to traverse 'in_str'. */
1748  in = in_str;
1749 
1750  auto is_negative = false;
1751  auto got_decimal = false;
1752  auto need_paren = false;
1753  int64_t numer = 0;
1754  int64_t denom = 1;
1755 
1756  /* Initialize the state machine */
1757  auto state = START_ST;
1758 
1759  /* This while loop implements a state machine for parsing numbers. */
1760  while (TRUE)
1761  {
1762  auto next_state = state;
1763  auto uc = g_utf8_get_char(in);
1764 
1765  /* Ignore anything in the 'ignore list' */
1766  if (ignore_list && uc && g_utf8_strchr(ignore_list, -1, uc))
1767  {
1768  in = g_utf8_next_char(in);
1769  continue;
1770  }
1771 
1772  /* Note we never need to check for the end of 'in_str' explicitly.
1773  * The 'else' clauses on all the state transitions will handle that. */
1774  switch (state)
1775  {
1776  /* START_ST means we have parsed 0 or more whitespace characters */
1777  case START_ST:
1778  if (g_unichar_isdigit(uc))
1779  {
1780  int count = g_unichar_to_utf8(uc, out);
1781  out += count; /* we record the digits themselves in out_str
1782  * for later conversion by libc routines */
1783  next_state = NUMER_ST;
1784  }
1785  else if (uc == decimal_point)
1786  next_state = FRAC_ST;
1787  else if (g_unichar_isspace(uc))
1788  ;
1789  else if (uc == negative_sign)
1790  {
1791  is_negative = TRUE;
1792  next_state = NEG_ST;
1793  }
1794  else if (uc == '(')
1795  {
1796  is_negative = TRUE;
1797  need_paren = TRUE;
1798  next_state = NEG_ST;
1799  }
1800  else
1801  next_state = NO_NUM_ST;
1802 
1803  break;
1804 
1805  /* NEG_ST means we have just parsed a negative sign. For now,
1806  * we only recognize formats where the negative sign comes first. */
1807  case NEG_ST:
1808  if (g_unichar_isdigit(uc))
1809  {
1810  int count = g_unichar_to_utf8(uc, out);
1811  out += count;
1812  next_state = NUMER_ST;
1813  }
1814  else if (uc == decimal_point)
1815  next_state = FRAC_ST;
1816  else if (g_unichar_isspace(uc))
1817  ;
1818  else
1819  next_state = NO_NUM_ST;
1820 
1821  break;
1822 
1823  /* NUMER_ST means we have started parsing the number, but
1824  * have not encountered a decimal separator. */
1825  case NUMER_ST:
1826  if (g_unichar_isdigit(uc))
1827  {
1828  int count = g_unichar_to_utf8(uc, out);
1829  out += count;
1830  }
1831  else if (uc == decimal_point)
1832  next_state = FRAC_ST;
1833  else if (uc == group_separator)
1834  ; //ignore it
1835  else if (uc == ')' && need_paren)
1836  {
1837  next_state = DONE_ST;
1838  need_paren = FALSE;
1839  }
1840  else
1841  next_state = DONE_ST;
1842 
1843  break;
1844 
1845  /* FRAC_ST means we are now parsing fractional digits. */
1846  case FRAC_ST:
1847  if (g_unichar_isdigit(uc))
1848  {
1849  int count = g_unichar_to_utf8(uc, out);
1850  out += count;
1851  }
1852  else if (uc == decimal_point)
1853  {
1854  /* If a subsequent decimal point is also whitespace,
1855  * assume it was intended as such and stop parsing.
1856  * Otherwise, there is a problem. */
1857  if (g_unichar_isspace(decimal_point))
1858  next_state = DONE_ST;
1859  else
1860  next_state = NO_NUM_ST;
1861  }
1862  else if (uc == group_separator)
1863  {
1864  /* If a subsequent group separator is also whitespace,
1865  * assume it was intended as such and stop parsing.
1866  * Otherwise ignore it. */
1867  if (g_unichar_isspace(group_separator))
1868  next_state = DONE_ST;
1869  }
1870  else if (uc == ')' && need_paren)
1871  {
1872  next_state = DONE_ST;
1873  need_paren = FALSE;
1874  }
1875  else
1876  next_state = DONE_ST;
1877 
1878  break;
1879 
1880  default:
1881  PERR("bad state");
1882  g_assert_not_reached();
1883  break;
1884  }
1885 
1886  /* If we're moving into the FRAC_ST or out of the machine
1887  * without going through FRAC_ST, record the integral value. */
1888  if (((next_state == FRAC_ST) && (state != FRAC_ST)) ||
1889  ((next_state == DONE_ST) && !got_decimal))
1890  {
1891  *out = '\0';
1892 
1893  if (*out_str && sscanf(out_str, "%" SCNd64, &numer) < 1)
1894  next_state = NO_NUM_ST;
1895  else if (next_state == FRAC_ST)
1896  {
1897  /* reset the out pointer to record the fraction */
1898  out = out_str;
1899  *out = '\0';
1900 
1901  got_decimal = TRUE;
1902  }
1903  }
1904 
1905  state = next_state;
1906  if (done_state (state))
1907  break;
1908 
1909  in = g_utf8_next_char(in);
1910  }
1911 
1912  /* If there was an error, just quit */
1913  if (need_paren || (state == NO_NUM_ST))
1914  {
1915  g_free(out_str);
1916  return FALSE;
1917  }
1918 
1919  /* Cap the end of the fraction string, if any */
1920  *out = '\0';
1921 
1922  /* Add in fractional value */
1923  if (got_decimal && *out_str)
1924  {
1925 
1926  auto len = strlen(out_str);
1927 
1928  if (len > 12)
1929  {
1930  out_str[12] = '\0';
1931  len = 12;
1932  }
1933 
1934  int64_t fraction;
1935  if (sscanf (out_str, "%" SCNd64 , &fraction) < 1)
1936  {
1937  g_free(out_str);
1938  return FALSE;
1939  }
1940 
1941  denom = multiplier(len);
1942  numer *= denom;
1943  numer += fraction;
1944  }
1945  else if (monetary && use_auto_decimal && !got_decimal)
1946  {
1947  if ((auto_decimal_places > 0) && (auto_decimal_places <= 12))
1948  {
1949  denom = multiplier(auto_decimal_places);
1950 
1951  /* No need to multiply numer by denom at this point,
1952  * since by specifying the auto decimal places the
1953  * user has effectively determined the scaling factor
1954  * for the numerator they entered.
1955  */
1956  }
1957  }
1958 
1959  if (result)
1960  {
1961  *result = gnc_numeric_create (numer, denom);
1962  if (is_negative)
1963  *result = gnc_numeric_neg (*result);
1964  }
1965 
1966  if (endstr)
1967  *endstr = (char* ) in;
1968 
1969  g_free (out_str);
1970 
1971  return TRUE;
1972 }
1973 
1974 
1975 static gboolean
1976 xaccParseAmountBasicInternal (const char* in_str, gboolean monetary,
1977  gboolean use_auto_decimal, gnc_numeric *result,
1978  char** endstr, gboolean skip)
1979 {
1980  struct lconv *lc = gnc_localeconv();
1981  gunichar negative_sign = g_utf8_get_char(lc->negative_sign);
1982 
1983  gunichar decimal_point;
1984  gunichar group_separator;
1985  if (monetary)
1986  {
1987  group_separator = g_utf8_get_char(lc->mon_thousands_sep);
1988  decimal_point = g_utf8_get_char(lc->mon_decimal_point);
1989  }
1990  else
1991  {
1992  group_separator = g_utf8_get_char(lc->thousands_sep);
1993  decimal_point = g_utf8_get_char(lc->decimal_point);
1994  }
1995 
1996  const char* ignore = nullptr;
1997  if (skip)
1998  {
1999  /* We want the locale's positive sign to be ignored.
2000  * If the locale doesn't specify one, we assume "+" as
2001  * an optional positive sign and ignore that */
2002  ignore = lc->positive_sign;
2003  if (!ignore || !*ignore)
2004  ignore = "+";
2005  }
2006 
2007  return xaccParseAmountInternal(in_str, monetary, negative_sign,
2008  decimal_point, group_separator,
2009  ignore, use_auto_decimal,
2010  result, endstr);
2011 }
2012 
2013 
2014 gboolean
2015 xaccParseAmount (const char* in_str, gboolean monetary, gnc_numeric *result,
2016  char** endstr)
2017 {
2018  return xaccParseAmountBasicInternal (in_str, monetary, auto_decimal_enabled,
2019  result, endstr, FALSE);
2020 }
2021 
2022 gboolean
2023 xaccParseAmountImport (const char* in_str, gboolean monetary,
2024  gnc_numeric *result,
2025  char** endstr, gboolean skip)
2026 {
2027  return xaccParseAmountBasicInternal (in_str, monetary, FALSE,
2028  result, endstr, skip);
2029 }
2030 
2031 
2032 gboolean
2033 xaccParseAmountExtended (const char* in_str, gboolean monetary,
2034  gunichar negative_sign, gunichar decimal_point,
2035  gunichar group_separator, const char* ignore_list,
2036  gnc_numeric *result, char** endstr)
2037 {
2038  return xaccParseAmountInternal (in_str, monetary, negative_sign,
2039  decimal_point, group_separator,
2040  ignore_list, auto_decimal_enabled,
2041  result, endstr);
2042 }
2043 
2044 gboolean
2045 xaccParseAmountExtImport (const char* in_str, gboolean monetary,
2046  gunichar negative_sign, gunichar decimal_point,
2047  gunichar group_separator, const char* ignore_list,
2048  gnc_numeric *result, char** endstr)
2049 {
2050  return xaccParseAmountInternal (in_str, monetary, negative_sign,
2051  decimal_point, group_separator,
2052  ignore_list, FALSE,
2053  result, endstr);
2054 }
2055 
2056 
2057 /* enable/disable the auto_decimal_enabled option */
2058 static void
2059 gnc_set_auto_decimal_enabled (gpointer settings, char* key, gpointer user_data)
2060 {
2061  auto_decimal_enabled =
2062  gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT);
2063 }
2064 
2065 /* set the number of auto decimal places to use */
2066 static void
2067 gnc_set_auto_decimal_places (gpointer settings, char* key, gpointer user_data)
2068 {
2069  auto_decimal_places =
2070  gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES);
2071 }
2072 
2073 static void
2074 gnc_auto_decimal_init (void)
2075 {
2076  auto_decimal_enabled =
2077  gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT);
2078  auto_decimal_places =
2079  gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES);
2080 }
2081 
2082 void
2083 gnc_ui_util_init (void)
2084 {
2085  gnc_configure_account_separator ();
2086  gnc_auto_decimal_init();
2087 
2088  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNT_SEPARATOR,
2089  (void*)gnc_configure_account_separator, nullptr);
2090  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_NONE,
2091  (void*)gnc_configure_reverse_balance, nullptr);
2092  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_CREDIT,
2093  (void*)gnc_configure_reverse_balance, nullptr);
2094  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_INC_EXP,
2095  (void*)gnc_configure_reverse_balance, nullptr);
2096  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_CHOICE_LOCALE,
2097  (void*)gnc_currency_changed_cb, nullptr);
2098  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_CHOICE_OTHER,
2099  (void*)gnc_currency_changed_cb, nullptr);
2100  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_OTHER,
2101  (void*)gnc_currency_changed_cb, nullptr);
2102  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_CHOICE_LOCALE,
2103  (void*)gnc_currency_changed_cb, nullptr);
2104  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_CHOICE_OTHER,
2105  (void*)gnc_currency_changed_cb, nullptr);
2106  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_OTHER,
2107  (void*)gnc_currency_changed_cb, nullptr);
2108  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT,
2109  (void*)gnc_set_auto_decimal_enabled, nullptr);
2110  gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES,
2111  (void*)gnc_set_auto_decimal_places, nullptr);
2112 
2113 }
2114 
2115 void
2116 gnc_ui_util_remove_registered_prefs (void)
2117 {
2118  // remove the registered pref call backs above
2119  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2120  GNC_PREF_ACCOUNT_SEPARATOR,
2121  (void*)gnc_configure_account_separator, nullptr);
2122  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2123  GNC_PREF_REVERSED_ACCTS_NONE,
2124  (void*)gnc_configure_reverse_balance, nullptr);
2125  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2126  GNC_PREF_REVERSED_ACCTS_CREDIT,
2127  (void*)gnc_configure_reverse_balance, nullptr);
2128  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2129  GNC_PREF_REVERSED_ACCTS_INC_EXP,
2130  (void*)gnc_configure_reverse_balance, nullptr);
2131  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2132  GNC_PREF_CURRENCY_CHOICE_LOCALE,
2133  (void*)gnc_currency_changed_cb, nullptr);
2134  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2135  GNC_PREF_CURRENCY_CHOICE_OTHER,
2136  (void*)gnc_currency_changed_cb, nullptr);
2137  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2138  GNC_PREF_CURRENCY_OTHER,
2139  (void*)gnc_currency_changed_cb, nullptr);
2140  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
2141  GNC_PREF_CURRENCY_CHOICE_LOCALE,
2142  (void*)gnc_currency_changed_cb, nullptr);
2143  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
2144  GNC_PREF_CURRENCY_CHOICE_OTHER,
2145  (void*)gnc_currency_changed_cb, nullptr);
2146  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
2147  GNC_PREF_CURRENCY_OTHER,
2148  (void*)gnc_currency_changed_cb, nullptr);
2149  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2150  GNC_PREF_AUTO_DECIMAL_POINT,
2151  (void*)gnc_set_auto_decimal_enabled, nullptr);
2152  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
2153  GNC_PREF_AUTO_DECIMAL_PLACES,
2154  (void*)gnc_set_auto_decimal_places, nullptr);
2155 }
2156 
2157 char*
2159 {
2160  if (!text)
2161  return nullptr;
2162 
2163  int32_t len = static_cast<int32_t> (strlen (text));
2164  std::string result;
2165  result.reserve (len);
2166 
2167  const char* p = text;
2168  int32_t i = 0;
2169 
2170  while (i < len)
2171  {
2172  UChar32 c;
2173  int32_t start = i;
2174  U8_NEXT (p, i, len, c);
2175 
2176  if (c >= 0 && !u_hasBinaryProperty (c, UCHAR_BIDI_CONTROL))
2177  result.append (p + start, i - start);
2178  }
2179 
2180  return g_strdup (result.c_str ());
2181 }
2182 
2183 static inline bool
2184 unichar_is_cntrl (gunichar uc)
2185 {
2186  return (uc < 0x20 || (uc > 0x7e && uc < 0xa0));
2187 }
2188 
2189 char*
2191 {
2192  bool cntrl = false;
2193  bool text_found = false;
2194 
2195  if (!text)
2196  return nullptr;
2197 
2198  if (!g_utf8_validate (text, -1, nullptr))
2199  return nullptr;
2200 
2201  auto filtered = g_string_sized_new (strlen (text) + 1);
2202 
2203  auto ch = text;
2204 
2205  while (*ch)
2206  {
2207  auto uc = g_utf8_get_char (ch);
2208 
2209  // check for starting with control characters
2210  if (unichar_is_cntrl (uc) && !text_found)
2211  {
2212  ch = g_utf8_next_char (ch);
2213  continue;
2214  }
2215  // check for alpha, num and punctuation
2216  if (!unichar_is_cntrl (uc))
2217  {
2218  filtered = g_string_append_unichar (filtered, uc);
2219  text_found = true;
2220  }
2221  // check for control characters after text
2222  if (unichar_is_cntrl (uc))
2223  cntrl = true;
2224 
2225  ch = g_utf8_next_char (ch);
2226 
2227  if (cntrl) // if control characters in text replace with space
2228  {
2229  auto uc2 = g_utf8_get_char (ch);
2230 
2231  if (!unichar_is_cntrl (uc2))
2232  filtered = g_string_append_unichar (filtered, ' ');
2233  }
2234  cntrl = false;
2235  }
2236  return g_string_free (filtered, FALSE);
2237 }
2238 
2239 void
2240 gnc_filter_text_set_cursor_position (const char* incoming_text,
2241  const char* symbol,
2242  gint *cursor_position)
2243 {
2244  int num = 0;
2245 
2246  if (*cursor_position == 0)
2247  return;
2248 
2249  if (!incoming_text || !symbol)
2250  return;
2251 
2252  if (g_strrstr (incoming_text, symbol) == nullptr)
2253  return;
2254 
2255  auto text_len = g_utf8_strlen (incoming_text, -1);
2256 
2257  for (int x = 0; x < text_len; x++)
2258  {
2259  auto temp = g_utf8_offset_to_pointer (incoming_text, x);
2260 
2261  if (g_str_has_prefix (temp, symbol))
2262  num++;
2263 
2264  if (g_strrstr (temp, symbol) == nullptr)
2265  break;
2266  }
2267  *cursor_position = *cursor_position - (num * g_utf8_strlen (symbol, -1));
2268 }
2269 
2270 char*
2271 gnc_filter_text_for_currency_symbol (const char* incoming_text,
2272  const char* symbol)
2273 {
2274  if (!incoming_text)
2275  return nullptr;
2276 
2277  if (!symbol)
2278  return g_strdup (incoming_text);
2279 
2280  if (g_strrstr (incoming_text, symbol) == nullptr)
2281  return g_strdup (incoming_text);
2282 
2283  auto split = g_strsplit (incoming_text, symbol, -1);
2284 
2285  auto ret_text = g_strjoinv (nullptr, split);
2286 
2287  g_strfreev (split);
2288  return ret_text;
2289 }
2290 
2291 char*
2292 gnc_filter_text_for_currency_commodity (const gnc_commodity *comm,
2293  const char* incoming_text,
2294  const char** symbol)
2295 {
2296  if (!incoming_text)
2297  {
2298  *symbol = nullptr;
2299  return nullptr;
2300  }
2301 
2302  if (!gnc_commodity_is_currency (comm))
2303  {
2304  *symbol = nullptr;
2305  return g_strdup (incoming_text);
2306  }
2307 
2308  if (comm)
2309  *symbol = gnc_commodity_get_nice_symbol (comm);
2310  else
2312 
2313  return gnc_filter_text_for_currency_symbol (incoming_text, *symbol);
2314 }
2315 
2316 gchar*
2317 gnc_list_formatter (GList *strings)
2318 {
2319  g_return_val_if_fail (strings, nullptr);
2320 
2321  UErrorCode status = U_ZERO_ERROR;
2322  auto formatter = icu::ListFormatter::createInstance(status);
2323  std::vector<UniStr> strvec;
2324  UniStr result;
2325  std::string retval;
2326 
2327  for (auto n = strings; n; n = g_list_next (n))
2328  {
2329  auto utf8_str{static_cast<const char*>(n->data)};
2330  strvec.push_back (UniStr::fromUTF8(utf8_str));
2331  }
2332 
2333  formatter->format (strvec.data(), strvec.size(), result, status);
2334 
2335  if (U_FAILURE(status))
2336  PERR ("Unicode error");
2337  else
2338  result.toUTF8String(retval);
2339 
2340  delete formatter;
2341  return g_strdup (retval.c_str());
2342 }
void xaccAccountSetType(Account *acc, GNCAccountType tip)
Set the account&#39;s type.
Definition: Account.cpp:2418
void xaccSplitSetValue(Split *split, gnc_numeric val)
The xaccSplitSetValue() method sets the value of this split in the transaction&#39;s commodity.
Definition: gmock-Split.cpp:92
Never round at all, and signal an error if there is a fractional result in a computation.
Definition: gnc-numeric.h:177
#define xaccTransAppendSplit(t, s)
Add a split to the transaction.
Definition: Transaction.h:381
gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book)
Returns the commodity table associated with a book.
Transaction * xaccMallocTransaction(QofBook *book)
The xaccMallocTransaction() will malloc memory and initialize it.
gboolean xaccParseAmount(const char *in_str, gboolean monetary, gnc_numeric *result, char **endstr)
Parses in_str to obtain a numeric result.
gboolean xaccParseAmountImport(const char *in_str, gboolean monetary, gnc_numeric *result, char **endstr, gboolean skip)
Similar to xaccParseAmount, but with two differences.
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
This function sets the posted date of the transaction, specified by a time64 (see ctime(3))...
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...
int gnc_commodity_get_fraction(const gnc_commodity *cm)
Retrieve the fraction for the specified commodity.
gnc_commodity * gnc_locale_default_currency_nodefault(void)
Returns the default currency of the current locale, or NULL if no sensible currency could be identifi...
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:2810
const char * gnc_print_amount_with_bidi_ltr_isolate(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
gchar * gnc_prefs_get_string(const gchar *group, const gchar *pref_name)
Get a string value from the preferences backend.
const char * gnc_commodity_get_mnemonic(const gnc_commodity *cm)
Retrieve the mnemonic for the specified commodity.
gulong gnc_prefs_register_cb(const char *group, const gchar *pref_name, gpointer func, gpointer user_data)
Register a callback that gets triggered when the given preference changes.
Definition: gnc-prefs.cpp:127
utility functions for the GnuCash UI
Expense accounts are used to denote expenses.
Definition: Account.h:143
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3241
int xaccAccountGetCommoditySCU(const Account *acc)
Return the SCU for the account.
Definition: Account.cpp:2719
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.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
void gnc_features_set_used(QofBook *book, const gchar *feature)
Indicate that the current book uses the given feature.
char * gnc_wrap_text_with_bidi_ltr_isolate(const char *text)
This function helps with GTK&#39;s use of &#39;Unicode Bidirectional Text Algorithm&#39;.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a+b.
gboolean gnc_numeric_to_decimal(gnc_numeric *a, guint8 *max_decimal_places)
Attempt to convert the denominator to an exact power of ten without rounding.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
stop here; the following types just aren&#39;t ready for prime time
Definition: Account.h:161
char * gnc_filter_text_for_currency_commodity(const gnc_commodity *comm, const char *incoming_text, const char **symbol)
Returns the incoming text removed of currency symbol.
Use any denominator which gives an exactly correct ratio of numerator to denominator.
Definition: gnc-numeric.h:188
gboolean gnc_prefs_set_string(const gchar *group, const gchar *pref_name, const gchar *value)
Store a string into the preferences backend.
Definition: gnc-prefs.cpp:319
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
gnc_commodity * gnc_default_report_currency(void)
Return the default currency for use in reports, as set by the user.
char * gnc_filter_text_for_bidi_marks(const char *text)
Returns the incoming text with Unicode bidi control characters removed.
gint gnc_prefs_get_int(const gchar *group, const gchar *pref_name)
Get an integer value from the preferences backend.
gboolean gnc_numeric_negative_p(gnc_numeric a)
Returns 1 if a < 0, otherwise returns 0.
#define VREC
split is void
Definition: Split.h:77
gnc_numeric gnc_numeric_reduce(gnc_numeric n)
Return input after reducing it by Greater Common Factor (GCF) elimination.
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction.
gnc_commodity * gnc_default_currency(void)
Return the default currency set by the user.
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:3067
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
gboolean xaccParseAmountExtImport(const char *in_str, gboolean monetary, gunichar negative_sign, gunichar decimal_point, gunichar group_separator, const char *ignore_list, gnc_numeric *result, char **endstr)
Similar to xaccParseAmountExtended, but will not automatically set a decimal point, regardless of what the user has set for this option.
char * gnc_get_account_name_for_register(const Account *account)
Get either the full name of the account or the simple name, depending on the configuration parameter ...
gdouble gnc_numeric_to_double(gnc_numeric n)
Convert numeric to floating-point value.
void xaccSplitSetAmount(Split *split, gnc_numeric amt)
The xaccSplitSetAmount() method sets the amount in the account&#39;s commodity that the split should have...
Definition: gmock-Split.cpp:77
QofBook * qof_session_get_book(const QofSession *session)
Returns the QofBook of this session.
Definition: qofsession.cpp:575
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:3279
Account handling public routines.
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;...
Income accounts are used to denote income.
Definition: Account.h:140
#define YREC
The Split has been reconciled.
Definition: Split.h:74
char * gnc_get_account_name_for_split_register(const Account *account, gboolean show_leaf_accounts)
Get either the full name of the account or the simple name, depending on the show_leaf_accounts.
const char * gnc_numeric_errorCode_to_string(GNCNumericErrorCode error_code)
Returns a string representation of the given GNCNumericErrorCode.
const char * gnc_get_doclink_flag_order(void)
Get a string containing document link flag order.
#define FREC
frozen into accounting period
Definition: Split.h:75
gboolean xaccParseAmountExtended(const char *in_str, gboolean monetary, gunichar negative_sign, gunichar decimal_point, gunichar group_separator, const char *ignore_list, gnc_numeric *result, char **endstr)
Converts a string to a gnc_numeric.
void gnc_filter_text_set_cursor_position(const char *incoming_text, const char *symbol, int *zcursor_position)
Updates cursor_position after removal of currency symbols.
gchar * gnc_list_formatter(GList *strings)
This function takes a GList of char*, and uses locale-sensitive list formatter.
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:3137
A/P account type.
Definition: Account.h:151
const char * gnc_commodity_get_nice_symbol(const gnc_commodity *cm)
Retrieve a symbol for the specified commodity, suitable for display to the user.
gnc_numeric gnc_numeric_abs(gnc_numeric a)
Returns a newly created gnc_numeric that is the absolute value of the given gnc_numeric value...
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
void xaccAccountSetIsOpeningBalance(Account *acc, gboolean val)
Set the "opening-balance" flag for an account.
Definition: Account.cpp:4112
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
gint gnc_account_n_children(const Account *account)
Return the number of children of the specified account.
Definition: Account.cpp:2950
All type declarations for the whole Gnucash engine.
#define CREC
The Split has been cleared.
Definition: Split.h:73
int xaccSPrintAmount(char *bufp, gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
gnc_commodity * gnc_account_get_currency_or_parent(const Account *account)
Returns a gnc_commodity that is a currency, suitable for being a Transaction&#39;s currency.
Definition: Account.cpp:3382
Split * xaccMallocSplit(QofBook *book)
Constructor.
Definition: gmock-Split.cpp:37
char * gnc_filter_text_for_control_chars(const char *text)
Returns the incoming text removed of control characters.
Account * gnc_account_lookup_for_register(const Account *base_account, const char *name)
Retrieve the account matching the given name starting from the descendants of base_account.
Generic api to store and retrieve preferences.
gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a-b.
gnc_commodity * gnc_account_or_default_currency(const Account *account, gboolean *currency_from_account_found)
Returns a gnc_commodity that is a currency, suitable for being a Transaction&#39;s currency.
char * gnc_filter_text_for_currency_symbol(const char *incoming_text, const char *symbol)
Returns the incoming text removed of a currency symbol.
Truncate fractions (round toward zero)
Definition: gnc-numeric.h:152
liability (and asset) accounts indicate generic, generalized accounts that are none of the above...
Definition: Account.h:119
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition: Account.cpp:1473
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
Definition: gmock-Split.cpp:53
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3375
#define xaccAccountInsertSplit(acc, s)
The xaccAccountInsertSplit() method will insert the indicated split into the indicated account...
Definition: Account.h:1052
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
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:1269
const char * gnc_get_doclink_str(char link_flag)
Get a string representing the document link type.
GNCNumericErrorCode gnc_numeric_check(gnc_numeric a)
Check for error signal in value.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
const char * gnc_get_doclink_valid_flags(void)
Get a string containing documentation link valid flags.
Account * gnc_account_get_root(Account *acc)
This routine returns the root account of the account tree that the specified account belongs to...
Definition: Account.cpp:2916
Account * gnc_account_lookup_by_opening_balance(Account *account, gnc_commodity *commodity)
Find the opening balance account for the currency.
Definition: Account.cpp:3095
const char * xaccAccountGetName(const Account *acc)
Get the account&#39;s name.
Definition: Account.cpp:3263
Equity account is used to balance the balance sheet.
Definition: Account.h:146
#define GNC_DENOM_AUTO
Values that can be passed as the &#39;denom&#39; argument.
Definition: gnc-numeric.h:245
API for Transactions and Splits (journal entries)
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition: Account.cpp:1514
gnc_commodity * gnc_locale_default_currency(void)
Returns the default currency of the current locale.
void xaccAccountSetName(Account *acc, const char *str)
Set the account&#39;s name.
Definition: Account.cpp:2439
gboolean gnc_commodity_equiv(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equivalent.
gboolean gnc_commodity_is_iso(const gnc_commodity *cm)
Checks to see if the specified commodity is an ISO 4217 recognized currency.
The Credit card account is used to denote credit (e.g.
Definition: Account.h:113
void xaccAccountSetCommodity(Account *acc, gnc_commodity *com)
Set the account&#39;s commodity.
Definition: Account.cpp:2652
#define NREC
not reconciled or cleared
Definition: Split.h:76
void gnc_prefs_remove_cb_by_func(const gchar *group, const gchar *pref_name, gpointer func, gpointer user_data)
Remove a function that was registered for a callback when the given preference changed.
Definition: gnc-prefs.cpp:142
Utility functions for file access.