Implement GncOptionMultichoiceValue

Replaces GncOptionValue<GncMultiChoiceOptionChoices> because having the
vector as the value obviously wouldn't work and besides it needs
additional functions.
This commit is contained in:
John Ralls 2019-10-20 15:13:33 -07:00
parent 8eedcb6d6d
commit 435667e8fe
6 changed files with 229 additions and 13 deletions

View File

@ -125,9 +125,6 @@ struct OptionClassifier
std::string m_doc_string;
};
using GncMultiChoiceOptionEntry = std::pair<std::string, std::string>;
using GncMultiChoiceOptionChoices = std::vector<GncMultiChoiceOptionEntry>;
class GncOptionUIItem;
/**
@ -289,16 +286,109 @@ private:
ValueType m_step;
};
/** MultiChoice options have a vector of valid options
* (GncMultiChoiceOptionChoices) and validate the selection as being one of
* those values. The value is the index of the selected item in the vector. The
* tuple contains three strings, a key, a display
* name and a brief description for the tooltip. Both name and description
* should be localized at the point of use.
*
*
*/
using GncMultiChoiceOptionEntry = std::tuple<const std::string,
const std::string,
const std::string>;
using GncMultiChoiceOptionChoices = std::vector<GncMultiChoiceOptionEntry>;
class GncOptionMultichoiceValue :
public OptionClassifier, public OptionUIItem
{
public:
GncOptionMultichoiceValue(const char* section, const char* name,
const char* key, const char* doc_string,
GncMultiChoiceOptionChoices&& choices,
GncOptionUIType ui_type = GncOptionUIType::MULTICHOICE) :
OptionClassifier{section, name, key, doc_string},
OptionUIItem(ui_type),
m_value{}, m_default_value{}, m_choices{std::move(choices)} {}
GncOptionMultichoiceValue(const GncOptionMultichoiceValue&) = default;
GncOptionMultichoiceValue(GncOptionMultichoiceValue&&) = default;
GncOptionMultichoiceValue& operator=(const GncOptionMultichoiceValue&) = default;
GncOptionMultichoiceValue& operator=(GncOptionMultichoiceValue&&) = default;
const std::string& get_value() const
{
return std::get<0>(m_choices.at(m_value));
}
const std::string& get_default_value() const
{
return std::get<0>(m_choices.at(m_default_value));
}
bool validate(const std::string& value) const noexcept
{
auto index = find_key(value);
return index != std::numeric_limits<std::size_t>::max();
}
void set_value(const std::string& value)
{
auto index = find_key(value);
if (index != std::numeric_limits<std::size_t>::max())
m_value = index;
else
throw std::invalid_argument("Value not a valid choice.");
}
std::size_t num_permissible_values() const noexcept
{
return m_choices.size();
}
std::size_t permissible_value_index(const std::string& key) const noexcept
{
return find_key(key);
}
const std::string& permissible_value(std::size_t index) const
{
return std::get<0>(m_choices.at(index));
}
const std::string& permissible_value_name(std::size_t index) const
{
return std::get<1>(m_choices.at(index));
}
const std::string& permissible_value_description(std::size_t index) const
{
return std::get<2>(m_choices.at(index));
}
private:
std::size_t find_key (const std::string& key) const noexcept
{
auto iter = std::find_if(m_choices.begin(), m_choices.end(),
[key](auto choice) {
return std::get<0>(choice) == key; });
if (iter != m_choices.end())
return iter - m_choices.begin();
else
return std::numeric_limits<std::size_t>::max();
}
std::size_t m_value;
std::size_t m_default_value;
GncMultiChoiceOptionChoices m_choices;
};
using GncOptionVariant = std::variant<GncOptionValue<std::string>,
GncOptionValue<bool>,
GncOptionValue<int64_t>,
GncOptionValue<QofInstance*>,
GncOptionValue<QofQuery*>,
GncOptionValue<std::vector<GncGUID>>,
GncOptionValue<GncMultiChoiceOptionChoices>,
GncOptionMultichoiceValue,
GncOptionRangeValue<int>,
GncOptionRangeValue<GncNumeric>,
GncOptionValidatedValue<QofInstance*>>;
class GncOption
{
public:
@ -316,9 +406,8 @@ public:
template <typename ValueType> ValueType get_value() const
{
return std::visit([](const auto& option)->ValueType {
if constexpr (std::is_same_v<decltype(option.get_value()), ValueType>)
if constexpr (std::is_same_v<std::decay_t<decltype(option.get_value())>, std::decay_t<ValueType>>)
return option.get_value();
else
return ValueType {};
}, m_option);
}
@ -326,9 +415,8 @@ public:
template <typename ValueType> ValueType get_default_value() const
{
return std::visit([](const auto& option)->ValueType {
if constexpr (std::is_same_v<decltype(option.get_value()), ValueType>)
if constexpr (std::is_same_v<std::decay_t<decltype(option.get_value())>, std::decay_t<ValueType>>)
return option.get_default_value();
else
return ValueType {};
}, m_option);
@ -337,7 +425,7 @@ public:
template <typename ValueType> void set_value(ValueType value)
{
std::visit([value](auto& option) {
if constexpr (std::is_same_v<decltype(option.get_value()), ValueType>)
if constexpr (std::is_same_v<std::decay_t<decltype(option.get_value())>, std::decay_t<ValueType>>)
option.set_value(value);
}, m_option);
}

View File

@ -316,8 +316,8 @@ gnc_register_multichoice_option(const GncOptionDBPtr& db, const char* section,
const char* doc_string,
GncMultiChoiceOptionChoices&& value)
{
GncOption option{section, name, key, doc_string, value,
GncOptionUIType::MULTICHOICE};
GncOption option{GncOptionMultichoiceValue{section, name, key, doc_string,
std::move(value)}};
db->register_option(section, std::move(option));
}
@ -327,8 +327,8 @@ gnc_register_list_option(const GncOptionDBPtr& db, const char* section,
const char* doc_string,
GncMultiChoiceOptionChoices&& value)
{
GncOption option{section, name, key, doc_string, value,
GncOptionUIType::LIST};
GncOption option{GncOptionMultichoiceValue{section, name, key, doc_string,
std::move(value), GncOptionUIType::LIST}};
db->register_option(section, std::move(option));
}

View File

@ -45,6 +45,7 @@ extern "C" SCM scm_init_sw_gnc_optiondb_module(void);
%}
%include <std_string.i>
%import <base-typemaps.i>
%inline %{
@ -117,6 +118,23 @@ inline SCM
%ignore OptionClassifier;
%ignore OptionUIItem;
%nodefaultctor GncOption;
%ignore GncOptionMultichoiceValue(GncOptionMultichoiceValue&&);
%ignore GncOptionMultichoiceValue::operator=(const GncOptionMultichoiceValue&);
%ignore GncOptionMultichoiceValue::operator=(GncOptionMultichoiceValue&&);
%typemap(in) GncMultiChoiceOptionChoices&& (GncMultiChoiceOptionChoices choices)
{
auto len = scm_to_size_t(scm_length($input));
for (std::size_t i = 0; i < len; ++i)
{
SCM vec = scm_list_ref($input, scm_from_size_t(i));
std::string key{scm_to_utf8_string(SCM_SIMPLE_VECTOR_REF(vec, 0))};
std::string name{scm_to_utf8_string(SCM_SIMPLE_VECTOR_REF(vec, 1))};
std::string desc{scm_to_utf8_string(SCM_SIMPLE_VECTOR_REF(vec, 2))};
choices.push_back({std::move(key), std::move(name), std::move(desc)});
}
$1 = &choices;
}
wrap_unique_ptr(GncOptionDBPtr, GncOptionDB);
%include "gnc-option.hpp"
@ -152,6 +170,7 @@ wrap_unique_ptr(GncOptionDBPtr, GncOptionDB);
%template(set_option_int) set_option<int>;
};
/*
TEST(GncOption, test_string_scm_functions)
{

View File

@ -91,6 +91,7 @@ TEST(GNCOption, test_commodity_ctor)
gnc_commodity_destroy(hpe);
qof_book_destroy(book);
}
static GncOption
make_currency_option (const char* section, const char* name,
const char* key, const char* doc_string,
@ -205,3 +206,64 @@ TEST_F(GncOptionUI, test_set_option_ui_item)
m_option.set_ui_item(&option_ui_item);
EXPECT_EQ(&ui_item, m_option.get_ui_item()->m_widget);
}
class GncOptionMultichoiceTest : public ::testing::Test
{
protected:
GncOptionMultichoiceTest() :
m_option{"foo", "bar", "baz", "Phony Option",
{
{"plugh", "xyzzy", "thud"},
{"waldo", "pepper", "salt"},
{"pork", "sausage", "links"},
{"corge", "grault", "garply"}
}} {}
GncOptionMultichoiceValue m_option;
};
using GncMultichoiceOption = GncOptionMultichoiceTest;
TEST_F(GncMultichoiceOption, test_option_ui_type)
{
EXPECT_EQ(GncOptionUIType::MULTICHOICE, m_option.get_ui_type());
}
TEST_F(GncMultichoiceOption, test_validate)
{
EXPECT_TRUE(m_option.validate("waldo"));
EXPECT_FALSE(m_option.validate("grault"));
}
TEST_F(GncMultichoiceOption, test_set_value)
{
EXPECT_NO_THROW({
m_option.set_value("pork");
EXPECT_STREQ("pork", m_option.get_value().c_str());
});
EXPECT_THROW({ m_option.set_value("salt"); }, std::invalid_argument);
EXPECT_STREQ("pork", m_option.get_value().c_str());
}
TEST_F(GncMultichoiceOption, test_num_permissible)
{
EXPECT_EQ(4, m_option.num_permissible_values());
}
TEST_F(GncMultichoiceOption, test_permissible_value_stuff)
{
EXPECT_NO_THROW({
EXPECT_EQ(3, m_option.permissible_value_index("corge"));
EXPECT_STREQ("waldo", m_option.permissible_value(1).c_str());
EXPECT_STREQ("sausage", m_option.permissible_value_name(2).c_str());
EXPECT_STREQ("thud",
m_option.permissible_value_description(0).c_str());
});
EXPECT_THROW({ auto result = m_option.permissible_value(7); },
std::out_of_range);
EXPECT_THROW({ auto result = m_option.permissible_value_name(9); },
std::out_of_range);
EXPECT_THROW({ auto result = m_option.permissible_value_description(4); },
std::out_of_range);
EXPECT_EQ(std::numeric_limits<std::size_t>::max(),
m_option.permissible_value_index("xyzzy"));
}

View File

@ -69,6 +69,19 @@ TEST_F(GncOptionDBTest, test_register_string_option)
EXPECT_STREQ("waldo", m_db->lookup_string_option("foo", "bar").c_str());
}
TEST_F(GncOptionDBTest, test_register_multichoice_option)
{
GncMultiChoiceOptionChoices choices{
{ "plugh", "xyzzy", "thud"},
{ "waldo", "pepper", "salt"},
{ "pork", "sausage", "links"},
{ "corge", "grault", "garply"}};
gnc_register_multichoice_option(m_db, "foo", "bar", "baz", "Phony Option",
std::move(choices));
ASSERT_TRUE(m_db->set_option("foo", "bar", std::string{"corge"}));
EXPECT_STREQ("corge", m_db->lookup_string_option("foo", "bar").c_str());
}
class GncUIType
{
public:

View File

@ -33,6 +33,7 @@
(test-runner-factory gnc:test-runner)
(test-begin "test-gnc-optiondb-scheme")
(test-gnc-make-text-option)
(test-gnc-make-multichoice-option)
(test-end "test-gnc-optiondb-scheme"))
(define (test-gnc-make-text-option)
@ -47,3 +48,36 @@
(test-equal "pepper" (GncOptionDB-lookup-option
(GncOptionDBPtr-get option-db) "foo" "bar")))
(test-end "test-gnc-make-string-option"))
(define (test-gnc-make-multichoice-option)
(define (keylist->vectorlist keylist)
(map
(lambda (item)
(vector
(car item)
(keylist-get-info keylist (car item) 'text)
(keylist-get-info keylist (car item) 'tip)))
keylist))
(define (keylist-get-info keylist key info)
(assq-ref (assq-ref keylist key) info))
(test-begin "test-gnc-test-multichoice-option")
(let* ((option-db (gnc-option-db-new))
(multilist (list
(list "plugh" (cons 'text "xyzzy") (cons 'tip "thud"))
(list "waldo" (cons 'text "pepper") (cons 'tip "salt"))
(list "pork" (cons 'text "sausage") (cons 'tip "links"))
(list "corge" (cons 'text "grault") (cons 'tip "garply"))))
(multichoice (keylist->vectorlist multilist))
(multi-opt (gnc-register-multichoice-option option-db "foo" "bar" "baz"
"Phony Option" multichoice)))
(GncOptionDB-set-option-string
(GncOptionDBPtr-get option-db) "foo" "bar" "corge")
(test-equal "corge" (GncOptionDB-lookup-option
(GncOptionDBPtr-get option-db) "foo" "bar")))
(test-end "test-gnc-test-multichoice-option"))
(GncOptionDBPtr-get option-db) "foo" "bar"))
(test-end "test-gnc-test-multichoice-option")))