Line data Source code
1 : /********************************************************************\
2 : * gnc-option-impl.cpp -- Application options system *
3 : * Copyright (C) 2019 John Ralls <jralls@ceridwen.us> *
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 :
24 : #include "gnc-option-impl.hpp"
25 : #include "gnc-datetime.hpp"
26 : #include "gnc-option-uitype.hpp"
27 : #include "guid.hpp"
28 : #include <cassert>
29 : #include <sstream>
30 : #include <numeric>
31 :
32 : #include "gnc-accounting-period.h"
33 : #include "gnc-session.h"
34 : #include "gncOwner.h"
35 :
36 : static const QofLogModule log_module{"gnc.options"};
37 :
38 : const std::string GncOptionMultichoiceValue::c_empty_string{""};
39 : const std::string GncOptionMultichoiceValue::c_list_string{"multiple values"};
40 :
41 : static inline GncOwnerType
42 0 : ui_type_to_owner_type(GncOptionUIType ui_type)
43 : {
44 0 : if (ui_type == GncOptionUIType::CUSTOMER)
45 0 : return GNC_OWNER_CUSTOMER;
46 0 : if (ui_type == GncOptionUIType::VENDOR)
47 0 : return GNC_OWNER_VENDOR;
48 0 : if (ui_type == GncOptionUIType::EMPLOYEE)
49 0 : return GNC_OWNER_EMPLOYEE;
50 0 : if (ui_type == GncOptionUIType::JOB)
51 0 : return GNC_OWNER_JOB;
52 0 : return GNC_OWNER_NONE;
53 : }
54 :
55 : static GncOwner*
56 63 : make_owner_ptr(const GncOwner* owner)
57 : {
58 63 : if (!owner)
59 60 : return nullptr;
60 3 : auto rv{gncOwnerNew()};
61 3 : gncOwnerCopy(owner, rv);
62 3 : return rv;
63 : }
64 :
65 15 : GncOptionGncOwnerValue::GncOptionGncOwnerValue(
66 : const char* section, const char* name,
67 : const char* key, const char* doc_string,
68 15 : const GncOwner* value, GncOptionUIType ui_type) :
69 0 : OptionClassifier{section, name, key, doc_string},
70 15 : m_ui_type(ui_type), m_value{make_owner_ptr(value)},
71 120 : m_default_value{make_owner_ptr(value)} {}
72 :
73 15 : GncOptionGncOwnerValue::GncOptionGncOwnerValue(const GncOptionGncOwnerValue& from) :
74 45 : OptionClassifier{from.m_section, from.m_name, from.m_sort_tag,
75 15 : from.m_doc_string},
76 15 : m_ui_type(from.get_ui_type()), m_value{make_owner_ptr(from.get_value())},
77 30 : m_default_value{make_owner_ptr(from.get_default_value())} {}
78 :
79 : void
80 3 : GncOptionGncOwnerValue::set_value(const GncOwner* new_value)
81 : {
82 3 : m_value.reset(make_owner_ptr(new_value));
83 3 : m_dirty = true;
84 3 : }
85 :
86 : void
87 0 : GncOptionGncOwnerValue::set_default_value(const GncOwner *new_value)
88 : {
89 0 : m_value.reset(make_owner_ptr(new_value));
90 0 : m_default_value.reset(make_owner_ptr(new_value));
91 0 : }
92 :
93 : const GncOwner*
94 52 : GncOptionGncOwnerValue::get_value() const
95 : {
96 52 : return m_value.get();
97 : }
98 :
99 : const GncOwner*
100 17 : GncOptionGncOwnerValue::get_default_value() const
101 : {
102 17 : return m_default_value.get();
103 : }
104 :
105 : void
106 0 : GncOptionGncOwnerValue::reset_default_value()
107 : {
108 0 : gncOwnerCopy(m_default_value.get(), m_value.get());
109 0 : }
110 :
111 : bool
112 0 : GncOptionGncOwnerValue::is_changed() const noexcept
113 : {
114 0 : return gncOwnerEqual(m_value.get(), m_default_value.get());
115 : }
116 :
117 : bool
118 0 : GncOptionGncOwnerValue::deserialize(const std::string& str) noexcept
119 : {
120 : try {
121 0 : auto guid{static_cast<GncGUID>(gnc::GUID::from_string(str))};
122 0 : auto inst = qof_instance_from_guid(&guid, m_ui_type);
123 0 : if (inst)
124 : {
125 0 : GncOwner owner{};
126 0 : owner.type = ui_type_to_owner_type(m_ui_type);
127 0 : owner.owner.undefined = inst;
128 0 : set_default_value(&owner);
129 0 : return true;
130 : }
131 : }
132 0 : catch (const gnc::guid_syntax_exception& err)
133 : {
134 0 : PWARN("Failed to convert %s to a GUID", str.c_str());
135 0 : }
136 0 : return false;
137 : }
138 :
139 : std::string
140 0 : GncOptionGncOwnerValue::serialize() const noexcept
141 : {
142 :
143 0 : auto owner{m_value.get()};
144 0 : gnc::GUID guid{*qof_instance_get_guid(static_cast<QofInstance*>(owner->owner.undefined))};
145 0 : std::string retval{guid.to_string()};
146 :
147 0 : return retval;
148 : }
149 :
150 : static GncItem
151 109 : make_gnc_item(const QofInstance* inst)
152 : {
153 109 : if (!inst)
154 64 : return std::make_pair<QofIdTypeConst, GncGUID>("", guid_new_return());
155 45 : auto type{qof_collection_get_type(qof_instance_get_collection(inst))};
156 45 : auto guid{qof_instance_get_guid(inst)};
157 90 : return std::make_pair(std::move(type), std::move(*const_cast<GncGUID*>(guid)));
158 : }
159 :
160 : static inline QofBook*
161 422 : get_current_book(void)
162 : {
163 422 : return qof_session_get_book(gnc_get_current_session());
164 : }
165 :
166 : static inline Account*
167 27 : get_current_root_account(void)
168 : {
169 27 : return gnc_book_get_root_account(get_current_book());
170 : }
171 :
172 : static const QofInstance*
173 130 : qof_instance_from_gnc_item(const GncItem& item)
174 : {
175 130 : auto [type, guid] = item;
176 130 : auto book{get_current_book()};
177 130 : auto coll{qof_book_get_collection(book, type)};
178 260 : return static_cast<QofInstance*>(qof_collection_lookup_entity(coll, &guid));
179 : }
180 :
181 39 : GncOptionQofInstanceValue::GncOptionQofInstanceValue(
182 : const char* section, const char* name,
183 : const char* key, const char* doc_string,
184 39 : const QofInstance* value, GncOptionUIType ui_type) :
185 0 : OptionClassifier{section, name, key, doc_string},
186 39 : m_ui_type(ui_type), m_value{},
187 312 : m_default_value{} {
188 39 : m_value = make_gnc_item(value);
189 39 : m_default_value = make_gnc_item(value);
190 39 : }
191 :
192 39 : GncOptionQofInstanceValue::GncOptionQofInstanceValue(const GncOptionQofInstanceValue& from) :
193 117 : OptionClassifier{from.m_section, from.m_name, from.m_sort_tag,
194 39 : from.m_doc_string},
195 39 : m_ui_type(from.get_ui_type()), m_value{from.get_item()},
196 78 : m_default_value{from.get_default_item()}
197 : {
198 39 : }
199 : void
200 30 : GncOptionQofInstanceValue::set_value(const QofInstance* new_value)
201 : {
202 30 : m_value = make_gnc_item(new_value);
203 30 : m_dirty = true;
204 30 : }
205 :
206 : void
207 1 : GncOptionQofInstanceValue::set_default_value(const QofInstance *new_value)
208 : {
209 1 : m_value = m_default_value = make_gnc_item(new_value);
210 :
211 1 : }
212 :
213 : const QofInstance*
214 126 : GncOptionQofInstanceValue::get_value() const
215 : {
216 126 : return qof_instance_from_gnc_item(m_value);
217 : }
218 :
219 : const QofInstance*
220 4 : GncOptionQofInstanceValue::get_default_value() const
221 : {
222 4 : return qof_instance_from_gnc_item(m_default_value);
223 : }
224 :
225 : void
226 0 : GncOptionQofInstanceValue::reset_default_value()
227 : {
228 0 : m_value = m_default_value;
229 0 : }
230 :
231 : bool
232 0 : GncOptionQofInstanceValue::is_changed() const noexcept
233 : {
234 0 : return m_value != m_default_value;
235 : }
236 :
237 : bool
238 0 : GncOptionQofInstanceValue::deserialize(const std::string& str) noexcept
239 : {
240 0 : QofInstance* inst{};
241 : // Commodities are often serialized as Namespace::Mnemonic or just Mnemonic
242 : try {
243 0 : auto guid{static_cast<GncGUID>(gnc::GUID::from_string(str))};
244 0 : inst = qof_instance_from_guid(&guid, m_ui_type);
245 0 : if (inst)
246 : {
247 0 : m_value = make_gnc_item(inst);
248 0 : return true;
249 : }
250 : }
251 0 : catch (const gnc::guid_syntax_exception& err)
252 : {
253 0 : PWARN("Failed to convert %s to a GUID", str.c_str());
254 0 : }
255 0 : return false;
256 : }
257 :
258 : std::string
259 4 : GncOptionQofInstanceValue::serialize() const noexcept
260 : {
261 4 : auto inst{get_value()};
262 4 : std::string retval;
263 4 : if (GNC_IS_COMMODITY(inst))
264 : {
265 1 : auto commodity{GNC_COMMODITY(inst)};
266 1 : if (!gnc_commodity_is_currency(commodity))
267 : {
268 1 : auto name_space{gnc_commodity_get_namespace(GNC_COMMODITY(inst))};
269 1 : if (name_space && *name_space != '\0')
270 : {
271 1 : retval = name_space;
272 1 : retval += ":";
273 : }
274 : }
275 1 : retval += gnc_commodity_get_mnemonic(GNC_COMMODITY(inst));
276 1 : return retval;
277 : }
278 : else
279 : {
280 3 : gnc::GUID guid{m_value.second};
281 3 : retval = guid.to_string();
282 : }
283 3 : return retval;
284 : }
285 :
286 : static gnc_commodity*
287 178 : gnc_commodity_from_namespace_and_mnemonic(std::string_view name_space,
288 : std::string_view mnemonic)
289 : {
290 178 : auto book{get_current_book()};
291 178 : auto table = gnc_commodity_table_get_table(book);
292 178 : return gnc_commodity_table_lookup(table, name_space.data(),
293 178 : mnemonic.data());
294 : }
295 :
296 : gnc_commodity*
297 171 : GncOptionCommodityValue::get_value() const
298 : {
299 171 : return gnc_commodity_from_namespace_and_mnemonic(m_namespace, m_mnemonic);
300 : }
301 :
302 : gnc_commodity*
303 4 : GncOptionCommodityValue::get_default_value() const
304 : {
305 4 : return gnc_commodity_from_namespace_and_mnemonic(m_default_namespace,
306 4 : m_default_mnemonic);
307 : }
308 :
309 : void
310 24 : GncOptionCommodityValue::set_value(gnc_commodity* value)
311 : {
312 24 : if (!validate(value))
313 1 : throw std::invalid_argument("Value not a currency when required or not a commodity. Value not set.");
314 23 : m_mnemonic = gnc_commodity_get_mnemonic(value);
315 23 : m_namespace = gnc_commodity_get_namespace(value);
316 23 : m_dirty = true;
317 23 : }
318 :
319 : void
320 0 : GncOptionCommodityValue::set_default_value(gnc_commodity* value)
321 : {
322 0 : if (!validate(value))
323 0 : throw std::invalid_argument("Value not a currency when required or not a commodity. Value not set.");
324 0 : m_mnemonic = m_default_mnemonic = gnc_commodity_get_mnemonic(value);
325 0 : m_namespace = m_default_namespace = gnc_commodity_get_namespace(value);
326 0 : }
327 :
328 : void
329 0 : GncOptionCommodityValue::reset_default_value()
330 : {
331 0 : m_mnemonic = m_default_mnemonic;
332 0 : m_namespace = m_default_namespace;
333 0 : }
334 :
335 : bool
336 0 : GncOptionCommodityValue::is_changed() const noexcept
337 : {
338 0 : return m_namespace != m_default_namespace || m_mnemonic != m_default_mnemonic;
339 : }
340 :
341 : bool
342 173 : GncOptionCommodityValue::validate(gnc_commodity* comm) const noexcept
343 : {
344 173 : if (!GNC_IS_COMMODITY(comm))
345 0 : return false;
346 173 : if (m_is_currency && !gnc_commodity_is_currency(comm))
347 4 : return false;
348 169 : return true;
349 : }
350 :
351 : std::string
352 3 : GncOptionCommodityValue::serialize() const noexcept
353 : {
354 3 : if (m_is_currency)
355 2 : return m_mnemonic;
356 : else
357 1 : return m_namespace + ":" + m_mnemonic;
358 : }
359 :
360 : bool
361 3 : GncOptionCommodityValue::deserialize(const std::string& str) noexcept
362 : {
363 3 : auto sep{str.find(":")};
364 3 : gnc_commodity* comm{};
365 3 : std::string mnemonic, name_space;
366 3 : if (sep != std::string::npos)
367 : {
368 2 : name_space = str.substr(0, sep);
369 2 : mnemonic = str.substr(sep + 1, -1);
370 : }
371 : else
372 : {
373 1 : name_space = "CURRENCY";
374 1 : mnemonic = str;
375 : }
376 3 : comm = gnc_commodity_from_namespace_and_mnemonic(name_space, mnemonic);
377 3 : if (!validate(comm))
378 1 : return false;
379 2 : m_namespace = std::move(name_space);
380 2 : m_mnemonic = std::move(mnemonic);
381 2 : return true;
382 3 : }
383 :
384 : bool
385 164 : GncOptionAccountListValue::validate(const GncOptionAccountList& values) const
386 : {
387 164 : if (values.empty())
388 24 : return true;
389 141 : if ((get_ui_type() == GncOptionUIType::ACCOUNT_SEL || !m_multiselect) &&
390 1 : values.size() != 1)
391 : {
392 1 : PWARN("GncOptionAccountListValue::validate: Multiple values for a non-multiselect option.");
393 1 : return false;
394 : }
395 139 : if (m_allowed.empty())
396 70 : return true;
397 69 : auto book{get_current_book()};
398 244 : for(auto& guid : values)
399 : {
400 124 : if (std::find(m_allowed.begin(), m_allowed.end(),
401 248 : xaccAccountGetType(xaccAccountLookup(&guid, book))) == m_allowed.end())
402 : {
403 18 : PWARN("GncOptionAccountListValue::validate: Account %s is not of an allowed type", gnc::GUID(guid).to_string().c_str());
404 18 : return false; }
405 : }
406 51 : return true;
407 : }
408 :
409 : GncOptionAccountList
410 350 : GncOptionAccountListValue::get_value() const
411 : {
412 350 : return !m_value.empty() ? m_value : get_default_value();
413 : }
414 :
415 : GncOptionAccountList
416 118 : GncOptionAccountListValue::get_default_value() const
417 : {
418 118 : if (!m_default_value.empty())
419 4 : return m_default_value;
420 :
421 : /* If no default has been set and there's an allowed set then find the first
422 : * account that matches one of the allowed account types.
423 : */
424 114 : GncOptionAccountList retval{};
425 114 : if (m_allowed.empty())
426 95 : return retval;
427 :
428 19 : auto root{get_current_root_account()};
429 19 : auto account_list{gnc_account_get_descendants_sorted(root)};
430 19 : if (!account_list)
431 16 : return retval;
432 :
433 45 : for (auto node = account_list; node; node = g_list_next (node))
434 : {
435 23 : if (std::find(m_allowed.begin(), m_allowed.end(),
436 46 : xaccAccountGetType(GNC_ACCOUNT(node->data))) != m_allowed.end())
437 : {
438 2 : retval.push_back(*qof_entity_get_guid(GNC_ACCOUNT(node->data)));
439 2 : break;
440 : }
441 : }
442 3 : g_list_free(account_list);
443 3 : return retval;
444 114 : }
445 :
446 : bool
447 0 : GncOptionAccountListValue::is_changed() const noexcept
448 : {
449 0 : return m_value != m_default_value;
450 : }
451 :
452 :
453 :
454 : /**
455 : * Create a GList of account types to pass to gnc_account_sel_set_acct_filters.
456 : * gnc_account_sel_set_acct_filters copies the list so the intermediary caller
457 : * is responsible for freeing the list.
458 : *
459 : * @return an allocated GList* or nullptr if the list is empty.
460 : */
461 : GList*
462 0 : GncOptionAccountListValue::account_type_list() const noexcept
463 : {
464 0 : if (m_allowed.empty())
465 0 : return nullptr;
466 0 : GList* retval{nullptr};
467 0 : for (auto type : m_allowed)
468 0 : retval = g_list_prepend(retval, GINT_TO_POINTER(type));
469 0 : return g_list_reverse(retval);
470 : }
471 :
472 : bool
473 15 : GncOptionAccountSelValue::validate(const Account* value) const
474 : {
475 15 : if (m_allowed.empty() || !value)
476 12 : return true;
477 3 : if (std::find(m_allowed.begin(), m_allowed.end(),
478 6 : xaccAccountGetType(value)) == m_allowed.end())
479 0 : return false;
480 3 : return true;
481 : }
482 :
483 : const Account*
484 13 : GncOptionAccountSelValue::get_value() const
485 : {
486 13 : auto book{get_current_book()};
487 13 : return guid_equal(guid_null(), &m_value) ? get_default_value() :
488 13 : xaccAccountLookup(&m_value, book);
489 : }
490 :
491 : const Account*
492 11 : GncOptionAccountSelValue::get_default_value() const
493 : {
494 :
495 11 : if (!guid_equal(guid_null(), &m_default_value))
496 : {
497 0 : auto book{get_current_book()};
498 0 : return xaccAccountLookup(&m_default_value, book);
499 : }
500 :
501 : /* If no default has been set and there's an allowed set then find the first
502 : * account that matches one of the allowed account types.
503 : */
504 11 : if (m_allowed.empty())
505 3 : return nullptr;
506 :
507 8 : const Account* retval{nullptr};
508 8 : auto root{get_current_root_account()};
509 8 : auto account_list{gnc_account_get_descendants_sorted(root)};
510 8 : if (!account_list)
511 5 : return nullptr;
512 :
513 41 : for (auto node = account_list; node; node = g_list_next (node))
514 22 : if (std::find(m_allowed.begin(), m_allowed.end(),
515 44 : xaccAccountGetType(GNC_ACCOUNT(node->data))) != m_allowed.end())
516 : {
517 3 : retval = GNC_ACCOUNT(node->data);
518 3 : break;
519 : }
520 3 : g_list_free(account_list);
521 3 : return retval;
522 : }
523 :
524 :
525 : /**
526 : * Create a GList of account types to pass to gnc_account_sel_set_acct_filters.
527 : * gnc_account_sel_set_acct_filters copies the list so the intermediary caller
528 : * is responsible for freeing the list.
529 : *
530 : * @return an allocated GList* or nullptr if the list is empty.
531 : */
532 : GList*
533 0 : GncOptionAccountSelValue::account_type_list() const noexcept
534 : {
535 0 : if (m_allowed.empty())
536 0 : return nullptr;
537 0 : GList* retval{nullptr};
538 0 : for (auto type : m_allowed)
539 0 : retval = g_list_prepend(retval, GINT_TO_POINTER(type));
540 0 : return g_list_reverse(retval);
541 : }
542 :
543 : bool
544 174 : GncOptionDateValue::validate(RelativeDatePeriod value) {
545 174 : if (m_period_set.empty())
546 27 : return true; // No restrictions
547 147 : if (std::find(m_period_set.begin(), m_period_set.end(),
548 294 : value) != m_period_set.end())
549 145 : return true;
550 2 : return false;
551 : }
552 :
553 : time64
554 238 : GncOptionDateValue::get_value() const noexcept
555 : {
556 238 : if (m_period == RelativeDatePeriod::ABSOLUTE)
557 225 : return m_date;
558 13 : return gnc_relative_date_to_time64(m_period);
559 : }
560 :
561 : time64
562 0 : GncOptionDateValue::get_default_value() const noexcept
563 : {
564 0 : if (m_default_period == RelativeDatePeriod::ABSOLUTE)
565 0 : return m_default_date;
566 0 : return gnc_relative_date_to_time64(m_default_period);
567 : }
568 :
569 : /* Use asserts for pre- and post-conditions to deliberately crash if they're not
570 : * met as the program design should prevent that from happening.
571 : */
572 : uint16_t
573 4 : GncOptionDateValue::get_period_index() const noexcept
574 : {
575 4 : assert (m_period != RelativeDatePeriod::ABSOLUTE);
576 4 : assert(!m_period_set.empty());
577 4 : auto item{std::find(m_period_set.begin(), m_period_set.end(), m_period)};
578 8 : assert(item != m_period_set.end());
579 8 : return item - m_period_set.begin();
580 : }
581 :
582 : uint16_t
583 0 : GncOptionDateValue::get_default_period_index() const noexcept
584 : {
585 0 : assert(m_period != RelativeDatePeriod::ABSOLUTE);
586 0 : assert(!m_period_set.empty());
587 0 : auto item{std::find(m_period_set.begin(), m_period_set.end(),
588 0 : m_default_period)};
589 0 : assert (item != m_period_set.end());
590 0 : return item - m_period_set.begin();
591 : }
592 :
593 : void
594 2 : GncOptionDateValue::set_value(uint16_t index) noexcept
595 : {
596 2 : assert(!m_period_set.empty());
597 2 : assert(index < m_period_set.size());
598 2 : m_date = INT64_MAX;
599 2 : m_period = m_period_set[index];
600 2 : m_dirty = true;
601 2 : }
602 :
603 : uint16_t
604 0 : GncOptionDateValue::permissible_value_index(const char* key) const noexcept
605 : {
606 0 : auto index = std::find_if(m_period_set.begin(), m_period_set.end(),
607 0 : [key](auto period) -> bool {
608 0 : return strcmp(gnc_relative_date_display_string(period),
609 0 : key) == 0;
610 : });
611 0 : return index != m_period_set.end() ? index - m_period_set.begin() : 0;
612 : }
613 :
614 : static const char* date_type_str[] {"absolute", "relative"};
615 :
616 : std::ostream&
617 0 : GncOptionDateValue::out_stream(std::ostream& oss) const noexcept
618 : {
619 0 : if (m_period == RelativeDatePeriod::ABSOLUTE)
620 0 : oss << date_type_str[0] << " . " << m_date;
621 : else
622 : oss << date_type_str[1] << " . " <<
623 0 : gnc_relative_date_storage_string(m_period);
624 0 : return oss;
625 : }
626 :
627 : std::istream&
628 13 : GncOptionDateValue::in_stream(std::istream& iss)
629 : {
630 : char type_str[10]; //The length of both "absolute" and "relative" plus 1.
631 13 : iss.getline(type_str, sizeof(type_str), '.');
632 13 : if(!iss)
633 0 : throw std::invalid_argument("Date Type separator missing");
634 : /* strcmp is safe, istream::getline null terminates the buffer. */
635 13 : if (strcmp(type_str, "absolute ") == 0)
636 : {
637 : time64 time;
638 1 : iss >> time;
639 1 : set_value(time);
640 1 : if (iss.get() != ')')
641 1 : iss.unget();
642 : }
643 12 : else if (strcmp(type_str, "relative ") == 0)
644 : {
645 12 : std::string period_str;
646 12 : iss >> period_str;
647 12 : if (period_str.back() == ')')
648 0 : period_str.pop_back();
649 12 : auto period = gnc_relative_date_from_storage_string(period_str.c_str());
650 12 : if (period == RelativeDatePeriod::ABSOLUTE)
651 : {
652 0 : std::string err{"Unknown period string in date option: '"};
653 0 : err += period_str;
654 0 : err += "'";
655 0 : throw std::invalid_argument(err);
656 0 : }
657 :
658 12 : set_value(period);
659 12 : }
660 : else
661 : {
662 0 : std::string err{"Unknown date type string in date option: '"};
663 0 : err += type_str;
664 0 : err += "'";
665 0 : throw std::invalid_argument{err};
666 0 : }
667 13 : return iss;
668 : }
669 :
670 : QofInstance*
671 5 : qof_instance_from_guid(GncGUID* guid, GncOptionUIType type)
672 : {
673 : QofIdType qof_type;
674 5 : switch(type)
675 : {
676 1 : case GncOptionUIType::BUDGET:
677 1 : qof_type = "Budget";
678 1 : break;
679 0 : case GncOptionUIType::JOB:
680 0 : qof_type = "gncJob";
681 0 : break;
682 0 : case GncOptionUIType::CUSTOMER:
683 0 : qof_type = "gncCustomer";
684 0 : break;
685 0 : case GncOptionUIType::VENDOR:
686 0 : qof_type = "gncVendor";
687 0 : break;
688 0 : case GncOptionUIType::EMPLOYEE:
689 0 : qof_type = "gncEmployee";
690 0 : break;
691 0 : case GncOptionUIType::INVOICE:
692 0 : qof_type = "gncInvoice";
693 0 : break;
694 0 : case GncOptionUIType::TAX_TABLE:
695 0 : qof_type = "gncTaxTable";
696 0 : break;
697 4 : case GncOptionUIType::ACCOUNT_LIST:
698 : case GncOptionUIType::ACCOUNT_SEL:
699 : default:
700 4 : qof_type = "Account";
701 4 : break;
702 : }
703 5 : auto book{get_current_book()};
704 5 : auto col{qof_book_get_collection(book, qof_type)};
705 5 : return QOF_INSTANCE(qof_collection_lookup_entity(col, guid));
706 : }
707 :
708 : QofInstance*
709 5 : qof_instance_from_string(const std::string& str, GncOptionUIType type)
710 : {
711 5 : QofInstance* retval{nullptr};
712 : try {
713 5 : auto guid{static_cast<GncGUID>(gnc::GUID::from_string(str))};
714 5 : retval = qof_instance_from_guid(&guid, type);
715 : }
716 0 : catch (const gnc::guid_syntax_exception& err)
717 : {
718 0 : PWARN("Failed to convert %s to a GUID", str.c_str());
719 0 : }
720 5 : return retval;
721 : }
722 :
723 : std::string
724 1 : qof_instance_to_string(const QofInstance* inst)
725 : {
726 1 : std::string retval;
727 1 : gnc::GUID guid{*qof_instance_get_guid(inst)};
728 1 : retval = guid.to_string();
729 1 : return retval;
730 0 : }
731 :
732 : template <typename ValueType> void
733 1315 : GncOptionValue<ValueType>::set_value(ValueType new_value)
734 : {
735 1315 : m_value = new_value;
736 1315 : m_dirty = true;
737 1315 : }
738 :
739 : template <typename ValueType> void
740 9 : GncOptionValue<ValueType>::set_default_value(ValueType new_value)
741 : {
742 9 : m_value = m_default_value = new_value;
743 9 : }
744 :
745 : template <typename ValueType> void
746 0 : GncOptionValue<ValueType>::reset_default_value()
747 : {
748 0 : m_value = m_default_value;
749 0 : }
750 :
751 : /* Missing on purpose: QofQuery because for current usage it's serialized with
752 : * gnc_query2scm. The future is to replace QofQuery with SQL queries so there's
753 : * not much point to spending the time to create a std::string serialization for
754 : * them.
755 : */
756 : template <typename ValueType> std::string
757 12 : GncOptionValue<ValueType>::serialize() const noexcept
758 : {
759 20 : static const std::string no_value{"No Value"};
760 : if constexpr(std::is_same_v<ValueType, const QofInstance*>)
761 : return m_value ? qof_instance_to_string(m_value) : no_value;
762 : if constexpr(std::is_same_v<ValueType, const GncOwner*>)
763 : {
764 0 : if (!m_value)
765 0 : return no_value;
766 0 : auto guid{qof_instance_to_string(qofOwnerGetOwner(m_value))};
767 0 : auto type{qofOwnerGetType(m_value)};
768 0 : std::ostringstream ostr{};
769 0 : ostr << type << " " << guid;
770 0 : return ostr.str();
771 0 : }
772 : if constexpr(std::is_same_v<ValueType, GncOptionReportPlacementVec>)
773 : {
774 0 : std::ostringstream ostr{};
775 0 : ostr << "'(";
776 0 : std::for_each(m_value.begin(), m_value.end(),
777 0 : [&ostr](auto rp){
778 0 : auto [id, wide, high] = rp;
779 0 : ostr << "(" << id << " " << wide << " " << high << " #f) ";
780 : });
781 0 : ostr << ")";
782 0 : return ostr.str();
783 0 : }
784 : else if constexpr(is_same_decayed_v<ValueType, std::string>)
785 9 : return m_value;
786 : else if constexpr(is_same_decayed_v<ValueType, bool>)
787 4 : return m_value ? "True" : "False";
788 : else if constexpr(std::is_arithmetic_v<ValueType>)
789 1 : return std::to_string(m_value);
790 : else
791 0 : return "Serialization not implemented";
792 : }
793 :
794 : template <typename ValueType> bool
795 0 : GncOptionValue<ValueType>::deserialize(const std::string& str) noexcept
796 : {
797 : if constexpr(std::is_same_v<ValueType, const QofInstance*>)
798 : set_value(qof_instance_from_string(str, get_ui_type()));
799 : if constexpr(std::is_same_v<ValueType, const GncOwner*>)
800 : {
801 0 : std::istringstream istr{str};
802 0 : std::string type, guid;
803 0 : istr >> type >> guid;
804 0 : auto inst{qof_instance_from_string(guid, get_ui_type())};
805 0 : qofOwnerSetEntity(const_cast<GncOwner*>(m_value), inst);
806 0 : }
807 : if constexpr(std::is_same_v<ValueType, GncOptionReportPlacementVec>)
808 : {
809 0 : std::istringstream istr{str};
810 0 : GncOptionReportPlacementVec rpv;
811 0 : while (istr)
812 : {
813 : uint32_t id, wide, high;
814 0 : istr >> id >> wide >> high;
815 0 : rpv.emplace_back(id, wide, high);
816 : }
817 0 : set_value(rpv);
818 0 : }
819 : else if constexpr(is_same_decayed_v<ValueType, std::string>)
820 0 : set_value(str);
821 : else if constexpr(is_same_decayed_v<ValueType, bool>)
822 0 : set_value(str == "True");
823 : else if constexpr(is_same_decayed_v<ValueType, int>)
824 0 : set_value(stoi(str));
825 : else if constexpr(is_same_decayed_v<ValueType, int64_t>)
826 0 : set_value(stoll(str));
827 : else if constexpr(is_same_decayed_v<ValueType, double>)
828 0 : set_value(stod(str));
829 : else
830 0 : return false;
831 0 : return true;
832 : }
833 :
834 : std::string
835 3 : GncOptionAccountListValue::serialize() const noexcept
836 : {
837 7 : static const std::string no_value{"No Value"};
838 3 : std::string retval;
839 3 : bool first = true;
840 3 : if (m_value.empty())
841 0 : return no_value;
842 : gchar guidstr[GUID_ENCODING_LENGTH + 1];
843 11 : for (auto val : m_value)
844 : {
845 5 : if (!first)
846 2 : retval += " ";
847 5 : first = false;
848 5 : guid_to_string_buff (&val, guidstr);
849 5 : retval += guidstr;
850 : }
851 3 : return retval;
852 3 : }
853 :
854 : bool
855 0 : GncOptionAccountListValue::deserialize(const std::string& str) noexcept
856 : {
857 0 : if (str.empty() || str.size() < GUID_ENCODING_LENGTH)
858 0 : return false;
859 0 : m_value.clear();
860 0 : m_value.reserve(str.size() / GUID_ENCODING_LENGTH);
861 0 : bool first = true;
862 0 : size_t pos{};
863 0 : while (pos + GUID_ENCODING_LENGTH < str.size())
864 : {
865 0 : if (!first)
866 0 : ++pos;
867 0 : first = false;
868 0 : GncGUID guid{};
869 0 : string_to_guid(str.substr(pos, pos + GUID_ENCODING_LENGTH).c_str(), &guid);
870 0 : m_value.push_back(guid);
871 0 : pos += GUID_ENCODING_LENGTH;
872 : }
873 0 : return true;
874 : }
875 :
876 : std::string
877 2 : GncOptionAccountSelValue::serialize() const noexcept
878 : {
879 4 : static const std::string no_value{"No Value"};
880 2 : if (guid_equal(guid_null(), &m_value))
881 0 : return no_value;
882 :
883 : gchar strbuff[GUID_ENCODING_LENGTH + 1];
884 2 : guid_to_string_buff (&m_value, strbuff);
885 4 : return strbuff;
886 : }
887 :
888 : bool
889 0 : GncOptionAccountSelValue::deserialize(const std::string& str) noexcept
890 : {
891 0 : set_value(reinterpret_cast<Account*>(qof_instance_from_string(str, get_ui_type())));
892 0 : return true;
893 : }
894 :
895 : std::string
896 8 : GncOptionMultichoiceValue::serialize() const noexcept
897 : {
898 12 : static const std::string no_value{""};
899 8 : std::string retval;
900 8 : bool first = true;
901 8 : bool list_context = m_ui_type == GncOptionUIType::LIST;
902 8 : if (m_value.empty())
903 0 : return no_value;
904 :
905 8 : if (list_context)
906 2 : retval += '(';
907 25 : for (auto index : m_value)
908 : {
909 9 : if (!first)
910 1 : retval += " ";
911 9 : first = false;
912 9 : retval += std::get<0>(m_choices[index]);
913 : }
914 8 : if (list_context)
915 2 : retval += ')';
916 8 : return retval;
917 8 : }
918 :
919 : bool
920 0 : GncOptionMultichoiceValue::deserialize(const std::string& str) noexcept
921 : {
922 : static const auto uint16_t_max = std::numeric_limits<uint16_t>::max();
923 0 : if (str.empty())
924 :
925 0 : return false;
926 0 : uint16_t pos{};
927 0 : while (pos < str.size())
928 : {
929 0 : auto endpos{str.find(' ', pos)};
930 0 : if (endpos == std::string::npos)
931 0 : endpos = str.size();
932 : //need a null-terminated char* to pass to permissible_value_index
933 0 : auto index{permissible_value_index(str.substr(pos, endpos).c_str())};
934 0 : if (index == uint16_t_max)
935 0 : return false;
936 0 : m_value.push_back(index);
937 0 : pos = endpos + 1;
938 : }
939 0 : return true;
940 : }
941 :
942 : template <typename ValueType> std::string
943 4 : GncOptionRangeValue<ValueType>::serialize() const noexcept
944 : {
945 : if constexpr (std::is_arithmetic_v<ValueType>)
946 : {
947 4 : std::ostringstream ostr;
948 : if constexpr(is_same_decayed_v<ValueType, double>)
949 2 : ostr << std::showpoint << std::fixed;
950 4 : ostr << m_value;
951 4 : return ostr.str();
952 4 : }
953 : return "";
954 : }
955 :
956 : template <typename ValueType> bool
957 0 : GncOptionRangeValue<ValueType>::deserialize(const std::string& str) noexcept
958 : {
959 : if constexpr(is_same_decayed_v<ValueType, int>)
960 0 : set_value(stoi(str));
961 : else if constexpr(is_same_decayed_v<ValueType, double>)
962 0 : set_value(stod(str));
963 0 : return true;
964 : }
965 :
966 : std::string
967 20 : GncOptionDateValue::serialize() const noexcept
968 : {
969 20 : std::string retval{"("};
970 20 : if (m_period == RelativeDatePeriod::ABSOLUTE)
971 : {
972 3 : retval += date_type_str[0];
973 3 : retval += " . ";
974 3 : retval += std::to_string(m_date);
975 : }
976 : else
977 : {
978 17 : retval += date_type_str[1];
979 17 : retval += " . ";
980 17 : retval += gnc_relative_date_storage_string(m_period);
981 : }
982 20 : retval += ")";
983 20 : return retval;
984 : }
985 :
986 : bool
987 0 : GncOptionDateValue::deserialize(const std::string& str) noexcept
988 : {
989 : //The length of both "absolute" and "relative".
990 : static constexpr size_t date_type_len{9};
991 : // date_type_len plus the length of " . ".
992 : static constexpr size_t date_value_pos{12};
993 0 : auto type_str{str.substr(0, date_type_len)};
994 0 : auto period_str{str.substr(date_value_pos)};
995 0 : if (type_str == "absolute")
996 : {
997 : // Need a cast to disambiguate from time64.
998 0 : set_value(static_cast<uint16_t>(std::stoll(period_str)));
999 0 : return true;
1000 : }
1001 0 : else if (type_str == "relative ")
1002 : {
1003 0 : auto period = gnc_relative_date_from_storage_string(period_str.c_str());
1004 0 : if (period == RelativeDatePeriod::ABSOLUTE)
1005 : {
1006 0 : PWARN("Unknown period string in date option: '%s'",
1007 : period_str.c_str());
1008 0 : return false;
1009 : }
1010 :
1011 0 : set_value(period);
1012 0 : return true;
1013 : }
1014 : else
1015 : {
1016 0 : PWARN("Unknown date type string in date option: '%s'",
1017 : type_str.c_str());
1018 0 : return false;
1019 : }
1020 0 : }
1021 :
1022 : std::istream&
1023 3 : operator>> (std::istream& iss, GncOptionCommodityValue& opt)
1024 : {
1025 3 : std::string instr;
1026 3 : iss >> instr;
1027 3 : if (!opt.deserialize(instr))
1028 1 : throw std::invalid_argument("Invalid commodity string in stream.");
1029 2 : return iss;
1030 3 : }
1031 :
1032 : template void GncOptionValue<bool>::set_value(bool);
1033 : template void GncOptionValue<int>::set_value(int);
1034 : template void GncOptionValue<int64_t>::set_value(int64_t);
1035 : template void GncOptionValue<double>::set_value(double);
1036 : template void GncOptionValue<char*>::set_value(char*);
1037 : template void GncOptionValue<const char*>::set_value(const char*);
1038 : template void GncOptionValue<std::string>::set_value(std::string);
1039 : template void GncOptionValue<const QofQuery*>::set_value(const QofQuery*);
1040 : template void GncOptionValue<const GncOwner*>::set_value(const GncOwner*);
1041 : template void GncOptionValue<RelativeDatePeriod>::set_value(RelativeDatePeriod);
1042 : template void GncOptionValue<uint16_t>::set_value(uint16_t);
1043 : template void GncOptionValue<GncOptionAccountList>::set_value(GncOptionAccountList);
1044 : template void GncOptionValue<GncMultichoiceOptionIndexVec>::set_value(GncMultichoiceOptionIndexVec);
1045 : template void GncOptionValue<GncOptionReportPlacementVec>::set_value(GncOptionReportPlacementVec);
1046 : template void GncOptionValue<GncOptionDateFormat>::set_value(GncOptionDateFormat);
1047 : template void GncOptionValue<bool>::set_default_value(bool);
1048 : template void GncOptionValue<int>::set_default_value(int);
1049 : template void GncOptionValue<int64_t>::set_default_value(int64_t);
1050 : template void GncOptionValue<double>::set_default_value(double);
1051 : template void GncOptionValue<char*>::set_default_value(char*);
1052 : template void GncOptionValue<const char*>::set_default_value(const char*);
1053 : template void GncOptionValue<std::string>::set_default_value(std::string);
1054 : template void GncOptionValue<const QofQuery*>::set_default_value(const QofQuery*);
1055 : template void GncOptionValue<const GncOwner*>::set_default_value(const GncOwner*);
1056 : template void GncOptionValue<RelativeDatePeriod>::set_default_value(RelativeDatePeriod);
1057 : template void GncOptionValue<uint16_t>::set_default_value(uint16_t);
1058 : template void GncOptionValue<GncOptionAccountList>::set_default_value(GncOptionAccountList);
1059 : template void GncOptionValue<GncMultichoiceOptionIndexVec>::set_default_value(GncMultichoiceOptionIndexVec);
1060 : template void GncOptionValue<GncOptionReportPlacementVec>::set_default_value(GncOptionReportPlacementVec);
1061 : template void GncOptionValue<GncOptionDateFormat>::set_default_value(GncOptionDateFormat);
1062 : template void GncOptionValue<bool>::reset_default_value();
1063 : template void GncOptionValue<int>::reset_default_value();
1064 : template void GncOptionValue<int64_t>::reset_default_value();
1065 : template void GncOptionValue<double>::reset_default_value();
1066 : template void GncOptionValue<char*>::reset_default_value();
1067 : template void GncOptionValue<const char*>::reset_default_value();
1068 : template void GncOptionValue<std::string>::reset_default_value();
1069 : template void GncOptionValue<const QofQuery*>::reset_default_value();
1070 : template void GncOptionValue<const GncOwner*>::reset_default_value();
1071 : template void GncOptionValue<RelativeDatePeriod>::reset_default_value();
1072 : template void GncOptionValue<uint16_t>::reset_default_value();
1073 : template void GncOptionValue<GncOptionAccountList>::reset_default_value();
1074 : template void GncOptionValue<GncMultichoiceOptionIndexVec>::reset_default_value();
1075 : template void GncOptionValue<GncOptionReportPlacementVec>::reset_default_value();
1076 : template void GncOptionValue<GncOptionDateFormat>::reset_default_value();
1077 : template std::string GncOptionValue<bool>::serialize() const noexcept;
1078 : template std::string GncOptionValue<int>::serialize() const noexcept;
1079 : template std::string GncOptionValue<int64_t>::serialize() const noexcept;
1080 : template std::string GncOptionValue<double>::serialize() const noexcept;
1081 : template std::string GncOptionValue<char*>::serialize() const noexcept;
1082 : template std::string GncOptionValue<const char*>::serialize() const noexcept;
1083 : template std::string GncOptionValue<std::string>::serialize() const noexcept;
1084 : template std::string GncOptionValue<const QofQuery*>::serialize() const noexcept;
1085 : template std::string GncOptionValue<const GncOwner*>::serialize() const noexcept;
1086 : template std::string GncOptionValue<GncOptionReportPlacementVec>::serialize() const noexcept;
1087 : template std::string GncOptionValue<GncOptionDateFormat>::serialize() const noexcept;
1088 : template std::string GncOptionRangeValue<int>::serialize() const noexcept;
1089 : template std::string GncOptionRangeValue<double>::serialize() const noexcept;
1090 : template bool GncOptionValue<bool>::deserialize(const std::string&) noexcept;
1091 : template bool GncOptionValue<int>::deserialize(const std::string&) noexcept;
1092 : template bool GncOptionValue<int64_t>::deserialize(const std::string&) noexcept;
1093 : template bool GncOptionValue<double>::deserialize(const std::string&) noexcept;
1094 : template bool GncOptionValue<char*>::deserialize(const std::string&) noexcept;
1095 : template bool GncOptionValue<const char*>::deserialize(const std::string&) noexcept;
1096 : template bool GncOptionValue<std::string>::deserialize(const std::string&) noexcept;
1097 : template bool GncOptionValue<const QofQuery*>::deserialize(const std::string&) noexcept;
1098 : template bool GncOptionValue<const GncOwner*>::deserialize(const std::string&) noexcept;
1099 : template bool GncOptionValue<GncOptionReportPlacementVec>::deserialize(const std::string&) noexcept;
1100 : template bool GncOptionValue<GncOptionDateFormat>::deserialize(const std::string&) noexcept;
1101 : template bool GncOptionRangeValue<int>::deserialize(const std::string&) noexcept;
1102 : template bool GncOptionRangeValue<double>::deserialize(const std::string&) noexcept;
|