mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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:
parent
16da3208fc
commit
a995343a8b
@ -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
|
||||
{
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user