diff --git a/bindings/guile/gnc-optiondb.i b/bindings/guile/gnc-optiondb.i index 6aba978442..ce5c9433c8 100644 --- a/bindings/guile/gnc-optiondb.i +++ b/bindings/guile/gnc-optiondb.i @@ -740,6 +740,10 @@ gnc_option_test_book_destroy(QofBook* book) wrap_unique_ptr(GncOptionDBPtr, GncOptionDB); %ignore swig_get_option(GncOption&); +%ignore GncOwnerDeleter; +%ignore + GncOptionGncOwnerValue::GncOptionGncOwnerValue(GncOptionGncOwnerValue &&); + %inline %{ #include #include @@ -1151,7 +1155,6 @@ inline SCM return_scm_value(ValueType value) %template(gnc_make_bool_option) gnc_make_option; %template(gnc_make_int64_option) gnc_make_option; %template(gnc_make_query_option) gnc_make_option; -%template(gnc_make_owner_option) gnc_make_option; %rename (get_value) GncOption::get_scm_value; %rename (get_default_value) GncOption::get_scm_default_value; @@ -1247,13 +1250,12 @@ inline SCM return_scm_value(ValueType value) return scm_simple_format(SCM_BOOL_F, date_fmt, value); } - if constexpr (is_same_decayed_v>) + if constexpr (is_GncOwnerValue_v) { auto value{option.get_value()}; auto guid{scm_from_utf8_string(qof_instance_to_string(qofOwnerGetOwner(value)).c_str())}; auto type{scm_from_long(gncOwnerGetType(value))}; - return scm_simple_format(SCM_BOOL_F, ticked_format_str, + return scm_simple_format(SCM_BOOL_F, list_format_str, scm_list_1(scm_cons(type, guid))); } if constexpr (is_QofQueryValue_v) @@ -1420,6 +1422,24 @@ inline SCM return_scm_value(ValueType value) } return; } + if constexpr (is_GncOwnerValue_v) + { + if (scm_is_pair(new_value)) + { + GncOwner owner{}; + owner.type = static_cast(scm_to_int(scm_car(new_value))); + auto strval{scm_to_utf8_string(scm_cdr(new_value))}; + owner.owner.undefined = qof_instance_from_string(strval, option.get_ui_type()); + option.set_value(&owner); + } + else + { + auto val{scm_to_value(new_value)}; + option.set_value(val); + } + return; + } + if constexpr (is_QofQueryValue_v) { if (scm_is_pair(new_value)) @@ -1516,6 +1536,37 @@ inline SCM return_scm_value(ValueType value) } return; } + if constexpr (is_GncOwnerValue_v) + { + if (scm_is_pair(new_value)) + { + GncOwner owner{}; + owner.type = static_cast(scm_to_int(scm_car(new_value))); + auto strval{scm_to_utf8_string(scm_cdr(new_value))}; + owner.owner.undefined = qof_instance_from_string(strval, option.get_ui_type()); + option.set_default_value(&owner); + } + else + { + auto val{scm_to_value(new_value)}; + option.set_default_value(val); + } + return; + } + if constexpr (is_QofQueryValue_v) + { + if (scm_is_pair(new_value)) + { + auto val{gnc_scm2query(new_value)}; + option.set_default_value(val); + } + else + { + auto val{scm_to_value(new_value)}; + option.set_default_value(val); + } + return; + } if constexpr (is_same_decayed_v) { @@ -1674,6 +1725,26 @@ gnc_register_multichoice_callback_option(GncOptionDBPtr& db, } } + static GncOption* + gnc_make_gncowner_option(const char* section, + const char* name, const char* key, + const char* doc_string, + const GncOwner* value, + GncOptionUIType ui_type) + { + try { + return new GncOption(GncOptionGncOwnerValue{section, name, key, + doc_string, + value, ui_type}); + } + catch (const std::exception& err) + { + std::cerr << "Make GncOwner option threw unexpected exception" + << err.what() << ", option not created." << std::endl; + return nullptr; + } + } + static GncOption* gnc_make_account_list_option(const char* section, const char* name, const char* key, diff --git a/bindings/guile/options.scm b/bindings/guile/options.scm index f72c08debf..6cf904c1eb 100644 --- a/bindings/guile/options.scm +++ b/bindings/guile/options.scm @@ -297,7 +297,7 @@ ((eqv? owner-type GNC-OWNER-EMPLOYEE) (gncEmployeeLookupFlip guid book)) ((eqv? owner-type GNC-OWNER-JOB) (gncJobLookupFlip guid book))))) - (gnc-make-owner-option section name key docstring defval ui-type))) + (gnc-make-gncowner-option section name key docstring defval ui-type))) (define-public (gnc:make-invoice-option section name key docstring getter validator) (issue-deprecation-warning "gnc:make-invoice-option is deprecated. Make and register the option in one command with gnc-register-ionvoice-option.") (let ((defval (if getter (getter) #f))) diff --git a/bindings/guile/test/test-gnc-option-scheme-output.scm b/bindings/guile/test/test-gnc-option-scheme-output.scm index f133c297fe..18407c5c81 100644 --- a/bindings/guile/test/test-gnc-option-scheme-output.scm +++ b/bindings/guile/test/test-gnc-option-scheme-output.scm @@ -588,7 +588,7 @@ veritatis et quasi architecto beatae vitae dicta sunt, explicabo.") (test-equal "owner unchanged" test-unchanged-section-output-template (gnc:generate-restore-forms odb "options")) (let* ((option (gnc:lookup-option odb "foo" "bar")) - (test-template test-literal-output-template) + (test-template test-list-output-template) (book (gnc-get-current-book)) (owner (gncOwnerNew))) (gncOwnerInitCustomer owner (gncCustomerCreate book)) diff --git a/libgnucash/engine/gnc-option-impl.cpp b/libgnucash/engine/gnc-option-impl.cpp index 0f61e0995c..636960a548 100644 --- a/libgnucash/engine/gnc-option-impl.cpp +++ b/libgnucash/engine/gnc-option-impl.cpp @@ -37,6 +37,112 @@ static const QofLogModule log_module{"gnc.options"}; const std::string GncOptionMultichoiceValue::c_empty_string{""}; const std::string GncOptionMultichoiceValue::c_list_string{"multiple values"}; +static inline GncOwnerType +ui_type_to_owner_type(GncOptionUIType ui_type) +{ + if (ui_type == GncOptionUIType::CUSTOMER) + return GNC_OWNER_CUSTOMER; + if (ui_type == GncOptionUIType::VENDOR) + return GNC_OWNER_VENDOR; + if (ui_type == GncOptionUIType::EMPLOYEE) + return GNC_OWNER_EMPLOYEE; + return GNC_OWNER_NONE; +} + +static GncOwner* +make_owner_ptr(const GncOwner* owner) +{ + if (!owner) + return nullptr; + auto rv{gncOwnerNew()}; + gncOwnerCopy(owner, rv); + return rv; +} + +GncOptionGncOwnerValue::GncOptionGncOwnerValue( + const char* section, const char* name, + const char* key, const char* doc_string, + const GncOwner* value, GncOptionUIType ui_type) : + OptionClassifier{section, name, key, doc_string}, + m_ui_type(ui_type), m_value{make_owner_ptr(value)}, + m_default_value{make_owner_ptr(value)} {} + +GncOptionGncOwnerValue::GncOptionGncOwnerValue(const GncOptionGncOwnerValue& from) : + OptionClassifier{from.m_section, from.m_name, from.m_sort_tag, + from.m_doc_string}, + m_ui_type(from.get_ui_type()), m_value{make_owner_ptr(from.get_value())}, + m_default_value{make_owner_ptr(from.get_default_value())} {} + +void +GncOptionGncOwnerValue::set_value(const GncOwner* new_value) +{ + m_value.reset(make_owner_ptr(new_value)); +} + +void +GncOptionGncOwnerValue::set_default_value(const GncOwner *new_value) +{ + m_value.reset(make_owner_ptr(new_value)); + m_default_value.reset(make_owner_ptr(new_value)); +} + +const GncOwner* +GncOptionGncOwnerValue::get_value() const +{ + return m_value.get(); +} + +const GncOwner* +GncOptionGncOwnerValue::get_default_value() const +{ + return m_default_value.get(); +} + +void +GncOptionGncOwnerValue::reset_default_value() +{ + gncOwnerCopy(m_default_value.get(), m_value.get()); +} + +bool +GncOptionGncOwnerValue::is_changed() const noexcept +{ + return gncOwnerEqual(m_value.get(), m_default_value.get()); +} + +bool +GncOptionGncOwnerValue::deserialize(const std::string& str) noexcept +{ + try { + auto guid{static_cast(gnc::GUID::from_string(str))}; + auto inst = qof_instance_from_guid(&guid, m_ui_type); + if (inst) + { + GncOwner owner{}; + owner.type = ui_type_to_owner_type(m_ui_type); + owner.owner.undefined = inst; + set_default_value(&owner); + return true; + } + } + catch (const gnc::guid_syntax_exception& err) + { + PWARN("Failed to convert %s to a GUID", str.c_str()); + } + return false; +} + +std::string +GncOptionGncOwnerValue::serialize() const noexcept +{ + + auto owner{m_value.get()}; + gnc::GUID guid{*qof_instance_get_guid(static_cast(owner->owner.undefined))}; + std::string retval{guid.to_string()}; + + return retval; +} + using GncItem = std::pair; static GncItem @@ -60,6 +166,7 @@ get_current_root_account(void) { return gnc_book_get_root_account(get_current_book()); } + static const QofInstance* qof_instance_from_gnc_item(const GncItem& item) { diff --git a/libgnucash/engine/gnc-option-impl.hpp b/libgnucash/engine/gnc-option-impl.hpp index fec6a5db7f..704596ca51 100644 --- a/libgnucash/engine/gnc-option-impl.hpp +++ b/libgnucash/engine/gnc-option-impl.hpp @@ -113,6 +113,54 @@ private: ValueType m_default_value; }; + +/** class GncOptionGncOwnerValue + * + * Unlike QofInstance based classes GncOwners are created on the fly, aren't + * placed in QofCollection, and therefore their lifetimes have to be managed. + * We use GncOwnerPtr for the purpose. + */ +struct GncOwnerDeleter +{ + void operator()(GncOwner* o) { + g_free(o); + } +}; + +using GncOwnerPtr = std::unique_ptr; + +class GncOptionGncOwnerValue: public OptionClassifier { +public: + GncOptionGncOwnerValue( + const char* section, const char* name, + const char* key, const char* doc_string, + const GncOwner* value, + GncOptionUIType ui_type = GncOptionUIType::INTERNAL); + GncOptionGncOwnerValue(const GncOptionGncOwnerValue& from); + GncOptionGncOwnerValue(GncOptionGncOwnerValue&&) = default; + ~GncOptionGncOwnerValue() = default; + const GncOwner* get_value() const; + const GncOwner* get_default_value() const; + void set_value(const GncOwner* new_value); + void set_default_value(const GncOwner* new_value); + void reset_default_value(); + bool is_changed() const noexcept; + GncOptionUIType get_ui_type() const noexcept { return m_ui_type; } + void make_internal() { m_ui_type = GncOptionUIType::INTERNAL; } + bool is_internal() { return m_ui_type == GncOptionUIType::INTERNAL; } + std::string serialize() const noexcept; + bool deserialize(const std::string& str) noexcept; +private: + GncOptionUIType m_ui_type; + GncOwnerPtr m_value; + GncOwnerPtr m_default_value; +}; + +/** class GncOptionQofinstanceValue + * + * QofInstances know what type they are but getting them to tell you is a pain + * so we put them in a pair with a type identifier. + */ using GncItem = std::pair; class GncOptionQofInstanceValue: public OptionClassifier { @@ -201,6 +249,16 @@ QofInstance* qof_instance_from_string(const std::string& str, QofInstance* qof_instance_from_guid(GncGUID*, GncOptionUIType type); std::string qof_instance_to_string(const QofInstance* inst); +template +struct is_GncOwnerValue +{ + static constexpr bool value = + std::is_same_v, GncOptionGncOwnerValue>; +}; + +template inline constexpr bool +is_GncOwnerValue_v = is_GncOwnerValue::value; + template struct is_QofInstanceValue { diff --git a/libgnucash/engine/gnc-option.cpp b/libgnucash/engine/gnc-option.cpp index 56f3f8aa31..81ce571b48 100644 --- a/libgnucash/engine/gnc-option.cpp +++ b/libgnucash/engine/gnc-option.cpp @@ -25,6 +25,7 @@ #include "gnc-option-impl.hpp" #include "gnc-option-uitype.hpp" #include "gnc-option-ui.hpp" +#include "gncOwner.h" static const char* log_module{"gnc.app-utils.gnc-option"}; @@ -466,8 +467,6 @@ template GncOption::GncOption(const char*, const char*, const char*, const char*, std::string, GncOptionUIType); template GncOption::GncOption(const char*, const char*, const char*, const char*, const QofQuery*, GncOptionUIType); -template GncOption::GncOption(const char*, const char*, const char*, - const char*, const GncOwner*, GncOptionUIType); template bool GncOption::get_value() const; template int GncOption::get_value() const; @@ -477,6 +476,7 @@ template uint16_t GncOption::get_value() const; template const char* GncOption::get_value() const; template std::string GncOption::get_value() const; template const QofInstance* GncOption::get_value() const; +template const GncOwner* GncOption::get_value() const; template gnc_commodity* GncOption::get_value() const; template const Account* GncOption::get_value() const; template RelativeDatePeriod GncOption::get_value() const; @@ -506,6 +506,7 @@ template void GncOption::set_value(char*); template void GncOption::set_value(const char*); template void GncOption::set_value(std::string); template void GncOption::set_value(const QofInstance*); +template void GncOption::set_value(const GncOwner*); template void GncOption::set_value(gnc_commodity*); template void GncOption::set_value(const Account*); template void GncOption::set_value(RelativeDatePeriod); @@ -556,5 +557,3 @@ template GncOption* gnc_make_option(const char*, const char*, const char*, template GncOption* gnc_make_option(const char*, const char*, const char*, const char*, int64_t, GncOptionUIType); - - diff --git a/libgnucash/engine/gnc-option.hpp b/libgnucash/engine/gnc-option.hpp index 828ebaae29..f561414a0d 100644 --- a/libgnucash/engine/gnc-option.hpp +++ b/libgnucash/engine/gnc-option.hpp @@ -56,6 +56,7 @@ using QofQuery = _QofQuery; struct QofInstance_s; using QofInstance = QofInstance_s; template class GncOptionValue; +class GncOptionGncOwnerValue; class GncOptionQofInstanceValue; class GncOptionAccountListValue; class GncOptionAccountSelValue; @@ -101,8 +102,8 @@ using GncOptionVariant = std::variant, GncOptionValue, GncOptionValue, GncOptionQofInstanceValue, + GncOptionGncOwnerValue, GncOptionValue, - GncOptionValue, GncOptionValue, GncOptionAccountListValue, GncOptionAccountSelValue, diff --git a/libgnucash/engine/gnc-optiondb.cpp b/libgnucash/engine/gnc-optiondb.cpp index b7f4f4083e..293bf9a24f 100644 --- a/libgnucash/engine/gnc-optiondb.cpp +++ b/libgnucash/engine/gnc-optiondb.cpp @@ -809,8 +809,8 @@ gnc_register_owner_option(GncOptionDB* db, const char* section, default: uitype = GncOptionUIType::INTERNAL; }; - GncOption option{section, name, key, doc_string, value, - uitype}; + GncOption option{GncOptionGncOwnerValue{section, name, key, doc_string, + value, uitype}}; db->register_option(section, std::move(option)); }