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