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