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