LCOV - code coverage report
Current view: top level - libgnucash/app-utils - gnc-ui-util.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 38.4 % 1038 399
Test Date: 2026-11-02 18:19:43 Functions: 36.0 % 75 27
Legend: Lines:     hit not hit

            Line data    Source code
       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            0 : gnc_normalize_account_separator (const gchar* separator)
      92              : {
      93            0 :     char* new_sep=nullptr;
      94              : 
      95            0 :     if (!separator || !*separator || g_strcmp0(separator, "colon") == 0)
      96            0 :             new_sep = g_strdup (":");
      97            0 :         else if (g_strcmp0(separator, "slash") == 0)
      98            0 :             new_sep = g_strdup ("/");
      99            0 :         else if (g_strcmp0(separator, "backslash") == 0)
     100            0 :             new_sep = g_strdup ("\\");
     101            0 :         else if (g_strcmp0(separator, "dash") == 0)
     102            0 :             new_sep = g_strdup ("-");
     103            0 :         else if (g_strcmp0(separator, "period") == 0)
     104            0 :             new_sep = g_strdup (".");
     105              :         else
     106            0 :             new_sep = g_strdup (separator);
     107              : 
     108            0 :     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            0 : gnc_configure_account_separator (void)
     118              : {
     119            0 :     auto string = gnc_prefs_get_string(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNT_SEPARATOR);
     120            0 :     auto separator = gnc_normalize_account_separator (string);
     121              : 
     122            0 :     gnc_set_account_separator(separator);
     123              : 
     124            0 :     g_free(string);
     125            0 :     g_free(separator);
     126            0 : }
     127              : 
     128              : 
     129              : static void
     130            7 : gnc_configure_reverse_balance (void)
     131              : {
     132          112 :     for (auto i = 0; i < NUM_ACCOUNT_TYPES; i++)
     133          105 :         reverse_type[i] = false;
     134              : 
     135            7 :     if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_INC_EXP))
     136              :     {
     137            0 :         reverse_type[ACCT_TYPE_INCOME]  = true;
     138            0 :         reverse_type[ACCT_TYPE_EXPENSE] = true;
     139              :     }
     140            7 :     else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_CREDIT))
     141              :     {
     142            0 :         reverse_type[ACCT_TYPE_LIABILITY] = true;
     143            0 :         reverse_type[ACCT_TYPE_PAYABLE]   = true;
     144            0 :         reverse_type[ACCT_TYPE_EQUITY]    = true;
     145            0 :         reverse_type[ACCT_TYPE_INCOME]    = true;
     146            0 :         reverse_type[ACCT_TYPE_CREDIT]    = true;
     147              :     }
     148            7 :     else if (!gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_NONE))
     149            7 :         PWARN("no reversed account preference set, using none");
     150              : 
     151            7 : }
     152              : 
     153              : static void
     154            7 : gnc_reverse_balance_init (void)
     155              : {
     156            7 :     gnc_configure_reverse_balance ();
     157            7 :     reverse_balance_inited = TRUE;
     158            7 : }
     159              : 
     160              : gboolean
     161         2204 : gnc_reverse_balance (const Account *account)
     162              : {
     163         2204 :     if (account == nullptr)
     164            0 :         return FALSE;
     165              : 
     166         2204 :     auto type = xaccAccountGetType (account);
     167         2204 :     if ((type < 0) || (type >= NUM_ACCOUNT_TYPES))
     168            0 :         return FALSE;
     169              : 
     170         2204 :     if (!reverse_balance_inited)
     171            7 :         gnc_reverse_balance_init ();
     172              : 
     173         2204 :     return reverse_type[type];
     174              : }
     175              : 
     176            0 : gboolean gnc_using_equity_type_opening_balance_account (QofBook* book)
     177              : {
     178            0 :     return gnc_features_check_used (book, GNC_FEATURE_EQUITY_TYPE_OPENING_BALANCE);
     179              : }
     180              : 
     181            0 : void gnc_set_use_equity_type_opening_balance_account (QofBook* book)
     182              : {
     183            0 :     gnc_features_set_used (book, GNC_FEATURE_EQUITY_TYPE_OPENING_BALANCE);
     184            0 : }
     185              : 
     186              : char*
     187            0 : gnc_get_default_directory (const char* section)
     188              : {
     189            0 :     auto dir = gnc_prefs_get_string (section, GNC_PREF_LAST_PATH);
     190            0 :     if (!(dir && *dir))
     191              :     {
     192            0 :         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            0 :         dir = g_strdup (g_get_home_dir ());
     197              : #endif
     198              :     }
     199            0 :     return dir;
     200              : }
     201              : 
     202              : void
     203            0 : gnc_set_default_directory (const char* section, const char* directory)
     204              : {
     205            0 :     gnc_prefs_set_string(section, GNC_PREF_LAST_PATH, directory);
     206            0 : }
     207              : 
     208              : QofBook *
     209        10670 : gnc_get_current_book (void)
     210              : {
     211        10670 :     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            0 : gnc_is_new_book (void)
     224              : {
     225            0 :     return (!gnc_current_session_exist() ||
     226            0 :             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            0 : gnc_set_current_book_tax_name_type (gboolean name_changed, const char* tax_name,
     236              :                                     gboolean type_changed, const char* tax_type)
     237              : {
     238            0 :     if (name_changed)
     239              :     {
     240            0 :         if (type_changed)
     241              :         {
     242            0 :             auto book = gnc_get_current_book();
     243            0 :             if ((g_strcmp0 (tax_name, "") == 0) ||
     244              :                 (tax_name == nullptr))
     245              :             { /* change to no name */
     246            0 :                 if ((g_strcmp0 (tax_type, "Other") == 0) ||
     247            0 :                     (g_strcmp0 (tax_type, "") == 0) ||
     248              :                     (tax_type == nullptr))
     249              :                 { /* need to delete both name and type and the "tax_US" frame */
     250            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
     251            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
     252            0 :                     qof_book_option_frame_delete(book, "tax_US");
     253              :                 }
     254              :                 else
     255              :                 { /* delete the name & change the type; keep the "tax_US" frame */
     256            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
     257            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
     258              :                 }
     259              :             }
     260              :             else /* new name */
     261              :             {
     262            0 :                 if ((g_strcmp0 (tax_type, "Other") == 0) ||
     263            0 :                     (g_strcmp0 (tax_type, "") == 0) ||
     264              :                     (tax_type == nullptr))
     265              :                 { /* delete the type & change the name; keep the "tax_US" frame */
     266            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
     267            0 :                     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            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
     272            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
     273              :                 }
     274              :             }
     275              :         }
     276              :         else /* no type change but name changed */
     277              :         {
     278            0 :             auto book = gnc_get_current_book();
     279            0 :             if ((g_strcmp0 (tax_name, "") == 0) ||
     280              :                 (tax_name == nullptr))
     281              :             { /* change to no name */
     282            0 :                 if ((g_strcmp0 (tax_type, "Other") == 0) ||
     283            0 :                     (g_strcmp0 (tax_type, "") == 0) ||
     284              :                     (tax_type == nullptr))
     285              :                 { /* delete the name; there is no type; deleted the "tax_US" frame */
     286            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
     287            0 :                     qof_book_option_frame_delete(book, "tax_US");
     288              :                 }
     289              :                 else
     290              :                 { /* need to delete the name and keep "tax_US" frame */
     291            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_NAME, nullptr);
     292              :                 }
     293              :             }
     294              :             else
     295              :             { /* change the name & keep "tax_US" frame */
     296            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_NAME, tax_name);
     297              :             }
     298              :         }
     299              :    }
     300              :    else /* no name change */
     301              :    {
     302            0 :         if (type_changed)
     303              :         {
     304            0 :             auto book = gnc_get_current_book();
     305            0 :             if ((g_strcmp0 (tax_type, "Other") == 0) ||
     306            0 :                 (g_strcmp0 (tax_type, "") == 0) ||
     307              :                 (tax_type == nullptr))
     308              :             {
     309            0 :                 if ((g_strcmp0 (tax_name, "") == 0) ||
     310              :                     (tax_name == nullptr))
     311              :                 {/* delete the type; there is no name; delete the "tax_US" frame */
     312            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
     313            0 :                     qof_book_option_frame_delete(book, "tax_US");
     314              :                 }
     315              :                 else
     316              :                 { /* need to delete the type and keep "tax_US" frame */
     317            0 :                     qof_book_set_string_option(book, OPTION_TAXUS_TYPE, nullptr);
     318              :                 }
     319              :             }
     320              :             else
     321              :             { /* change the type & keep "tax_US" frame */
     322            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_TYPE, tax_type);
     323              :             }
     324              :         } /*else no name and no type change - do nothing */
     325              :    }
     326            0 : }
     327              : 
     328              : const char*
     329            0 : gnc_get_current_book_tax_name (void)
     330              : {
     331            0 :     auto book = gnc_get_current_book();
     332            0 :     auto tax_name = qof_book_get_string_option(book, OPTION_TAXUS_NAME);
     333            0 :     if (tax_name)
     334              :     {
     335            0 :         return tax_name;
     336              :     }
     337              :     else
     338              :     {
     339              :         const char* old_option_taxus_name =
     340            0 :             qof_book_get_string_option(book, OLD_OPTION_TAXUS_NAME);
     341            0 :         if (old_option_taxus_name)
     342              :         {
     343            0 :             char* taxus_name = g_strdup(old_option_taxus_name);
     344              :             const char* old_option_taxus_type =
     345            0 :                 qof_book_get_string_option(book, OLD_OPTION_TAXUS_TYPE);
     346            0 :             if (old_option_taxus_type)
     347              :             { /* switch both name and type and remove unused frames */
     348            0 :                 char* taxus_type = g_strdup(old_option_taxus_type);
     349            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
     350            0 :                 qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
     351            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
     352            0 :                 qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
     353            0 :                 qof_book_option_frame_delete(book, "book/tax_US");
     354            0 :                 qof_book_option_frame_delete(book, "book");
     355            0 :                 g_free (taxus_type);
     356              :             }
     357              :             else
     358              :             { /* switch just name and remove unused frames */
     359            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
     360            0 :                 qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
     361            0 :                 qof_book_option_frame_delete(book, "book/tax_US");
     362            0 :                 qof_book_option_frame_delete(book, "book");
     363              :             }
     364            0 :             g_free (taxus_name);
     365            0 :             return qof_book_get_string_option(book, OPTION_TAXUS_NAME);
     366              :         }
     367            0 :         return nullptr;
     368              :     }
     369              : }
     370              : 
     371              : const char*
     372            0 : gnc_get_current_book_tax_type (void)
     373              : {
     374            0 :     auto book = gnc_get_current_book();
     375              :     auto tax_type =
     376            0 :         qof_book_get_string_option(book, OPTION_TAXUS_TYPE);
     377            0 :     if (tax_type)
     378              :     {
     379            0 :         return tax_type;
     380              :     }
     381              :     else
     382              :     {
     383              :         auto old_option_taxus_type =
     384            0 :             qof_book_get_string_option(book, OLD_OPTION_TAXUS_TYPE);
     385            0 :         if (old_option_taxus_type)
     386              :         {
     387            0 :             auto taxus_type = g_strdup(old_option_taxus_type);
     388              :             auto old_option_taxus_name =
     389            0 :                 qof_book_get_string_option(book, OLD_OPTION_TAXUS_NAME);
     390            0 :             if (old_option_taxus_name)
     391              :             { /* switch both name and type and remove unused frames */
     392            0 :                 auto taxus_name = g_strdup(old_option_taxus_name);
     393            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_NAME, taxus_name);
     394            0 :                 qof_book_set_string_option(book, OLD_OPTION_TAXUS_NAME, nullptr);
     395            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
     396            0 :                 qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
     397            0 :                 qof_book_option_frame_delete(book, "book/tax_US");
     398            0 :                 qof_book_option_frame_delete(book, "book");
     399            0 :                 g_free (taxus_name);
     400              :             }
     401              :             else
     402              :             { /* switch just type and remove unused frames */
     403            0 :                 qof_book_set_string_option(book, OPTION_TAXUS_TYPE, taxus_type);
     404            0 :                 qof_book_set_string_option(book, OLD_OPTION_TAXUS_TYPE, nullptr);
     405            0 :                 qof_book_option_frame_delete(book, "book/tax_US");
     406            0 :                 qof_book_option_frame_delete(book, "book");
     407              :             }
     408            0 :             g_free (taxus_type);
     409            0 :             return qof_book_get_string_option(book, OPTION_TAXUS_TYPE);
     410              :         }
     411            0 :         return nullptr;
     412              :     }
     413              : }
     414              : 
     415              : Account *
     416         1292 : gnc_get_current_root_account (void)
     417              : {
     418         1292 :     return gnc_book_get_root_account (gnc_get_current_book ());
     419              : }
     420              : 
     421              : gnc_commodity_table *
     422         1544 : gnc_get_current_commodities (void)
     423              : {
     424         1544 :      if (gnc_current_session_exist())
     425         1504 :           return gnc_commodity_table_get_table (gnc_get_current_book ());
     426           40 :      return nullptr;
     427              : }
     428              : 
     429              : char*
     430            0 : gnc_get_account_name_for_split_register(const Account *account,
     431              :                                         gboolean show_leaf_accounts)
     432              : {
     433            0 :     if (show_leaf_accounts)
     434            0 :         return g_strdup (xaccAccountGetName (account));
     435              :     else
     436            0 :         return gnc_account_get_full_name (account);
     437              : }
     438              : 
     439              : char*
     440            0 : gnc_get_account_name_for_register(const Account *account)
     441              : {
     442              :     auto show_leaf_accounts =
     443            0 :         gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL_REGISTER,
     444              :                            GNC_PREF_SHOW_LEAF_ACCT_NAMES);
     445              : 
     446            0 :     return gnc_get_account_name_for_split_register(account, show_leaf_accounts);
     447              : }
     448              : 
     449              : Account *
     450            0 : gnc_account_lookup_for_register(const Account *base_account, const char* name)
     451              : {
     452              :     auto show_leaf_accounts =
     453            0 :         gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL_REGISTER,
     454              :                            GNC_PREF_SHOW_LEAF_ACCT_NAMES);
     455              : 
     456            0 :     if (show_leaf_accounts)
     457            0 :         return gnc_account_lookup_by_name (base_account, name);
     458              :     else
     459            0 :         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            0 : gnc_get_reconcile_str (char reconciled_flag)
     471              : {
     472            0 :     switch (reconciled_flag)
     473              :     {
     474            0 :     case NREC:
     475            0 :         return C_("Reconciled flag 'not cleared'", "n");
     476            0 :     case CREC:
     477            0 :         return C_("Reconciled flag 'cleared'", "c");
     478            0 :     case YREC:
     479            0 :         return C_("Reconciled flag 'reconciled'", "y");
     480            0 :     case FREC:
     481            0 :         return C_("Reconciled flag 'frozen'", "f");
     482            0 :     case VREC:
     483            0 :         return C_("Reconciled flag 'void'", "v");
     484            0 :     default:
     485            0 :         PERR("Bad reconciled flag\n");
     486            0 :         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            0 : gnc_get_reconcile_valid_flags (void)
     498              : {
     499              :     static const char flags[] = { NREC, CREC, YREC, FREC, VREC, 0 };
     500            0 :     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            0 : gnc_get_reconcile_flag_order (void)
     512              : {
     513              :     static const char flags[] = { NREC, CREC, 0 };
     514            0 :     return flags;
     515              : }
     516              : 
     517              : const char*
     518            0 : gnc_get_doclink_str (char link_flag)
     519              : {
     520            0 :     switch (link_flag)
     521              :     {
     522            0 :     case WLINK:
     523            0 :         return C_("Document Link flag for 'web'", "w");
     524            0 :     case FLINK:
     525            0 :         return C_("Document Link flag for 'file'", "f");
     526            0 :     case ' ':
     527            0 :         return " ";
     528            0 :     default:
     529            0 :         PERR("Bad link flag");
     530            0 :         return nullptr;
     531              :     }
     532              : }
     533              : 
     534              : const char*
     535            0 : gnc_get_doclink_valid_flags (void)
     536              : {
     537              :     static const char flags[] = { FLINK, WLINK, ' ', 0 };
     538            0 :     return flags;
     539              : }
     540              : 
     541              : const char*
     542            0 : gnc_get_doclink_flag_order (void)
     543              : {
     544              :     static const char flags[] = { FLINK, WLINK, ' ', 0 };
     545            0 :     return flags;
     546              : }
     547              : 
     548              : static const char*
     549            0 : equity_base_name (GNCEquityType equity_type)
     550              : {
     551            0 :     switch (equity_type)
     552              :     {
     553            0 :     case EQUITY_OPENING_BALANCE:
     554            0 :         return N_("Opening Balances");
     555              : 
     556            0 :     case EQUITY_RETAINED_EARNINGS:
     557            0 :         return N_("Retained Earnings");
     558              : 
     559            0 :     default:
     560            0 :         return nullptr;
     561              :     }
     562              : }
     563              : 
     564              : Account *
     565            0 : gnc_find_or_create_equity_account (Account *root,
     566              :                                    GNCEquityType equity_type,
     567              :                                    gnc_commodity *currency)
     568              : {
     569            0 :     g_return_val_if_fail (equity_type >= 0, nullptr);
     570            0 :     g_return_val_if_fail (equity_type < NUM_EQUITY_TYPES, nullptr);
     571            0 :     g_return_val_if_fail (currency != nullptr, nullptr);
     572            0 :     g_return_val_if_fail (root != nullptr, nullptr);
     573            0 :     g_return_val_if_fail (gnc_commodity_is_currency(currency), nullptr);
     574              : 
     575            0 :     auto use_eq_op_feature = equity_type == EQUITY_OPENING_BALANCE && gnc_using_equity_type_opening_balance_account (gnc_get_current_book ());
     576              : 
     577            0 :     if (use_eq_op_feature)
     578              :     {
     579            0 :         auto account = gnc_account_lookup_by_opening_balance (root, currency);
     580            0 :         if (account)
     581            0 :             return account;
     582              :     }
     583              : 
     584            0 :     auto base_name = equity_base_name (equity_type);
     585              : 
     586            0 :     auto account = gnc_account_lookup_by_name(root, base_name);
     587            0 :     if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
     588            0 :         account = nullptr;
     589              : 
     590            0 :     if (!account)
     591              :     {
     592            0 :         base_name = base_name && *base_name ? _(base_name) : "";
     593              : 
     594            0 :         account = gnc_account_lookup_by_name(root, base_name);
     595            0 :         if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
     596            0 :             account = nullptr;
     597              :     }
     598              : 
     599            0 :     auto base_name_exists = (account != nullptr);
     600              : 
     601            0 :     if (account &&
     602            0 :             gnc_commodity_equiv (currency, xaccAccountGetCommodity (account)))
     603              :     {
     604            0 :         if (use_eq_op_feature)
     605            0 :             xaccAccountSetIsOpeningBalance (account, TRUE);
     606            0 :         return account;
     607              :     }
     608              : 
     609            0 :     auto name = g_strconcat (base_name, " - ",
     610              :                         gnc_commodity_get_mnemonic (currency), nullptr);
     611            0 :     account = gnc_account_lookup_by_name(root, name);
     612            0 :     if (account && xaccAccountGetType (account) != ACCT_TYPE_EQUITY)
     613            0 :         account = nullptr;
     614              : 
     615            0 :     auto name_exists = (account != nullptr);
     616              : 
     617            0 :     if (account &&
     618            0 :             gnc_commodity_equiv (currency, xaccAccountGetCommodity (account)))
     619              :     {
     620            0 :         if (use_eq_op_feature)
     621            0 :             xaccAccountSetIsOpeningBalance (account, TRUE);
     622            0 :         return account;
     623              :     }
     624              : 
     625              :     /* Couldn't find one, so create it */
     626            0 :     if (name_exists && base_name_exists)
     627              :     {
     628            0 :         PWARN ("equity account with unexpected currency");
     629            0 :         g_free (name);
     630            0 :         return nullptr;
     631              :     }
     632              : 
     633            0 :     if (!base_name_exists &&
     634            0 :             gnc_commodity_equiv (currency, gnc_default_currency ()))
     635              :     {
     636            0 :         g_free (name);
     637            0 :         name = g_strdup (base_name);
     638              :     }
     639              : 
     640            0 :     auto parent = gnc_account_lookup_by_name(root, _("Equity"));
     641            0 :     if (!parent || xaccAccountGetType (parent) != ACCT_TYPE_EQUITY)
     642            0 :         parent = root;
     643            0 :     g_assert(parent);
     644              : 
     645            0 :     account = xaccMallocAccount (gnc_account_get_book(root));
     646              : 
     647            0 :     xaccAccountBeginEdit (account);
     648              : 
     649            0 :     xaccAccountSetName (account, name);
     650            0 :     xaccAccountSetType (account, ACCT_TYPE_EQUITY);
     651            0 :     xaccAccountSetCommodity (account, currency);
     652              : 
     653            0 :     if (use_eq_op_feature)
     654            0 :         xaccAccountSetIsOpeningBalance (account, TRUE);
     655              : 
     656            0 :     xaccAccountBeginEdit (parent);
     657            0 :     gnc_account_append_child (parent, account);
     658            0 :     xaccAccountCommitEdit (parent);
     659              : 
     660            0 :     xaccAccountCommitEdit (account);
     661              : 
     662            0 :     g_free (name);
     663              : 
     664            0 :     return account;
     665              : }
     666              : 
     667              : gboolean
     668            0 : gnc_account_create_opening_balance (Account *account,
     669              :                                     gnc_numeric balance,
     670              :                                     time64 date,
     671              :                                     QofBook *book)
     672              : {
     673            0 :     if (gnc_numeric_zero_p (balance))
     674            0 :         return TRUE;
     675              : 
     676            0 :     g_return_val_if_fail (account != nullptr, FALSE);
     677            0 :     auto commodity = xaccAccountGetCommodity (account);
     678            0 :     g_return_val_if_fail (gnc_commodity_is_currency (commodity), FALSE);
     679              : 
     680              :     auto equity_account =
     681            0 :         gnc_find_or_create_equity_account (gnc_account_get_root(account),
     682              :                                            EQUITY_OPENING_BALANCE,
     683              :                                            commodity);
     684            0 :     if (!equity_account)
     685            0 :         return FALSE;
     686              : 
     687            0 :     xaccAccountBeginEdit (account);
     688            0 :     xaccAccountBeginEdit (equity_account);
     689              : 
     690            0 :     auto trans = xaccMallocTransaction (book);
     691              : 
     692            0 :     xaccTransBeginEdit (trans);
     693              : 
     694            0 :     xaccTransSetCurrency (trans, gnc_account_or_default_currency (account, nullptr));
     695            0 :     xaccTransSetDatePostedSecsNormalized (trans, date);
     696            0 :     xaccTransSetDescription (trans, _("Opening Balance"));
     697              : 
     698            0 :     auto split = xaccMallocSplit (book);
     699              : 
     700            0 :     xaccTransAppendSplit (trans, split);
     701            0 :     xaccAccountInsertSplit (account, split);
     702              : 
     703            0 :     xaccSplitSetAmount (split, balance);
     704            0 :     xaccSplitSetValue (split, balance);
     705              : 
     706            0 :     balance = gnc_numeric_neg (balance);
     707              : 
     708            0 :     split = xaccMallocSplit (book);
     709              : 
     710            0 :     xaccTransAppendSplit (trans, split);
     711            0 :     xaccAccountInsertSplit (equity_account, split);
     712              : 
     713            0 :     xaccSplitSetAmount (split, balance);
     714            0 :     xaccSplitSetValue (split, balance);
     715              : 
     716            0 :     xaccTransCommitEdit (trans);
     717            0 :     xaccAccountCommitEdit (equity_account);
     718            0 :     xaccAccountCommitEdit (account);
     719              : 
     720            0 :     return TRUE;
     721              : }
     722              : 
     723              : 
     724              : gnc_commodity *
     725         1524 : gnc_locale_default_currency_nodefault (void)
     726              : {
     727         1524 :     auto table = gnc_get_current_commodities ();
     728         1524 :     auto code = gnc_locale_default_iso_currency_code ();
     729              : 
     730         1524 :     auto currency = gnc_commodity_table_lookup (table,
     731              :                                                 GNC_COMMODITY_NS_CURRENCY,
     732              :                                                 code);
     733              : 
     734         1524 :     return (currency ? currency : nullptr);
     735              : }
     736              : 
     737              : gnc_commodity*
     738         1524 : gnc_locale_default_currency (void)
     739              : {
     740         1524 :     auto currency = gnc_locale_default_currency_nodefault ();
     741              : 
     742         1524 :     return (currency ? currency :
     743           20 :             gnc_commodity_table_lookup (gnc_get_current_commodities (),
     744         1504 :                                         GNC_COMMODITY_NS_CURRENCY, "USD"));
     745              : }
     746              : 
     747              : 
     748              : static gnc_commodity*
     749         1524 : gnc_default_currency_common (char* requested_currency,
     750              :                              const char* section)
     751              : {
     752         1524 :     gnc_commodity *currency = nullptr;
     753              : 
     754         1524 :     if (requested_currency)
     755            0 :         return gnc_commodity_table_lookup(gnc_get_current_commodities(),
     756              :                                           GNC_COMMODITY_NS_CURRENCY,
     757            0 :                                           requested_currency);
     758              : 
     759         3028 :     if (gnc_current_session_exist() &&
     760         1504 :         gnc_prefs_get_bool (section, GNC_PREF_CURRENCY_CHOICE_OTHER))
     761              :     {
     762            0 :         auto mnemonic = gnc_prefs_get_string(section, GNC_PREF_CURRENCY_OTHER);
     763            0 :         currency = gnc_commodity_table_lookup(gnc_get_current_commodities(),
     764              :                                               GNC_COMMODITY_NS_CURRENCY,
     765              :                                               mnemonic);
     766            0 :         DEBUG("mnemonic %s, result %p",
     767              :               mnemonic && *mnemonic ? mnemonic : "(null)", currency);
     768            0 :         g_free(mnemonic);
     769              :     }
     770              : 
     771         1524 :     if (!currency)
     772         1524 :         currency = gnc_locale_default_currency ();
     773              : 
     774         1524 :     if (currency)
     775              :     {
     776         1504 :         g_free (requested_currency);
     777              :     }
     778              : 
     779         1524 :     return currency;
     780              : }
     781              : 
     782              : gnc_commodity*
     783          532 : gnc_default_currency (void)
     784              : {
     785          532 :     return gnc_default_currency_common (user_default_currency,
     786          532 :                                         GNC_PREFS_GROUP_GENERAL);
     787              : }
     788              : 
     789              : gnc_commodity*
     790            0 : gnc_account_or_default_currency(const Account* account,
     791              :                                 gboolean * currency_from_account_found)
     792              : {
     793              :     gnc_commodity *currency;
     794            0 :     if (!account)
     795              :     {
     796            0 :         if (currency_from_account_found)
     797            0 :             *currency_from_account_found = FALSE;
     798            0 :         return gnc_default_currency();
     799              :     }
     800              : 
     801            0 :     currency = gnc_account_get_currency_or_parent(account);
     802            0 :     if (currency)
     803              :     {
     804            0 :         if (currency_from_account_found)
     805            0 :             *currency_from_account_found = TRUE;
     806              :     }
     807              :     else
     808              :     {
     809            0 :         if (currency_from_account_found)
     810            0 :             *currency_from_account_found = FALSE;
     811            0 :         currency = gnc_default_currency();
     812              :     }
     813            0 :     return currency;
     814              : }
     815              : 
     816              : 
     817              : 
     818              : gnc_commodity*
     819          992 : gnc_default_report_currency (void)
     820              : {
     821          992 :     return gnc_default_currency_common (user_report_currency,
     822          992 :                                         GNC_PREFS_GROUP_GENERAL_REPORT);
     823              : }
     824              : 
     825              : static void
     826            0 : gnc_currency_changed_cb (GSettings *settings, char* key, gpointer user_data)
     827              : {
     828            0 :     user_default_currency = nullptr;
     829            0 :     user_report_currency = nullptr;
     830            0 :     gnc_hook_run(HOOK_CURRENCY_CHANGED, nullptr);
     831            0 : }
     832              : 
     833              : 
     834              : GNCPrintAmountInfo
     835          526 : 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          526 :     info.use_symbol = use_symbol ? 1 : 0;
     842          526 :     info.commodity = gnc_default_currency ();
     843              : 
     844          526 :     if (got_it)
     845          522 :         return info;
     846              : 
     847            4 :     auto lc = gnc_localeconv ();
     848              : 
     849            4 :     info.max_decimal_places = lc->frac_digits;
     850            4 :     info.min_decimal_places = lc->frac_digits;
     851              : 
     852            4 :     info.use_separators = 1;
     853            4 :     info.use_locale = 1;
     854            4 :     info.monetary = 1;
     855            4 :     info.force_fit = 0;
     856            4 :     info.round = 0;
     857              : 
     858            4 :     got_it = TRUE;
     859              : 
     860            4 :     return info;
     861              : }
     862              : 
     863              : static bool
     864         8245 : is_decimal_fraction (int64_t fraction, uint8_t *max_decimal_places_p)
     865              : {
     866         8245 :     uint8_t max_decimal_places = 0;
     867              : 
     868         8245 :     if (fraction <= 0)
     869            0 :         return false;
     870              : 
     871        24840 :     while (fraction != 1)
     872              :     {
     873        16595 :         if (fraction % 10 != 0)
     874            0 :             return false;
     875              : 
     876        16595 :         fraction = fraction / 10;
     877        16595 :         max_decimal_places += 1;
     878              :     }
     879              : 
     880         8245 :     if (max_decimal_places_p)
     881         8245 :         *max_decimal_places_p = max_decimal_places;
     882              : 
     883         8245 :     return true;
     884              : }
     885              : 
     886              : GNCPrintAmountInfo
     887         8569 : gnc_commodity_print_info (const gnc_commodity *commodity,
     888              :                           gboolean use_symbol)
     889              : {
     890              :     GNCPrintAmountInfo info;
     891              : 
     892         8569 :     if (commodity == nullptr)
     893          324 :         return gnc_default_print_info (use_symbol);
     894              : 
     895         8245 :     info.commodity = commodity;
     896              : 
     897         8245 :     auto is_iso = gnc_commodity_is_iso (commodity);
     898              : 
     899         8245 :     if (is_decimal_fraction (gnc_commodity_get_fraction (commodity),
     900              :                              &info.max_decimal_places))
     901              :     {
     902         8245 :         if (is_iso)
     903         7919 :             info.min_decimal_places = info.max_decimal_places;
     904              :         else
     905          326 :             info.min_decimal_places = 0;
     906              :     }
     907              :     else
     908            0 :         info.max_decimal_places = info.min_decimal_places = 0;
     909              : 
     910         8245 :     info.use_separators = 1;
     911         8245 :     info.use_symbol = use_symbol ? 1 : 0;
     912         8245 :     info.use_locale = is_iso ? 1 : 0;
     913         8245 :     info.monetary = 1;
     914         8245 :     info.force_fit = 0;
     915         8245 :     info.round = 0;
     916              : 
     917         8245 :     return info;
     918              : }
     919              : 
     920              : static GNCPrintAmountInfo
     921            0 : 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            0 :     if (account == nullptr)
     928            0 :         return gnc_default_print_info (use_symbol);
     929              : 
     930            0 :     info.commodity = efffunc (account);
     931              : 
     932            0 :     auto is_iso = gnc_commodity_is_iso (info.commodity);
     933              : 
     934            0 :     auto scu = scufunc (account);
     935              : 
     936            0 :     if (is_decimal_fraction (scu, &info.max_decimal_places))
     937              :     {
     938            0 :         if (is_iso)
     939            0 :             info.min_decimal_places = info.max_decimal_places;
     940              :         else
     941            0 :             info.min_decimal_places = 0;
     942              :     }
     943              :     else
     944            0 :         info.max_decimal_places = info.min_decimal_places = 0;
     945              : 
     946            0 :     info.use_separators = 1;
     947            0 :     info.use_symbol = use_symbol ? 1 : 0;
     948            0 :     info.use_locale = is_iso ? 1 : 0;
     949            0 :     info.monetary = 1;
     950            0 :     info.force_fit = 0;
     951            0 :     info.round = 0;
     952              : 
     953            0 :     return info;
     954              : }
     955              : 
     956              : GNCPrintAmountInfo
     957            0 : gnc_account_print_info (const Account *account, gboolean use_symbol)
     958              : {
     959            0 :     return gnc_account_print_info_helper(account, use_symbol,
     960              :                                          xaccAccountGetCommodity,
     961            0 :                                          xaccAccountGetCommoditySCU);
     962              : }
     963              : 
     964              : GNCPrintAmountInfo
     965            0 : gnc_split_amount_print_info (Split *split, gboolean use_symbol)
     966              : {
     967            0 :     if (!split)
     968              :     {
     969            0 :         GNCPrintAmountInfo info = gnc_default_share_print_info ();
     970            0 :         info.use_symbol = use_symbol;
     971            0 :         return info;
     972              :     }
     973              : 
     974            0 :     return gnc_account_print_info (xaccSplitGetAccount (split), use_symbol);
     975              : }
     976              : 
     977              : static GNCPrintAmountInfo
     978            1 : gnc_default_print_info_helper (int decplaces)
     979              : {
     980              :     GNCPrintAmountInfo info;
     981              : 
     982            1 :     info.commodity = nullptr;
     983              : 
     984            1 :     info.max_decimal_places = decplaces;
     985            1 :     info.min_decimal_places = 0;
     986              : 
     987            1 :     info.use_separators = 1;
     988            1 :     info.use_symbol = 0;
     989            1 :     info.use_locale = 1;
     990            1 :     info.monetary = 1;
     991            1 :     info.force_fit = 0;
     992            1 :     info.round = 0;
     993              : 
     994            1 :     return info;
     995              : }
     996              : 
     997              : GNCPrintAmountInfo
     998            7 : gnc_default_share_print_info (void)
     999              : {
    1000              :     static GNCPrintAmountInfo info;
    1001              :     static bool got_it = false;
    1002              : 
    1003            7 :     if (!got_it)
    1004              :     {
    1005            1 :         info = gnc_default_print_info_helper (5);
    1006            1 :         info.monetary = 0;
    1007            1 :         got_it = TRUE;
    1008              :     }
    1009              : 
    1010            7 :     return info;
    1011              : }
    1012              : 
    1013              : GNCPrintAmountInfo
    1014            7 : gnc_share_print_info_places (int decplaces)
    1015              : {
    1016              :     GNCPrintAmountInfo info;
    1017              : 
    1018            7 :     info = gnc_default_share_print_info ();
    1019            7 :     info.max_decimal_places = decplaces;
    1020            7 :     info.min_decimal_places = decplaces;
    1021            7 :     info.force_fit = 1;
    1022            7 :     info.round = 1;
    1023            7 :     return info;
    1024              : }
    1025              : 
    1026              : GNCPrintAmountInfo
    1027          221 : gnc_price_print_info (const gnc_commodity *curr, gboolean use_symbol)
    1028              : {
    1029              :     GNCPrintAmountInfo info;
    1030          221 :     auto force = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL,
    1031              :                                      GNC_PREF_PRICES_FORCE_DECIMAL);
    1032          221 :     info.commodity = curr;
    1033              : 
    1034          221 :     if (info.commodity)
    1035              :     {
    1036          221 :         int frac = gnc_commodity_get_fraction (curr);
    1037          221 :         guint8 decplaces = 0;
    1038          663 :         while (frac != 1 && (frac % 10) == 0 && (frac /= 10)) ++decplaces;
    1039          221 :         if (force)
    1040            0 :             decplaces += 2;
    1041          221 :         info.max_decimal_places = decplaces;
    1042          221 :         info.min_decimal_places = decplaces;
    1043              :     }
    1044              :     else
    1045              :     {
    1046            0 :         info.max_decimal_places = 6;
    1047            0 :         info.min_decimal_places = 0;
    1048              :     }
    1049              : 
    1050          221 :     info.use_separators = 1;
    1051          221 :     info.use_symbol = use_symbol ? 1 : 0;
    1052          221 :     info.use_locale = 1;
    1053          221 :     info.monetary = 1;
    1054              : 
    1055          221 :     info.force_fit = force;
    1056          221 :     info.round = force;
    1057          221 :     return info;
    1058              : }
    1059              : 
    1060              : GNCPrintAmountInfo
    1061            0 : gnc_default_price_print_info (const gnc_commodity *curr)
    1062              : {
    1063            0 :     return gnc_price_print_info (curr, FALSE);
    1064              : }
    1065              : 
    1066              : 
    1067              : GNCPrintAmountInfo
    1068            0 : gnc_integral_print_info (void)
    1069              : {
    1070              :     static GNCPrintAmountInfo info;
    1071              :     static bool got_it = false;
    1072              : 
    1073            0 :     if (!got_it)
    1074              :     {
    1075            0 :         info = gnc_default_print_info_helper (0);
    1076            0 :         got_it = TRUE;
    1077              :     }
    1078              : 
    1079            0 :     return info;
    1080              : }
    1081              : 
    1082              : /* Utility function for printing non-negative amounts */
    1083              : static int
    1084        11936 : PrintAmountInternal(char* buf, gnc_numeric val, const GNCPrintAmountInfo *info)
    1085              : {
    1086        11936 :     auto *lc = gnc_localeconv();
    1087        11936 :     constexpr size_t buf_size = 128;
    1088              :     char temp_buf[buf_size];
    1089              : 
    1090        11936 :     g_return_val_if_fail (info != nullptr, 0);
    1091              : 
    1092        11936 :     if (gnc_numeric_check (val))
    1093              :     {
    1094            2 :         PWARN ("Bad numeric: %s.",
    1095              :                gnc_numeric_errorCode_to_string(gnc_numeric_check (val)));
    1096            2 :         *buf = '\0';
    1097            2 :         return 0;
    1098              :     }
    1099              : 
    1100              :     /* Print the absolute value, but remember sign */
    1101        11934 :     auto value_is_negative = gnc_numeric_negative_p (val);
    1102        11934 :     val = gnc_numeric_abs (val);
    1103              : 
    1104              :     /* Try to print as decimal. */
    1105        11934 :     auto value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
    1106        11934 :     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            0 :         const int64_t denom = info->commodity ?
    1114            0 :              gnc_commodity_get_fraction(info->commodity) * 100 :
    1115            0 :              (info->max_decimal_places &&
    1116            0 :                info->max_decimal_places <= maximum_decimals) ?
    1117            0 :              pow_10[info->max_decimal_places] : pow_10[maximum_decimals];
    1118            0 :         val = gnc_numeric_convert(val, denom, GNC_HOW_RND_ROUND_HALF_UP);
    1119            0 :         value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
    1120              :     }
    1121        11934 :     auto min_dp = info->min_decimal_places;
    1122        11934 :     auto max_dp = info->max_decimal_places;
    1123              : 
    1124              :     /* Don to limit the number of decimal places _UNLESS_ force_fit is
    1125              :      * true. */
    1126        11934 :     if (!info->force_fit)
    1127        10903 :         max_dp = 99;
    1128              : 
    1129              :     /* rounding? -- can only ROUND if force_fit is also true */
    1130        11934 :     if (value_is_decimal && info->round && info->force_fit)
    1131              :     {
    1132              :         /* Limit the denom to 10^13 ~= 2^44, leaving max at ~524288 */
    1133          519 :         gnc_numeric rounding{5, pow_10[max_dp + 1]};
    1134              : 
    1135          519 :         val = gnc_numeric_add(val, rounding, val.denom,
    1136              :                               GNC_HOW_DENOM_EXACT | GNC_HOW_RND_TRUNC);
    1137              : 
    1138          519 :         if (gnc_numeric_check(val))
    1139              :         {
    1140            0 :             PWARN ("Bad numeric from rounding: %s.",
    1141              :                    gnc_numeric_errorCode_to_string(gnc_numeric_check (val)));
    1142            0 :             *buf = '\0';
    1143            0 :             return 0;
    1144              :         }
    1145              :     }
    1146              : 
    1147              :     /* calculate the integer part and the remainder */
    1148        11934 :     auto whole = gnc_numeric_convert(val, 1, GNC_HOW_RND_TRUNC);
    1149        11934 :     val = gnc_numeric_sub (val, whole, GNC_DENOM_AUTO, GNC_HOW_RND_NEVER);
    1150        11934 :     if (gnc_numeric_check (val))
    1151              :     {
    1152            0 :         PWARN ("Problem with remainder: %s.",
    1153              :                gnc_numeric_errorCode_to_string(gnc_numeric_check (val)));
    1154            0 :         *buf = '\0';
    1155            0 :         return 0;
    1156              :     }
    1157              : 
    1158              :     // Value may now be decimal, for example if the factional part is zero
    1159        11934 :     value_is_decimal = gnc_numeric_to_decimal(&val, nullptr);
    1160              :     /* print the integer part without separators */
    1161        11934 :     snprintf(temp_buf, buf_size, "%" G_GINT64_FORMAT, whole.num);
    1162        11934 :     auto num_whole_digits = strlen (temp_buf);
    1163              : 
    1164        11934 :     if (!info->use_separators)
    1165         2048 :         strcpy (buf, temp_buf);
    1166              :     else
    1167              :     {
    1168              :         char* separator;
    1169              :         char* group;
    1170              : 
    1171         9886 :         if (info->monetary)
    1172              :         {
    1173         9367 :             separator = lc->mon_thousands_sep;
    1174         9367 :             group = lc->mon_grouping;
    1175              :         }
    1176              :         else
    1177              :         {
    1178          519 :             separator = lc->thousands_sep;
    1179          519 :             group = lc->grouping;
    1180              :         }
    1181              : 
    1182         9886 :         auto buf_ptr = buf;
    1183         9886 :         auto temp_ptr = &temp_buf[num_whole_digits - 1];
    1184         9886 :         auto group_count = 0;
    1185              : 
    1186        29038 :         while (temp_ptr != temp_buf)
    1187              :         {
    1188        19152 :             *buf_ptr++ = *temp_ptr--;
    1189              : 
    1190        19152 :             if (*group != CHAR_MAX)
    1191              :             {
    1192        19152 :                 group_count++;
    1193              : 
    1194        19152 :                 if (group_count == *group)
    1195              :                 {
    1196         3455 :                     g_utf8_strncpy(buf_ptr, separator, 1);
    1197         3455 :                     buf_ptr = g_utf8_find_next_char(buf_ptr, nullptr);
    1198         3455 :                     group_count = 0;
    1199              : 
    1200              :                     /* Peek ahead at the next group code */
    1201         3455 :                     switch (group[1])
    1202              :                     {
    1203              :                         /* A null char means repeat the last group indefinitely */
    1204         3455 :                     case '\0':
    1205         3455 :                         break;
    1206              :                         /* CHAR_MAX means no more grouping allowed */
    1207            0 :                     case CHAR_MAX:
    1208              :                         /* fall through */
    1209              :                         /* Anything else means another group size */
    1210              :                     default:
    1211            0 :                         group++;
    1212            0 :                         break;
    1213              :                     }
    1214              :                 }
    1215              :             }
    1216              :         }
    1217              : 
    1218              :         /* We built the string backwards, now reverse */
    1219         9886 :         *buf_ptr++ = *temp_ptr;
    1220         9886 :         *buf_ptr = '\0';
    1221         9886 :         auto rev_buf = g_utf8_strreverse(buf, -1);
    1222         9886 :         strcpy (buf, rev_buf);
    1223         9886 :         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        11934 :     if (!value_is_decimal)
    1230              :     {
    1231           42 :         val = gnc_numeric_reduce (val);
    1232              : 
    1233           42 :         if (val.denom > 0)
    1234           42 :             snprintf (temp_buf, buf_size, "%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
    1235              :                      val.num, val.denom);
    1236              :         else
    1237            0 :             snprintf (temp_buf, buf_size, "%" G_GINT64_FORMAT " * %" G_GINT64_FORMAT,
    1238            0 :                      val.num, -val.denom);
    1239              : 
    1240           42 :         if (whole.num == 0)
    1241            0 :             *buf = '\0';
    1242           42 :         else if (value_is_negative)
    1243            0 :             strcat(buf, " - ");
    1244              :         else
    1245           42 :             strcat(buf, " + ");
    1246              : 
    1247           42 :         strcat (buf, temp_buf);
    1248              :     }
    1249              :     else
    1250              :     {
    1251        11892 :         guint8 num_decimal_places = 0;
    1252        11892 :         char* temp_ptr = temp_buf;
    1253              : 
    1254        11892 :         auto decimal_point = info->monetary
    1255        11892 :                              ? lc->mon_decimal_point
    1256              :                              : lc->decimal_point;
    1257        11892 :         g_utf8_strncpy(temp_ptr, decimal_point, 1);
    1258        11892 :         temp_ptr = g_utf8_find_next_char(temp_ptr, nullptr);
    1259              : 
    1260        24685 :         while (!gnc_numeric_zero_p (val)
    1261        12793 :                 && (val.denom != 1)
    1262        37478 :                 && (num_decimal_places < max_dp))
    1263              :         {
    1264              :             gint64 digit;
    1265              : 
    1266        12793 :             val.denom = val.denom / 10;
    1267              : 
    1268        12793 :             digit = val.num / val.denom;
    1269              : 
    1270        12793 :             *temp_ptr++ = digit + '0';
    1271        12793 :             num_decimal_places++;
    1272              : 
    1273        12793 :             val.num = val.num - (digit * val.denom);
    1274              :         }
    1275              : 
    1276        27452 :         while (num_decimal_places < min_dp)
    1277              :         {
    1278        15560 :             *temp_ptr++ = '0';
    1279        15560 :             num_decimal_places++;
    1280              :         }
    1281              : 
    1282              :         /* cap the end and move to the last character */
    1283        11892 :         *temp_ptr-- = '\0';
    1284              : 
    1285              :         /* Here we strip off trailing decimal zeros per the argument. */
    1286        11892 :         while (*temp_ptr == '0' && num_decimal_places > min_dp)
    1287              :         {
    1288            0 :             *temp_ptr-- = '\0';
    1289            0 :             num_decimal_places--;
    1290              :         }
    1291              : 
    1292        11892 :         if (num_decimal_places > max_dp)
    1293              :         {
    1294            0 :             PWARN ("max_decimal_places too small; limit %d, value %s%s",
    1295              :                    info->max_decimal_places, buf, temp_buf);
    1296              :         }
    1297              : 
    1298        11892 :         strcat (buf, temp_buf);
    1299              :     }
    1300              : 
    1301        11934 :     return strlen(buf);
    1302              : }
    1303              : 
    1304              : /**
    1305              :  * @param bufp Should be at least 64 chars.
    1306              :  **/
    1307              : int
    1308        11936 : xaccSPrintAmount (char*  bufp, gnc_numeric val, GNCPrintAmountInfo info)
    1309              : {
    1310        11936 :     auto orig_bufp = bufp;
    1311        11936 :     auto currency_symbol = "";
    1312              :     const char* sign;
    1313              : 
    1314              :     char cs_precedes;
    1315              :     char sep_by_space;
    1316              :     char sign_posn;
    1317              : 
    1318        11936 :     bool print_sign = true;
    1319        11936 :     bool print_absolute = false;
    1320              : 
    1321        11936 :     if (!bufp)
    1322            0 :         return 0;
    1323              : 
    1324        11936 :     auto lc = gnc_localeconv();
    1325        11936 :     if (info.use_locale)
    1326        11655 :         if (gnc_numeric_negative_p (val))
    1327              :         {
    1328         1111 :             cs_precedes  = lc->n_cs_precedes;
    1329         1111 :             sep_by_space = lc->n_sep_by_space;
    1330              :         }
    1331              :         else
    1332              :         {
    1333        10544 :             cs_precedes  = lc->p_cs_precedes;
    1334        10544 :             sep_by_space = lc->p_sep_by_space;
    1335              :         }
    1336              :     else
    1337              :     {
    1338          281 :         cs_precedes = TRUE;
    1339          281 :         sep_by_space = TRUE;
    1340              :     }
    1341              : 
    1342        11936 :     if (info.commodity && info.use_symbol)
    1343              :     {
    1344         8653 :         currency_symbol = gnc_commodity_get_nice_symbol (info.commodity);
    1345         8653 :         if (!gnc_commodity_is_iso (info.commodity))
    1346              :         {
    1347          279 :             cs_precedes  = FALSE;
    1348          279 :             sep_by_space = TRUE;
    1349              :         }
    1350              :     }
    1351              : 
    1352        11936 :     if (gnc_numeric_negative_p (val))
    1353              :     {
    1354         1145 :         sign = lc->negative_sign;
    1355         1145 :         sign_posn = lc->n_sign_posn;
    1356              :     }
    1357              :     else
    1358              :     {
    1359        10791 :         sign = lc->positive_sign;
    1360        10791 :         sign_posn = lc->p_sign_posn;
    1361              :     }
    1362              : 
    1363        11936 :     if (gnc_numeric_zero_p (val) || (sign == nullptr) || (sign[0] == 0))
    1364        10791 :         print_sign = FALSE;
    1365              : 
    1366              :     /* See if we print sign now */
    1367        11936 :     if (print_sign && (sign_posn == 1))
    1368         1145 :         bufp = g_stpcpy(bufp, sign);
    1369              : 
    1370              :     /* Now see if we print currency */
    1371        11936 :     if (cs_precedes)
    1372              :     {
    1373              :         /* See if we print sign now */
    1374        11657 :         if (print_sign && (sign_posn == 3))
    1375            0 :             bufp = g_stpcpy(bufp, sign);
    1376              : 
    1377        11657 :         if (info.use_symbol)
    1378              :         {
    1379         8374 :             bufp = g_stpcpy(bufp, currency_symbol);
    1380         8374 :             if (sep_by_space)
    1381            0 :                 bufp = g_stpcpy(bufp, " ");
    1382              :         }
    1383              : 
    1384              :         /* See if we print sign now */
    1385        11657 :         if (print_sign && (sign_posn == 4))
    1386            0 :             bufp = g_stpcpy(bufp, sign);
    1387              :     }
    1388              : 
    1389              :     /* Now see if we print parentheses */
    1390        11936 :     if (print_sign && (sign_posn == 0))
    1391              :     {
    1392            0 :         bufp = g_stpcpy(bufp, "(");
    1393            0 :         print_absolute = TRUE;
    1394              :     }
    1395              : 
    1396              :     /* Now print the value */
    1397        11936 :     bufp += PrintAmountInternal(bufp,
    1398            0 :                                 print_absolute ? gnc_numeric_abs(val) : val,
    1399              :                                 &info);
    1400              : 
    1401              :     /* Now see if we print parentheses */
    1402        11936 :     if (print_sign && (sign_posn == 0))
    1403            0 :         bufp = g_stpcpy(bufp, ")");
    1404              : 
    1405              :     /* Now see if we print currency */
    1406        11936 :     if (!cs_precedes)
    1407              :     {
    1408              :         /* See if we print sign now */
    1409          279 :         if (print_sign && (sign_posn == 3))
    1410            0 :             bufp = g_stpcpy(bufp, sign);
    1411              : 
    1412          279 :         if (info.use_symbol)
    1413              :         {
    1414          279 :             if (sep_by_space)
    1415          279 :                 bufp = g_stpcpy(bufp, " ");
    1416          279 :             bufp = g_stpcpy(bufp, currency_symbol);
    1417              :         }
    1418              : 
    1419              :         /* See if we print sign now */
    1420          279 :         if (print_sign && (sign_posn == 4))
    1421            0 :             bufp = g_stpcpy(bufp, sign);
    1422              :     }
    1423              : 
    1424              :     /* See if we print sign now */
    1425        11936 :     if (print_sign && (sign_posn == 2))
    1426            0 :         bufp = g_stpcpy(bufp, sign);
    1427              : 
    1428              :     /* return length of printed string */
    1429        11936 :     return (bufp - orig_bufp);
    1430              : }
    1431              : 
    1432              : #define BUFLEN 1024
    1433              : 
    1434              : const char*
    1435        11934 : xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info)
    1436              : {
    1437              :     /* hack alert -- this is not thread safe ... */
    1438              :     static char buf[BUFLEN];
    1439              : 
    1440        11934 :     if (!xaccSPrintAmount (buf, val, info))
    1441            0 :         buf[0] = '\0';
    1442              : 
    1443              :     /* its OK to return buf, since we declared it static */
    1444        11934 :     return buf;
    1445              : }
    1446              : 
    1447              : const char*
    1448            0 : gnc_print_amount_with_bidi_ltr_isolate (gnc_numeric val, GNCPrintAmountInfo info)
    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            0 :     auto offset = info.use_symbol ? 3 : 0;
    1455              : 
    1456            0 :     if (!gnc_commodity_is_currency (info.commodity))
    1457            0 :         offset = 0;
    1458              : 
    1459            0 :     memset (buf, 0, BUFLEN);
    1460            0 :     if (!xaccSPrintAmount (buf + offset, val, info))
    1461              :     {
    1462            0 :         buf[0] = '\0';
    1463            0 :         return buf;
    1464              :     };
    1465              : 
    1466            0 :     if (offset == 0)
    1467            0 :         return buf;
    1468              : 
    1469            0 :     memcpy (buf, ltr_isolate, 3);
    1470              : 
    1471            0 :     if (buf[BUFLEN - 4] == '\0')
    1472              :     {
    1473            0 :         auto length = strlen (buf);
    1474            0 :         memcpy (buf + length, ltr_pop_isolate, 3);
    1475              :     }
    1476              :     else
    1477              :     {
    1478            0 :         buf[BUFLEN - 1] = '\0';
    1479            0 :         memcpy (buf + BUFLEN - 4, ltr_pop_isolate, 3);
    1480              : 
    1481            0 :         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            0 :     return buf;
    1486              : }
    1487              : 
    1488              : char*
    1489            0 : gnc_wrap_text_with_bidi_ltr_isolate (const char* text)
    1490              : {
    1491              :     static const char* ltr = "\u2066"; // ltr isolate
    1492              :     static const char* pop = "\u2069"; // pop directional formatting
    1493              : 
    1494            0 :     if (!text)
    1495            0 :         return nullptr;
    1496              : 
    1497            0 :     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            0 : numeric_to_words(gnc_numeric val)
    1564              : {
    1565            0 :     if (val.denom < 0)      // reciprocal denominator means num * -denom
    1566            0 :         val = gnc_numeric_convert (val, 1, GNC_HOW_RND_NEVER);
    1567            0 :     if (gnc_numeric_check (val))
    1568            0 :         return g_strdup ("");
    1569              : 
    1570            0 :     std::string out;
    1571            0 :     out.reserve (300);
    1572            0 :     auto append_word = [&out](auto word){ if (!out.empty()) out += ' '; out += word; };
    1573            0 :     auto append_below_1000 = [&](uint64_t n)
    1574              :     {
    1575            0 :         if (n >= 100)
    1576              :         {
    1577            0 :             append_word (small_numbers[n / 100]);
    1578            0 :             append_word (big_numbers[0]);
    1579            0 :             n %= 100;
    1580              :         }
    1581            0 :         if (n > 20)
    1582              :         {
    1583            0 :             append_word (medium_numbers[n / 10]);
    1584            0 :             n %= 10;
    1585              :         }
    1586            0 :         if (n > 0)
    1587            0 :             append_word (small_numbers[n]);
    1588            0 :     };
    1589              : 
    1590            0 :     auto absnum = val.num < 0 ? 0 - static_cast<uint64_t> (val.num)
    1591            0 :         : static_cast<uint64_t> (val.num);
    1592            0 :     auto denom = static_cast<uint64_t> (val.denom);
    1593            0 :     auto whole = absnum / denom;
    1594            0 :     if (whole == 0)
    1595            0 :         out = "zero";
    1596              :     else
    1597              :     {
    1598            0 :         std::vector<unsigned> groups;
    1599            0 :         for (; whole; whole /= 1000)
    1600            0 :             groups.push_back (whole % 1000);
    1601              : 
    1602            0 :         for (int i = groups.size() - 1; i >= 0; --i)
    1603              :         {
    1604            0 :             if (groups[i] == 0)
    1605            0 :                 continue;
    1606            0 :             append_below_1000 (groups[i]);
    1607            0 :             if (i > 0)
    1608            0 :                 append_word (big_numbers[i]);
    1609              :         }
    1610            0 :     }
    1611              : 
    1612            0 :     if (denom != 1)
    1613              :     {
    1614            0 :         out += " and ";
    1615            0 :         auto rem = absnum % denom;
    1616            0 :         for (auto d = denom, r = rem ? rem : 1; d % 10 == 0; d /= 10, r /= 10)
    1617            0 :             if (r == 0)
    1618            0 :                 out += '0';
    1619            0 :         out += std::to_string (rem);
    1620            0 :         out += '/';
    1621            0 :         out += std::to_string (denom);
    1622              :     }
    1623            0 :     return g_strndup (out.c_str(), out.size());
    1624            0 : }
    1625              : 
    1626              : const char*
    1627            0 : printable_value (double val, int denom)
    1628              : {
    1629            0 :     auto num = gnc_numeric_create(round(val * denom), denom);
    1630            0 :     auto info = gnc_share_print_info_places(log10(denom));
    1631            0 :     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         3013 : multiplier (int num_decimals)
    1661              : {
    1662         3013 :     switch (num_decimals)
    1663              :     {
    1664            0 :     case 12:
    1665            0 :         return 1000000000000;
    1666            0 :     case 11:
    1667            0 :         return 100000000000;
    1668            0 :     case 10:
    1669            0 :         return 10000000000;
    1670            0 :     case 9:
    1671            0 :         return 1000000000;
    1672          192 :     case 8:
    1673          192 :         return 100000000;
    1674          276 :     case 7:
    1675          276 :         return 10000000;
    1676          294 :     case 6:
    1677          294 :         return 1000000;
    1678          258 :     case 5:
    1679          258 :         return 100000;
    1680          467 :     case 4:
    1681          467 :         return 10000;
    1682          369 :     case 3:
    1683          369 :         return 1000;
    1684          687 :     case 2:
    1685          687 :         return 100;
    1686          470 :     case 1:
    1687          470 :         return 10;
    1688            0 :     case 0:
    1689            0 :          return 1;
    1690            0 :     default:
    1691            0 :         PERR("bad fraction length");
    1692            0 :         g_assert_not_reached();
    1693              :         break;
    1694              :     }
    1695              : 
    1696              :     return 1;
    1697              : }
    1698              : 
    1699              : static gboolean
    1700         3229 : 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         3229 :     if (endstr)
    1708          140 :         *endstr = (char* ) in_str;
    1709              : 
    1710         3229 :     if (!in_str)
    1711            0 :         return FALSE;
    1712              : 
    1713              :     const char* in;
    1714         3229 :     if (!g_utf8_validate(in_str, -1, &in))
    1715              :     {
    1716            0 :         printf("Invalid utf8 string '%s'. Bad character at position %ld.\n",
    1717              :                in_str, g_utf8_pointer_to_offset (in_str, in));
    1718            0 :         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         3229 :     auto out_str = g_new(gchar, strlen(in_str) + 128);
    1724         3229 :     auto out = out_str;
    1725              : 
    1726              :     /* 'in' is used to traverse 'in_str'. */
    1727         3229 :     in = in_str;
    1728              : 
    1729         3229 :     auto is_negative = false;
    1730         3229 :     auto got_decimal = false;
    1731         3229 :     auto need_paren = false;
    1732         3229 :     int64_t numer = 0;
    1733         3229 :     int64_t denom = 1;
    1734              : 
    1735              :     /* Initialize the state machine */
    1736         3229 :     auto state = START_ST;
    1737              : 
    1738              :     /* This while loop implements a state machine for parsing numbers. */
    1739              :     while (TRUE)
    1740              :     {
    1741        41134 :         auto next_state = state;
    1742        41134 :         auto uc = g_utf8_get_char(in);
    1743              : 
    1744              :         /* Ignore anything in the 'ignore list' */
    1745        41134 :         if (ignore_list && uc && g_utf8_strchr(ignore_list, -1, uc))
    1746              :         {
    1747            5 :             in = g_utf8_next_char(in);
    1748            5 :             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        41129 :         switch (state)
    1754              :         {
    1755              :             /* START_ST means we have parsed 0 or more whitespace characters */
    1756         3235 :             case START_ST:
    1757         3235 :                 if (g_unichar_isdigit(uc))
    1758              :                 {
    1759         3216 :                     int count = g_unichar_to_utf8(uc, out);
    1760         3216 :                     out += count; /* we record the digits themselves in out_str
    1761              :                     * for later conversion by libc routines */
    1762         3216 :                     next_state = NUMER_ST;
    1763              :                 }
    1764           19 :                 else if (uc == decimal_point)
    1765            0 :                     next_state = FRAC_ST;
    1766           19 :                 else if (g_unichar_isspace(uc))
    1767              :                     ;
    1768           13 :                 else if (uc == negative_sign)
    1769              :                 {
    1770            6 :                     is_negative = TRUE;
    1771            6 :                     next_state = NEG_ST;
    1772              :                 }
    1773            7 :                 else if (uc == '(')
    1774              :                 {
    1775            0 :                     is_negative = TRUE;
    1776            0 :                     need_paren = TRUE;
    1777            0 :                     next_state = NEG_ST;
    1778              :                 }
    1779              :                 else
    1780            7 :                     next_state = NO_NUM_ST;
    1781              : 
    1782         3235 :                 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            6 :             case NEG_ST:
    1787            6 :                 if (g_unichar_isdigit(uc))
    1788              :                 {
    1789            6 :                     int count = g_unichar_to_utf8(uc, out);
    1790            6 :                     out += count;
    1791            6 :                     next_state = NUMER_ST;
    1792              :                 }
    1793            0 :                 else if (uc == decimal_point)
    1794            0 :                     next_state = FRAC_ST;
    1795            0 :                 else if (g_unichar_isspace(uc))
    1796              :                     ;
    1797              :                 else
    1798            0 :                     next_state = NO_NUM_ST;
    1799              : 
    1800            6 :                 break;
    1801              : 
    1802              :             /* NUMER_ST means we have started parsing the number, but
    1803              :              * have not encountered a decimal separator. */
    1804        23435 :             case NUMER_ST:
    1805        23435 :                 if (g_unichar_isdigit(uc))
    1806              :                 {
    1807        18262 :                     int count = g_unichar_to_utf8(uc, out);
    1808        18262 :                     out += count;
    1809              :                 }
    1810         5173 :                 else if (uc == decimal_point)
    1811         3110 :                     next_state = FRAC_ST;
    1812         2063 :                 else if (uc == group_separator)
    1813              :                     ; //ignore it
    1814          112 :                 else if (uc == ')' && need_paren)
    1815              :                 {
    1816            0 :                     next_state = DONE_ST;
    1817            0 :                     need_paren = FALSE;
    1818              :                 }
    1819              :                 else
    1820          112 :                     next_state = DONE_ST;
    1821              : 
    1822        23435 :                 break;
    1823              : 
    1824              :             /* FRAC_ST means we are now parsing fractional digits. */
    1825        14453 :             case FRAC_ST:
    1826        14453 :                 if (g_unichar_isdigit(uc))
    1827              :                 {
    1828        11343 :                     int count = g_unichar_to_utf8(uc, out);
    1829        11343 :                     out += count;
    1830              :                 }
    1831         3110 :                 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            1 :                     if (g_unichar_isspace(decimal_point))
    1837            0 :                         next_state = DONE_ST;
    1838              :                     else
    1839            1 :                         next_state = NO_NUM_ST;
    1840              :                 }
    1841         3109 :                 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            0 :                     if (g_unichar_isspace(group_separator))
    1847            0 :                         next_state = DONE_ST;
    1848              :                 }
    1849         3109 :                 else if (uc == ')' && need_paren)
    1850              :                 {
    1851            0 :                     next_state = DONE_ST;
    1852            0 :                     need_paren = FALSE;
    1853              :                 }
    1854              :                 else
    1855         3109 :                     next_state = DONE_ST;
    1856              : 
    1857        14453 :                 break;
    1858              : 
    1859            0 :             default:
    1860            0 :                 PERR("bad state");
    1861            0 :                 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        41129 :         if (((next_state == FRAC_ST) && (state != FRAC_ST)) ||
    1868         3221 :             ((next_state == DONE_ST) && !got_decimal))
    1869              :         {
    1870         3222 :             *out = '\0';
    1871              : 
    1872         3222 :             if (*out_str && sscanf(out_str, "%" SCNd64, &numer) < 1)
    1873            0 :                 next_state = NO_NUM_ST;
    1874         3222 :             else if (next_state == FRAC_ST)
    1875              :             {
    1876              :                 /* reset the out pointer to record the fraction */
    1877         3110 :                 out = out_str;
    1878         3110 :                 *out = '\0';
    1879              : 
    1880         3110 :                 got_decimal = TRUE;
    1881              :             }
    1882              :         }
    1883              : 
    1884        41129 :         state = next_state;
    1885        41129 :         if (done_state (state))
    1886              :             break;
    1887              : 
    1888        37900 :         in = g_utf8_next_char(in);
    1889        37905 :     }
    1890              : 
    1891              :     /* If there was an error, just quit */
    1892         3229 :     if (need_paren || (state == NO_NUM_ST))
    1893              :     {
    1894            8 :         g_free(out_str);
    1895            8 :         return FALSE;
    1896              :     }
    1897              : 
    1898              :     /* Cap the end of the fraction string, if any */
    1899         3221 :     *out = '\0';
    1900              : 
    1901              :     /* Add in fractional value */
    1902         3221 :     if (got_decimal && *out_str)
    1903              :     {
    1904              : 
    1905         3013 :         auto len = strlen(out_str);
    1906              : 
    1907         3013 :         if (len > 12)
    1908              :         {
    1909            0 :             out_str[12] = '\0';
    1910            0 :             len = 12;
    1911              :         }
    1912              : 
    1913              :         int64_t fraction;
    1914         3013 :         if (sscanf (out_str, "%" SCNd64 , &fraction) < 1)
    1915              :         {
    1916            0 :             g_free(out_str);
    1917            0 :             return FALSE;
    1918              :         }
    1919              : 
    1920         3013 :         denom = multiplier(len);
    1921         3013 :         numer *= denom;
    1922         3013 :         numer += fraction;
    1923         3013 :     }
    1924          208 :     else if (monetary && use_auto_decimal && !got_decimal)
    1925              :     {
    1926            0 :         if ((auto_decimal_places > 0) && (auto_decimal_places <= 12))
    1927              :         {
    1928            0 :             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         3221 :     if (result)
    1939              :     {
    1940         3221 :         *result = gnc_numeric_create (numer, denom);
    1941         3221 :         if (is_negative)
    1942            6 :             *result = gnc_numeric_neg (*result);
    1943              :     }
    1944              : 
    1945         3221 :     if (endstr)
    1946          133 :         *endstr = (char* ) in;
    1947              : 
    1948         3221 :     g_free (out_str);
    1949              : 
    1950         3221 :     return TRUE;
    1951              : }
    1952              : 
    1953              : 
    1954              : static gboolean
    1955         3206 : xaccParseAmountBasicInternal (const char*  in_str, gboolean monetary,
    1956              :                               gboolean use_auto_decimal, gnc_numeric *result,
    1957              :                               char** endstr, gboolean skip)
    1958              : {
    1959         3206 :     struct lconv *lc = gnc_localeconv();
    1960         3206 :     gunichar negative_sign = g_utf8_get_char(lc->negative_sign);
    1961              : 
    1962              :     gunichar decimal_point;
    1963              :     gunichar group_separator;
    1964         3206 :     if (monetary)
    1965              :     {
    1966          646 :         group_separator = g_utf8_get_char(lc->mon_thousands_sep);
    1967          646 :         decimal_point = g_utf8_get_char(lc->mon_decimal_point);
    1968              :     }
    1969              :     else
    1970              :     {
    1971         2560 :         group_separator = g_utf8_get_char(lc->thousands_sep);
    1972         2560 :         decimal_point = g_utf8_get_char(lc->decimal_point);
    1973              :     }
    1974              : 
    1975         3206 :     const char* ignore = nullptr;
    1976         3206 :     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            0 :         ignore = lc->positive_sign;
    1982            0 :         if (!ignore || !*ignore)
    1983            0 :             ignore = "+";
    1984              :     }
    1985              : 
    1986         3206 :     return xaccParseAmountInternal(in_str, monetary, negative_sign,
    1987              :                                    decimal_point, group_separator,
    1988              :                                    ignore, use_auto_decimal,
    1989         3206 :                                    result, endstr);
    1990              : }
    1991              : 
    1992              : 
    1993              : gboolean
    1994         3206 : xaccParseAmount (const char*  in_str, gboolean monetary, gnc_numeric *result,
    1995              :                  char** endstr)
    1996              : {
    1997         3206 :     return xaccParseAmountBasicInternal (in_str, monetary, auto_decimal_enabled,
    1998         3206 :                                          result, endstr, FALSE);
    1999              : }
    2000              : 
    2001              : gboolean
    2002            0 : xaccParseAmountImport (const char*  in_str, gboolean monetary,
    2003              :                         gnc_numeric *result,
    2004              :                         char** endstr, gboolean skip)
    2005              : {
    2006            0 :     return xaccParseAmountBasicInternal (in_str, monetary, FALSE,
    2007            0 :                                          result, endstr, skip);
    2008              : }
    2009              : 
    2010              : 
    2011              : gboolean
    2012           13 : 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           13 :     return xaccParseAmountInternal (in_str, monetary, negative_sign,
    2018              :                                     decimal_point, group_separator,
    2019              :                                     ignore_list, auto_decimal_enabled,
    2020           13 :                                     result, endstr);
    2021              : }
    2022              : 
    2023              : gboolean
    2024           10 : 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           10 :     return xaccParseAmountInternal (in_str, monetary, negative_sign,
    2030              :                                     decimal_point, group_separator,
    2031              :                                     ignore_list, FALSE,
    2032           10 :                                     result, endstr);
    2033              : }
    2034              : 
    2035              : 
    2036              : /* enable/disable the auto_decimal_enabled option */
    2037              : static void
    2038            0 : gnc_set_auto_decimal_enabled (gpointer settings, char* key, gpointer user_data)
    2039              : {
    2040            0 :     auto_decimal_enabled =
    2041            0 :             gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT);
    2042            0 : }
    2043              : 
    2044              : /* set the number of auto decimal places to use */
    2045              : static void
    2046            0 : gnc_set_auto_decimal_places (gpointer settings, char* key, gpointer user_data)
    2047              : {
    2048            0 :     auto_decimal_places =
    2049            0 :             gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES);
    2050            0 : }
    2051              : 
    2052              : static void
    2053            0 : gnc_auto_decimal_init (void)
    2054              : {
    2055            0 :     auto_decimal_enabled =
    2056            0 :         gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT);
    2057            0 :     auto_decimal_places =
    2058            0 :         gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES);
    2059            0 : }
    2060              : 
    2061              : void
    2062            0 : gnc_ui_util_init (void)
    2063              : {
    2064            0 :     gnc_configure_account_separator ();
    2065            0 :     gnc_auto_decimal_init();
    2066              : 
    2067            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_ACCOUNT_SEPARATOR,
    2068              :                           (void*)gnc_configure_account_separator, nullptr);
    2069            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_NONE,
    2070              :                           (void*)gnc_configure_reverse_balance, nullptr);
    2071            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_CREDIT,
    2072              :                           (void*)gnc_configure_reverse_balance, nullptr);
    2073            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_REVERSED_ACCTS_INC_EXP,
    2074              :                           (void*)gnc_configure_reverse_balance, nullptr);
    2075            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_CHOICE_LOCALE,
    2076              :                           (void*)gnc_currency_changed_cb, nullptr);
    2077            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_CHOICE_OTHER,
    2078              :                           (void*)gnc_currency_changed_cb, nullptr);
    2079            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_CURRENCY_OTHER,
    2080              :                           (void*)gnc_currency_changed_cb, nullptr);
    2081            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_CHOICE_LOCALE,
    2082              :                           (void*)gnc_currency_changed_cb, nullptr);
    2083            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_CHOICE_OTHER,
    2084              :                           (void*)gnc_currency_changed_cb, nullptr);
    2085            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_CURRENCY_OTHER,
    2086              :                           (void*)gnc_currency_changed_cb, nullptr);
    2087            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_POINT,
    2088              :                           (void*)gnc_set_auto_decimal_enabled, nullptr);
    2089            0 :     gnc_prefs_register_cb(GNC_PREFS_GROUP_GENERAL, GNC_PREF_AUTO_DECIMAL_PLACES,
    2090              :                           (void*)gnc_set_auto_decimal_places, nullptr);
    2091              : 
    2092            0 : }
    2093              : 
    2094              : void
    2095            0 : gnc_ui_util_remove_registered_prefs (void)
    2096              : {
    2097              :     // remove the registered pref call backs above
    2098            0 :     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
    2099              :                                  GNC_PREF_ACCOUNT_SEPARATOR,
    2100              :                                  (void*)gnc_configure_account_separator, nullptr);
    2101            0 :     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
    2102              :                                  GNC_PREF_REVERSED_ACCTS_NONE,
    2103              :                                  (void*)gnc_configure_reverse_balance, nullptr);
    2104            0 :     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
    2105              :                                  GNC_PREF_REVERSED_ACCTS_CREDIT,
    2106              :                                  (void*)gnc_configure_reverse_balance, nullptr);
    2107            0 :     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            0 :     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
    2111              :                                  GNC_PREF_CURRENCY_CHOICE_LOCALE,
    2112              :                                  (void*)gnc_currency_changed_cb, nullptr);
    2113            0 :     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
    2114              :                                  GNC_PREF_CURRENCY_CHOICE_OTHER,
    2115              :                                  (void*)gnc_currency_changed_cb, nullptr);
    2116            0 :     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
    2117              :                                  GNC_PREF_CURRENCY_OTHER,
    2118              :                                  (void*)gnc_currency_changed_cb, nullptr);
    2119            0 :     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            0 :     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            0 :     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
    2126              :                                  GNC_PREF_CURRENCY_OTHER,
    2127              :                                  (void*)gnc_currency_changed_cb, nullptr);
    2128            0 :     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            0 :     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            0 : }
    2135              : 
    2136              : char*
    2137            0 : gnc_filter_text_for_bidi_marks (const char* text)
    2138              : {
    2139            0 :     if (!text)
    2140            0 :         return nullptr;
    2141              : 
    2142            0 :     int32_t len = static_cast<int32_t> (strlen (text));
    2143            0 :     std::string result;
    2144            0 :     result.reserve (len);
    2145              : 
    2146            0 :     const char* p = text;
    2147            0 :     int32_t i = 0;
    2148              : 
    2149            0 :     while (i < len)
    2150              :     {
    2151              :         UChar32 c;
    2152            0 :         int32_t start = i;
    2153            0 :         U8_NEXT (p, i, len, c);
    2154              : 
    2155            0 :         if (c >= 0 && !u_hasBinaryProperty (c, UCHAR_BIDI_CONTROL))
    2156            0 :             result.append (p + start, i - start);
    2157              :     }
    2158              : 
    2159            0 :     return g_strdup (result.c_str ());
    2160            0 : }
    2161              : 
    2162              : static inline bool
    2163            0 : unichar_is_cntrl (gunichar uc)
    2164              : {
    2165            0 :     return (uc < 0x20 || (uc > 0x7e && uc < 0xa0));
    2166              : }
    2167              : 
    2168              : char*
    2169            0 : gnc_filter_text_for_control_chars (const char* text)
    2170              : {
    2171            0 :     bool cntrl = false;
    2172            0 :     bool text_found = false;
    2173              : 
    2174            0 :     if (!text)
    2175            0 :         return nullptr;
    2176              : 
    2177            0 :     if (!g_utf8_validate (text, -1, nullptr))
    2178            0 :         return nullptr;
    2179              : 
    2180            0 :     auto filtered = g_string_sized_new (strlen (text) + 1);
    2181              : 
    2182            0 :     auto ch = text;
    2183              : 
    2184            0 :     while (*ch)
    2185              :     {
    2186            0 :         auto uc = g_utf8_get_char (ch);
    2187              : 
    2188              :         // check for starting with control characters
    2189            0 :         if (unichar_is_cntrl (uc) && !text_found)
    2190              :         {
    2191            0 :             ch = g_utf8_next_char (ch);
    2192            0 :             continue;
    2193              :         }
    2194              :         // check for alpha, num and punctuation
    2195            0 :         if (!unichar_is_cntrl (uc))
    2196              :         {
    2197            0 :             filtered = g_string_append_unichar (filtered, uc);
    2198            0 :             text_found = true;
    2199              :         }
    2200              :         // check for control characters after text
    2201            0 :         if (unichar_is_cntrl (uc))
    2202            0 :             cntrl = true;
    2203              : 
    2204            0 :         ch = g_utf8_next_char (ch);
    2205              : 
    2206            0 :         if (cntrl) // if control characters in text replace with space
    2207              :         {
    2208            0 :             auto uc2 = g_utf8_get_char (ch);
    2209              : 
    2210            0 :             if (!unichar_is_cntrl (uc2))
    2211            0 :                 filtered = g_string_append_unichar (filtered, ' ');
    2212              :         }
    2213            0 :         cntrl = false;
    2214              :     }
    2215            0 :     return g_string_free (filtered, FALSE);
    2216              : }
    2217              : 
    2218              : void
    2219            0 : gnc_filter_text_set_cursor_position (const char* incoming_text,
    2220              :                                      const char* symbol,
    2221              :                                      gint *cursor_position)
    2222              : {
    2223            0 :     int num = 0;
    2224              : 
    2225            0 :     if (*cursor_position == 0)
    2226            0 :         return;
    2227              : 
    2228            0 :     if (!incoming_text || !symbol)
    2229            0 :         return;
    2230              : 
    2231            0 :     if (g_strrstr (incoming_text, symbol) == nullptr)
    2232            0 :         return;
    2233              : 
    2234            0 :     auto text_len = g_utf8_strlen (incoming_text, -1);
    2235              : 
    2236            0 :     for (int x = 0; x < text_len; x++)
    2237              :     {
    2238            0 :         auto temp = g_utf8_offset_to_pointer (incoming_text, x);
    2239              : 
    2240            0 :         if (g_str_has_prefix (temp, symbol))
    2241            0 :             num++;
    2242              : 
    2243            0 :         if (g_strrstr (temp, symbol) == nullptr)
    2244            0 :             break;
    2245              :     }
    2246            0 :     *cursor_position = *cursor_position - (num * g_utf8_strlen (symbol, -1));
    2247              : }
    2248              : 
    2249              : char*
    2250            0 : gnc_filter_text_for_currency_symbol (const char* incoming_text,
    2251              :                                      const char* symbol)
    2252              : {
    2253            0 :     if (!incoming_text)
    2254            0 :         return nullptr;
    2255              : 
    2256            0 :     if (!symbol)
    2257            0 :        return g_strdup (incoming_text);
    2258              : 
    2259            0 :     if (g_strrstr (incoming_text, symbol) == nullptr)
    2260            0 :         return g_strdup (incoming_text);
    2261              : 
    2262            0 :     auto split = g_strsplit (incoming_text, symbol, -1);
    2263              : 
    2264            0 :     auto ret_text = g_strjoinv (nullptr, split);
    2265              : 
    2266            0 :     g_strfreev (split);
    2267            0 :     return ret_text;
    2268              : }
    2269              : 
    2270              : char*
    2271            0 : gnc_filter_text_for_currency_commodity (const gnc_commodity *comm,
    2272              :                                         const char* incoming_text,
    2273              :                                         const char** symbol)
    2274              : {
    2275            0 :     if (!incoming_text)
    2276              :     {
    2277            0 :         *symbol = nullptr;
    2278            0 :         return nullptr;
    2279              :     }
    2280              : 
    2281            0 :     if (!gnc_commodity_is_currency (comm))
    2282              :     {
    2283            0 :         *symbol = nullptr;
    2284            0 :         return g_strdup (incoming_text);
    2285              :     }
    2286              : 
    2287            0 :     if (comm)
    2288            0 :         *symbol = gnc_commodity_get_nice_symbol (comm);
    2289              :     else
    2290            0 :         *symbol = gnc_commodity_get_nice_symbol (gnc_default_currency ());
    2291              : 
    2292            0 :     return gnc_filter_text_for_currency_symbol (incoming_text, *symbol);
    2293              : }
    2294              : 
    2295              : gchar*
    2296            0 : gnc_list_formatter (GList *strings)
    2297              : {
    2298            0 :     g_return_val_if_fail (strings, nullptr);
    2299              : 
    2300            0 :     UErrorCode status = U_ZERO_ERROR;
    2301            0 :     auto formatter = icu::ListFormatter::createInstance(status);
    2302            0 :     std::vector<UniStr> strvec;
    2303            0 :     UniStr result;
    2304            0 :     std::string retval;
    2305              : 
    2306            0 :     for (auto n = strings; n; n = g_list_next (n))
    2307              :     {
    2308            0 :         auto utf8_str{static_cast<const char*>(n->data)};
    2309            0 :         strvec.push_back (UniStr::fromUTF8(utf8_str));
    2310              :     }
    2311              : 
    2312            0 :     formatter->format (strvec.data(), strvec.size(), result, status);
    2313              : 
    2314            0 :     if (U_FAILURE(status))
    2315            0 :         PERR ("Unicode error");
    2316              :     else
    2317            0 :         result.toUTF8String(retval);
    2318              : 
    2319            0 :     delete formatter;
    2320            0 :     return g_strdup (retval.c_str());
    2321            0 : }
        

Generated by: LCOV version 2.0-1