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