Provide for multiple selections in GncOptionMultichoiceValue

To support the GncOptionUIType::LIST. This UI type is unused in GnuCash
code but might be used in user custom reports.
This commit is contained in:
John Ralls 2020-03-12 17:30:52 -07:00
parent 16da3208fc
commit a995343a8b
4 changed files with 346 additions and 41 deletions

View File

@ -32,6 +32,9 @@ extern "C"
#include "gnc-ui-util.h"
}
const std::string GncOptionMultichoiceValue::c_empty_string{""};
const std::string GncOptionMultichoiceValue::c_list_string{"multiple values"};
bool
GncOptionAccountValue::validate(const GncOptionAccountList& values) const
{

View File

@ -412,13 +412,14 @@ private:
ValueType m_step;
};
using GncMultiChoiceOptionEntry = std::tuple<const std::string,
using GncMultichoiceOptionEntry = std::tuple<const std::string,
const std::string,
const std::string>;
using GncMultiChoiceOptionChoices = std::vector<GncMultiChoiceOptionEntry>;
using GncMultichoiceOptionIndexVec = std::vector<std::size_t>;
using GncMultichoiceOptionChoices = std::vector<GncMultichoiceOptionEntry>;
/** MultiChoice options have a vector of valid options
* (GncMultiChoiceOptionChoices) and validate the selection as being one of
/** 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
@ -433,22 +434,48 @@ public:
GncOptionMultichoiceValue(const char* section, const char* name,
const char* key, const char* doc_string,
const char* value,
GncMultiChoiceOptionChoices&& choices,
GncMultichoiceOptionChoices&& choices,
GncOptionUIType ui_type = GncOptionUIType::MULTICHOICE) :
OptionClassifier{section, name, key, doc_string},
m_ui_type{ui_type},
m_value{}, m_default_value{}, m_choices{std::move(choices)} {
if (value)
m_value{}, m_default_value{}, m_choices{std::move(choices)}
{
if (value)
{
if (auto index = find_key(value);
index != size_t_max)
{
if (auto index = find_key(value);
index != size_t_max)
{
m_value = index;
m_default_value = index;
}
m_value.push_back(index);
m_default_value.push_back(index);
}
}
}
GncOptionMultichoiceValue(const char* section, const char* name,
const char* key, const char* doc_string,
size_t index,
GncMultichoiceOptionChoices&& choices,
GncOptionUIType ui_type = GncOptionUIType::MULTICHOICE) :
OptionClassifier{section, name, key, doc_string},
m_ui_type{ui_type},
m_value{}, m_default_value{}, m_choices{std::move(choices)}
{
if (index < m_choices.size())
{
m_value.push_back(index);
m_default_value.push_back(index);
}
}
GncOptionMultichoiceValue(const char* section, const char* name,
const char* key, const char* doc_string,
GncMultichoiceOptionIndexVec&& indices,
GncMultichoiceOptionChoices&& choices,
GncOptionUIType ui_type = GncOptionUIType::LIST) :
OptionClassifier{section, name, key, doc_string},
m_ui_type{ui_type},
m_value{indices}, m_default_value{std::move(indices)},
m_choices{std::move(choices)} {}
GncOptionMultichoiceValue(const GncOptionMultichoiceValue&) = default;
GncOptionMultichoiceValue(GncOptionMultichoiceValue&&) = default;
GncOptionMultichoiceValue& operator=(const GncOptionMultichoiceValue&) = default;
@ -456,27 +483,84 @@ public:
const std::string& get_value() const
{
return std::get<0>(m_choices.at(m_value));
auto vec{m_value.size() > 0 ? m_value : m_default_value};
if (vec.size() == 0)
return c_empty_string;
if (vec.size() == 1)
return std::get<0>(m_choices.at(vec[0]));
else
return c_list_string;
}
const std::string& get_default_value() const
{
return std::get<0>(m_choices.at(m_default_value));
if (m_default_value.size() == 1)
return std::get<0>(m_choices.at(m_default_value[0]));
else if (m_default_value.size() == 0)
return c_empty_string;
else
return c_list_string;
}
bool validate(const std::string& value) const noexcept
size_t get_index() const
{
if (m_value.size() > 0)
return m_value[0];
if (m_default_value.size() > 0)
return m_default_value[0];
return 0;
}
const GncMultichoiceOptionIndexVec& get_multiple() const noexcept
{
return m_value;
}
const GncMultichoiceOptionIndexVec& get_default_multiple() const noexcept
{
return m_default_value;
}
bool validate(const std::string& value) const noexcept
{
auto index = find_key(value);
return index != size_t_max;
}
bool validate(const GncMultichoiceOptionIndexVec& indexes) const noexcept
{
for (auto index : indexes)
if (index >= m_choices.size())
return false;
return true;
}
void set_value(const std::string& value)
{
auto index = find_key(value);
if (index != size_t_max)
m_value = index;
{
m_value.clear();
m_value.push_back(index);
}
else
throw std::invalid_argument("Value not a valid choice.");
}
void set_value(size_t index)
{
if (index < m_choices.size())
{
m_value.clear();
m_value.push_back(index);
}
else
throw std::invalid_argument("Value not a valid choice.");
}
void set_multiple(const GncMultichoiceOptionIndexVec& indexes)
{
if (validate(indexes))
m_value = indexes;
else
throw std::invalid_argument("One of the supplied indexes was out of range.");
}
std::size_t num_permissible_values() const noexcept
{
return m_choices.size();
@ -513,11 +597,119 @@ private:
}
GncOptionUIType m_ui_type;
std::size_t m_value;
std::size_t m_default_value;
GncMultiChoiceOptionChoices m_choices;
GncMultichoiceOptionIndexVec m_value;
GncMultichoiceOptionIndexVec m_default_value;
GncMultichoiceOptionChoices m_choices;
static const std::string c_empty_string;
static const std::string c_list_string;
};
template<> inline std::ostream&
operator<< <GncOptionMultichoiceValue>(std::ostream& oss,
const GncOptionMultichoiceValue& opt)
{
auto vec{opt.get_multiple()};
bool first{true};
for (auto index : vec)
{
if (first)
first = false;
else
oss << " ";
oss << opt.permissible_value(index);
}
return oss;
}
template<> inline std::istream&
operator>> <GncOptionMultichoiceValue>(std::istream& iss,
GncOptionMultichoiceValue& opt)
{
GncMultichoiceOptionIndexVec values;
while (true)
{
std::string str;
std::getline(iss, str, ' ');
if (!str.empty())
{
auto index = opt.permissible_value_index(str.c_str());
if (index != size_t_max)
values.push_back(index);
else
{
std::string err = str + " is not one of ";
err += opt.m_name;
err += "'s permissible values.";
throw std::invalid_argument(err);
}
}
else
break;
}
opt.set_multiple(values);
iss.clear();
return iss;
}
template<class OptType, typename std::enable_if_t<std::is_same_v<std::decay_t<OptType>, GncOptionMultichoiceValue>, int> = 0>
inline std::ostream&
gnc_option_to_scheme(std::ostream& oss, const OptType& opt)
{
auto indexes{opt.get_multiple()};
if (indexes.size() > 1)
oss << "'(";
bool first = true;
for (auto index : indexes)
{
if (first)
first = false;
else
oss << " ";
oss << "'" << opt.permissible_value(index);
}
if (indexes.size() > 1)
oss << ')';
return oss;
}
template<class OptType, typename std::enable_if_t<std::is_same_v<std::decay_t<OptType>, GncOptionMultichoiceValue>, int> = 0>
inline std::istream&
gnc_option_from_scheme(std::istream& iss, OptType& opt)
{
iss.ignore(3, '\'');
auto c{iss.peek()};
if (static_cast<char>(c) == '(')
{
GncMultichoiceOptionIndexVec values;
iss.ignore(3, '\'');
while (true)
{
std::string str;
std::getline(iss, str, ' ');
if (!str.empty())
{
if (str.back() == ')')
{
str.pop_back();
break;
}
values.push_back(opt.permissible_value_index(str.c_str()));
iss.ignore(2, '\'');
}
else
break;
}
opt.set_multiple(values);
}
else
{
std::string str;
std::getline(iss, str, ' ');
opt.set_value(str);
}
return iss;
}
using GncOptionAccountList = std::vector<const Account*>;
using GncOptionAccountTypeList = std::vector<GNCAccountType>;

View File

@ -58,6 +58,18 @@ GncOption::get_value() const
std::is_same_v<std::decay_t<ValueType>,
size_t>)
return option.get_period_index();
if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue> &&
std::is_same_v<std::decay_t<ValueType>,
size_t>)
return option.get_index();
if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue> &&
std::is_same_v<std::decay_t<ValueType>,
GncMultichoiceOptionIndexVec>)
return option.get_multiple();
return ValueType {};
}, *m_option);
}
@ -78,6 +90,12 @@ GncOption::get_default_value() const
std::is_same_v<std::decay_t<ValueType>,
size_t>)
return option.get_default_period_index();
if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue> &&
std::is_same_v<std::decay_t<ValueType>,
GncMultichoiceOptionIndexVec>)
return option.get_default_multiple();
return ValueType {};
}, *m_option);
@ -96,6 +114,12 @@ GncOption::set_value(ValueType value)
RelativeDatePeriod> ||
std::is_same_v<std::decay_t<ValueType>, size_t>)))
option.set_value(value);
if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue> &&
std::is_same_v<std::decay_t<ValueType>,
GncMultichoiceOptionIndexVec>)
option.set_multiple(value);
}, *m_option);
}
@ -221,6 +245,10 @@ GncOption::validate(ValueType value) const
GncOptionMultichoiceValue> &&
std::is_same_v<std::decay_t<ValueType>,
std::string>) ||
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue> &&
std::is_same_v<std::decay_t<ValueType>,
GncMultichoiceOptionIndexVec>) ||
std::is_same_v<std::decay_t<decltype(option)>,
GncOptionValidatedValue<ValueType>>)
return option.validate(value);
@ -334,19 +362,15 @@ GncOption::to_scheme(std::ostream& oss) const
{
return std::visit([&oss](auto& option) ->std::ostream& {
if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionAccountValue>)
gnc_option_to_scheme(oss, option);
else if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue>)
oss << "'" << option;
else if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
((std::is_same_v<std::decay_t<decltype(option)>,
GncOptionAccountValue>) ||
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue>) ||
std::is_same_v<std::decay_t<decltype(option)>,
GncOptionValue<const QofInstance*>> ||
std::is_same_v<std::decay_t<decltype(option)>,
GncOptionValidatedValue<const QofInstance*>>)
gnc_option_to_scheme(oss, option);
gnc_option_to_scheme(oss, option);
else if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionDateValue>)
@ -366,18 +390,11 @@ GncOption::from_scheme(std::istream& iss)
{
return std::visit([&iss](auto& option) -> std::istream& {
if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionAccountValue>)
gnc_option_from_scheme(iss, option);
else if constexpr
((std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue>))
{
iss.ignore(1, '\'');
iss >> option;
}
else if constexpr
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionAccountValue>) ||
(std::is_same_v<std::decay_t<decltype(option)>,
GncOptionMultichoiceValue>) ||
std::is_same_v<std::decay_t<decltype(option)>,
GncOptionValue<const QofInstance*>> ||
std::is_same_v<std::decay_t<decltype(option)>,
GncOptionValidatedValue<const QofInstance*>>)
@ -438,6 +455,7 @@ template std::string GncOption::get_value<std::string>() const;
template const QofInstance* GncOption::get_value<const QofInstance*>() const;
template RelativeDatePeriod GncOption::get_value<RelativeDatePeriod>() const;
template GncOptionAccountList GncOption::get_value<GncOptionAccountList>() const;
template GncMultichoiceOptionIndexVec GncOption::get_value<GncMultichoiceOptionIndexVec>() const;
template bool GncOption::get_default_value<bool>() const;
template int GncOption::get_default_value<int>() const;
@ -447,6 +465,7 @@ template const char* GncOption::get_default_value<const char*>() const;
template std::string GncOption::get_default_value<std::string>() const;
template const QofInstance* GncOption::get_default_value<const QofInstance*>() const;
template RelativeDatePeriod GncOption::get_default_value<RelativeDatePeriod>() const;
template GncMultichoiceOptionIndexVec GncOption::get_default_value<GncMultichoiceOptionIndexVec>() const;
template void GncOption::set_value(bool);
template void GncOption::set_value(int);
@ -457,6 +476,7 @@ template void GncOption::set_value(std::string);
template void GncOption::set_value(const QofInstance*);
template void GncOption::set_value(RelativeDatePeriod);
template void GncOption::set_value(size_t);
template void GncOption::set_value(GncMultichoiceOptionIndexVec);
template bool GncOption::validate(bool) const;
template bool GncOption::validate(int) const;
@ -466,3 +486,4 @@ template bool GncOption::validate(const char*) const;
template bool GncOption::validate(std::string) const;
template bool GncOption::validate(const QofInstance*) const;
template bool GncOption::validate(RelativeDatePeriod) const;
template bool GncOption::validate(GncMultichoiceOptionIndexVec) const;

View File

@ -1033,6 +1033,95 @@ TEST_F(GncMultichoiceOption, test_multichoice_scheme_in)
EXPECT_STREQ("pork", m_option.get_value<std::string>().c_str());
}
class GncOptionListTest : public ::testing::Test
{
protected:
GncOptionListTest() :
m_option{GncOptionMultichoiceValue{"foo", "bar", "baz",
"Phony Option",
GncMultichoiceOptionIndexVec{0, 2},
{
{"plugh", "xyzzy", "thud"},
{"waldo", "pepper", "salt"},
{"pork", "sausage", "links"},
{"corge", "grault", "garply"}
}}} {}
GncOption m_option;
};
using GncListOption = GncOptionListTest;
TEST_F(GncListOption, test_option_ui_type)
{
EXPECT_EQ(GncOptionUIType::LIST, m_option.get_ui_type());
}
TEST_F(GncListOption, test_validate)
{
EXPECT_TRUE(m_option.validate(std::string{"pork"}));
EXPECT_TRUE(m_option.validate(GncMultichoiceOptionIndexVec{1, 3}));
EXPECT_FALSE(m_option.validate(GncMultichoiceOptionIndexVec{2, 6}));
}
TEST_F(GncListOption, test_set_value)
{
EXPECT_NO_THROW({
m_option.set_value(GncMultichoiceOptionIndexVec{1, 3});
EXPECT_STREQ("multiple values",
m_option.get_value<std::string>().c_str());
EXPECT_EQ(1U, m_option.get_value<size_t>());
auto vec{m_option.get_value<GncMultichoiceOptionIndexVec>()};
ASSERT_EQ(2U, vec.size());
EXPECT_EQ(1U, vec[0]);
EXPECT_EQ(3U, vec[1]);
});
EXPECT_THROW({ m_option.set_value(GncMultichoiceOptionIndexVec{2, 5}); }, std::invalid_argument);
EXPECT_EQ(1U, m_option.get_value<size_t>());
}
TEST_F(GncListOption, test_list_out)
{
auto vec{m_option.get_value<GncMultichoiceOptionIndexVec>()};
std::string value{m_option.permissible_value(vec[0])};
value += " ";
value += m_option.permissible_value(vec[1]);
std::ostringstream oss;
oss << m_option;
EXPECT_EQ(oss.str(), value);
}
TEST_F(GncListOption, test_list_in)
{
std::istringstream iss{"grault"};
EXPECT_THROW({
iss >> m_option;
}, std::invalid_argument);
iss.clear(); //reset failedbit
iss.str("pork");
iss >> m_option;
EXPECT_EQ(iss.str(), m_option.get_value<std::string>());
}
TEST_F(GncListOption, test_list_scheme_out)
{
std::ostringstream oss;
m_option.to_scheme(oss);
std::string value{"'('"};
auto vec{m_option.get_value<GncMultichoiceOptionIndexVec>()};
value += m_option.permissible_value(vec[0]);
value += " '";
value += m_option.permissible_value(vec[1]);
value += ")";
EXPECT_EQ(value, oss.str());
}
TEST_F(GncListOption, test_list_scheme_in)
{
std::istringstream iss{"'('pork 'waldo)"};
m_option.from_scheme(iss);
EXPECT_STREQ("pork", m_option.get_value<std::string>().c_str());
}
static time64
time64_from_gdate(const GDate* g_date, DayPart when)
{