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