From 07f6c8b73c2985ddc31424a5b37421f7dfbf5c8e Mon Sep 17 00:00:00 2001 From: Mat Sutcliffe Date: Tue, 26 Feb 2019 01:51:11 +0000 Subject: [PATCH] Issue #15 CVariant can convert between CVariantList and our other container classes --- .../aviation/aircrafticaocodelist.cpp | 4 ++ src/blackmisc/aviation/aircrafticaocodelist.h | 3 + .../aviation/airlineicaocodelist.cpp | 4 ++ src/blackmisc/aviation/airlineicaocodelist.h | 3 + src/blackmisc/sequence.h | 6 ++ src/blackmisc/variant.cpp | 53 ++++++++++++++++ src/blackmisc/variant.h | 62 +++++++++++++++---- src/blackmisc/variantlist.cpp | 24 +++++++ src/blackmisc/variantlist.h | 28 ++++++++- src/blackmisc/variantprivate.h | 10 ++- .../testvariantandmap/testvariantandmap.cpp | 28 +++++++++ 11 files changed, 212 insertions(+), 13 deletions(-) diff --git a/src/blackmisc/aviation/aircrafticaocodelist.cpp b/src/blackmisc/aviation/aircrafticaocodelist.cpp index 25fa71af5..cff76a913 100644 --- a/src/blackmisc/aviation/aircrafticaocodelist.cpp +++ b/src/blackmisc/aviation/aircrafticaocodelist.cpp @@ -25,6 +25,10 @@ namespace BlackMisc CSequence(other) { } + CAircraftIcaoCodeList::CAircraftIcaoCodeList(std::initializer_list il) : + CSequence(il) + { } + CAircraftIcaoCodeList CAircraftIcaoCodeList::findByDesignator(const QString &designator, int fuzzySearch) const { if (!fuzzySearch && !CAircraftIcaoCode::isValidDesignator(designator)) { return CAircraftIcaoCodeList(); } diff --git a/src/blackmisc/aviation/aircrafticaocodelist.h b/src/blackmisc/aviation/aircrafticaocodelist.h index 7bb1a51c1..9f7c026b9 100644 --- a/src/blackmisc/aviation/aircrafticaocodelist.h +++ b/src/blackmisc/aviation/aircrafticaocodelist.h @@ -45,6 +45,9 @@ namespace BlackMisc //! Construct from a base class object. CAircraftIcaoCodeList(const CSequence &other); + //! Construct from initializer list. + CAircraftIcaoCodeList(std::initializer_list il); + //! Find by designator CAircraftIcaoCodeList findByDesignator(const QString &designator, int fuzzySearch = -1) const; diff --git a/src/blackmisc/aviation/airlineicaocodelist.cpp b/src/blackmisc/aviation/airlineicaocodelist.cpp index ddd9d8e44..b8dd0cc9a 100644 --- a/src/blackmisc/aviation/airlineicaocodelist.cpp +++ b/src/blackmisc/aviation/airlineicaocodelist.cpp @@ -33,6 +33,10 @@ namespace BlackMisc CSequence(other) { } + CAirlineIcaoCodeList::CAirlineIcaoCodeList(std::initializer_list il) : + CSequence(il) + { } + CAirlineIcaoCodeList CAirlineIcaoCodeList::findByDesignator(const QString &designator) const { if (!CAirlineIcaoCode::isValidAirlineDesignator(designator)) { return CAirlineIcaoCodeList(); } diff --git a/src/blackmisc/aviation/airlineicaocodelist.h b/src/blackmisc/aviation/airlineicaocodelist.h index 438515e60..d4165260a 100644 --- a/src/blackmisc/aviation/airlineicaocodelist.h +++ b/src/blackmisc/aviation/airlineicaocodelist.h @@ -48,6 +48,9 @@ namespace BlackMisc //! Construct from a base class object. CAirlineIcaoCodeList(const CSequence &other); + //! Construct from initializer list. + CAirlineIcaoCodeList(std::initializer_list il); + //! Find by designator //! Not unique because of virtual airlines CAirlineIcaoCodeList findByDesignator(const QString &designator) const; diff --git a/src/blackmisc/sequence.h b/src/blackmisc/sequence.h index bfb192bcc..0b030b709 100644 --- a/src/blackmisc/sequence.h +++ b/src/blackmisc/sequence.h @@ -80,6 +80,12 @@ namespace BlackMisc //! Destructor. ~CSequence() = default; + //! Copy of internal vector. + //! @{ + QVector toVector() const & { return m_impl; } + QVector toVector() && { return std::move(m_impl); } + //! @} + //! Returns iterator at the beginning of the sequence. iterator begin() { return m_impl.begin(); } diff --git a/src/blackmisc/variant.cpp b/src/blackmisc/variant.cpp index 0d0686d74..f18016e3b 100644 --- a/src/blackmisc/variant.cpp +++ b/src/blackmisc/variant.cpp @@ -14,6 +14,7 @@ #include "blackmisc/logmessage.h" #include "blackmisc/propertyindex.h" #include "blackmisc/statusmessage.h" +#include "blackmisc/variantlist.h" #include "blackmisc/variant.h" #include @@ -42,6 +43,58 @@ namespace BlackMisc return v.value(); } + bool CVariant::canConvert(int typeId) const + { + if (m_v.canConvert(typeId)) + { + return true; + } + if (typeId == qMetaTypeId()) + { + return m_v.canConvert>() || m_v.canConvert(); + } + if (userType() == qMetaTypeId()) + { + return QVariant::fromValue(QVector()).canConvert(typeId) + || QVariant(typeId, nullptr).canConvert(); + } + return false; + } + + bool CVariant::convert(int typeId) + { + if (!m_v.canConvert(typeId)) + { + if (!canConvert(typeId)) { return false; } + if (typeId == qMetaTypeId()) + { + if (m_v.canConvert>()) + { + if (!m_v.convert(qMetaTypeId>())) { return false; } + } + else if (m_v.canConvert()) + { + m_v.setValue(CVariantList(m_v.value())); + } + else { return false; } + } + if (userType() == qMetaTypeId()) + { + if (QVariant::fromValue(QVector()).canConvert(typeId)) + { + if (!m_v.convert(qMetaTypeId>())) { return false; } + } + else { return false; } + } + } + return m_v.convert(typeId); + } + + bool CVariant::isVariantList() const + { + return userType() == qMetaTypeId(); + } + QString CVariant::convertToQString(bool i18n) const { auto *meta = getValueObjectMetaInfo(); diff --git a/src/blackmisc/variant.h b/src/blackmisc/variant.h index d4b3e9a3e..bcc8e3b7c 100644 --- a/src/blackmisc/variant.h +++ b/src/blackmisc/variant.h @@ -18,6 +18,7 @@ #include "blackmisc/datastream.h" #include "blackmisc/inheritancetraits.h" #include "blackmisc/json.h" +#include "blackmisc/range.h" #include "blackmisc/stringutils.h" #include "blackmisc/variantprivate.h" @@ -36,6 +37,7 @@ namespace BlackMisc { class CIcon; class CPropertyIndex; + class CVariantList; namespace Mixin { @@ -127,6 +129,8 @@ namespace BlackMisc public Mixin::JsonOperators, public Mixin::String { + template struct tag {}; + public: //! Default constructor. CVariant() {} @@ -212,10 +216,10 @@ namespace BlackMisc template void set(T &&value) { m_v.setValue(std::forward(value)); } //! Return the value converted to the type T. - template T value() const { return m_v.value(); } + template T value() const { return to(tag()); } //! Synonym for value(). - template T to() const { return m_v.value(); } + template T to() const { return to(tag()); } //! Returns the value converted to the type T, or a default if it can not be converted. //! \details Parameter is passed by value to avoid odr-using the argument in case it is @@ -226,13 +230,13 @@ namespace BlackMisc const QVariant &getQVariant() const { return m_v; } //! True if this variant can be converted to the type with the given metatype ID. - bool canConvert(int typeId) const { return m_v.canConvert(typeId); } + bool canConvert(int typeId) const; //! True if this variant can be converted to the type T. - template bool canConvert() const { return m_v.canConvert(); } + template bool canConvert() const { return canConvert(qMetaTypeId()); } //! Convert this variant to the type with the given metatype ID and return true if successful. - bool convert(int typeId) { return m_v.convert(typeId); } + bool convert(int typeId); //! \copydoc BlackMisc::Mixin::String::toQString QString convertToQString(bool i18n = false) const; @@ -349,17 +353,53 @@ namespace BlackMisc static int compareImpl(const CVariant &, const CVariant &); uint getValueHash() const; + template T to(tag) const + { + auto copy = *this; copy.convert(qMetaTypeId()); return *static_cast(copy.data()); + } + template QList to(tag>) const { return toImpl>(); } + template QVector to(tag>) const { return toImpl>(); } + template CSequence to(tag>) const { return toImpl>(); } + template T toImpl() const + { + using VT = typename T::value_type; + T result; + if (isVariantList()) { for (const auto &v : m_v.value>()) { result.push_back(v.value()); } } + else { for (const auto &v : m_v.value()) { result.push_back(v.value()); } } + return result; + } + bool isVariantList() const; + template static CVariant fromResultOfImpl(F &&func, std::true_type) { std::forward(func)(); return {}; } template static CVariant fromResultOfImpl(F &&func, std::false_type) { return from(std::forward(func)()); } }; - - namespace Private - { - //! \private Needed so we can copy forward-declared CVariant. - inline void assign(CVariant &a, const CVariant &b) { a = b; } - } } // namespace Q_DECLARE_METATYPE(BlackMisc::CVariant) +namespace BlackMisc +{ + namespace Private + { + //! \private Needed so we can copy forward-declared CVariant. + inline void assign(CVariant &a, const CVariant &b) { a = b; } + + //! \private + template + void maybeRegisterMetaListConvert(int) + { + if (QMetaType::hasRegisteredConverterFunction(qMetaTypeId(), qMetaTypeId>())) { return; } + + QMetaType::registerConverter>([](const T &list) -> QVector + { + return list.transform([](const typename T::value_type &v) { return CVariant::from(v); }); + }); + QMetaType::registerConverter, T>([](const QVector &list) -> T + { + return makeRange(list).transform([](const CVariant &v) { return v.to(); }); + }); + } + } +} // namespace + #endif diff --git a/src/blackmisc/variantlist.cpp b/src/blackmisc/variantlist.cpp index 8f41e09f1..7dbb62b1b 100644 --- a/src/blackmisc/variantlist.cpp +++ b/src/blackmisc/variantlist.cpp @@ -7,9 +7,33 @@ */ #include "blackmisc/variantlist.h" +#include +#include namespace BlackMisc { CVariantList::CVariantList(const CSequence &other) : CSequence(other) {} + + CVariantList::CVariantList(const QVariantList &other) + { + std::copy(other.begin(), other.end(), std::back_inserter(*this)); + } + + CVariantList::CVariantList(QVariantList &&other) + { + std::move(other.begin(), other.end(), std::back_inserter(*this)); + } + + CVariantList::CVariantList(const QSequentialIterable &other) + { + for (auto it = other.begin(); it != other.end(); ++it) { push_back(*it); } + } + + void CVariantList::registerMetadata() + { + Mixin::MetaType::registerMetadata(); + QMetaType::registerConverter>([](const CVariantList &list) { return list.toVector(); }); + QMetaType::registerConverter, CVariantList>([](const QVector &list) { return CSequence(list); }); + } } // ns diff --git a/src/blackmisc/variantlist.h b/src/blackmisc/variantlist.h index 8ba2a0463..2fab14a1c 100644 --- a/src/blackmisc/variantlist.h +++ b/src/blackmisc/variantlist.h @@ -15,11 +15,17 @@ #include "blackmisc/collection.h" #include "blackmisc/sequence.h" #include "blackmisc/variant.h" +#include #include +#include namespace BlackMisc { - //! Value object encapsulating a list of variants. + /*! + * Value object encapsulating a list of variants. + * + * A CVariant containing any registered sequential container type can be converted to a CVariantList. + */ class BLACKMISC_EXPORT CVariantList : public CSequence, public BlackMisc::Mixin::MetaType @@ -33,6 +39,26 @@ namespace BlackMisc //! Construct from a base class object. CVariantList(const CSequence &other); + + //! Construct from a QVariantList. + CVariantList(const QVariantList &other); + + //! Construct from a moved QVariantList. + CVariantList(QVariantList &&other); + + //! Construct from a QSequentialIterable. + CVariantList(const QSequentialIterable &other); + + //! Convert to a sequence type by converting all elements. + template + T to() const { return CVariant::from(*this).template to(); } + + //! Convert from a sequence type by converting all elements. + template + static CVariantList from(const T &list) { return CVariant::from(list).template to(); } + + //! \copydoc BlackMisc::CValueObject::registerMetadata + static void registerMetadata(); }; } diff --git a/src/blackmisc/variantprivate.h b/src/blackmisc/variantprivate.h index e5ffe163e..cf10d606c 100644 --- a/src/blackmisc/variantprivate.h +++ b/src/blackmisc/variantprivate.h @@ -27,6 +27,9 @@ namespace BlackMisc class CPropertyIndex; class CIcon; + template + class CSequence; + template void registerMetaValueType(); @@ -234,11 +237,16 @@ namespace BlackMisc IValueObjectMetaInfo *getValueObjectMetaInfo() { return getValueObjectMetaInfo(qMetaTypeId()); } //! \cond PRIVATE + template , T>::value && ! std::is_same::value>> + void maybeRegisterMetaListConvert(int); + template + void maybeRegisterMetaListConvert(...) {} + template struct MetaTypeHelperImpl { static constexpr int maybeGetMetaTypeId() { return qMetaTypeId(); } - static void maybeRegisterMetaType() { qRegisterMetaType(); qDBusRegisterMetaType(); qRegisterMetaTypeStreamOperators(); registerMetaValueType(); } + static void maybeRegisterMetaType() { qRegisterMetaType(); qDBusRegisterMetaType(); qRegisterMetaTypeStreamOperators(); registerMetaValueType(); maybeRegisterMetaListConvert(0); } }; template diff --git a/tests/blackmisc/testvariantandmap/testvariantandmap.cpp b/tests/blackmisc/testvariantandmap/testvariantandmap.cpp index e55cdba4f..1a0c52505 100644 --- a/tests/blackmisc/testvariantandmap/testvariantandmap.cpp +++ b/tests/blackmisc/testvariantandmap/testvariantandmap.cpp @@ -13,6 +13,7 @@ * \ingroup testblackmisc */ +#include "blackmisc/aviation/airlineicaocodelist.h" #include "blackmisc/aviation/atcstation.h" #include "blackmisc/aviation/callsign.h" #include "blackmisc/compare.h" @@ -24,6 +25,7 @@ #include "blackmisc/pq/units.h" #include "blackmisc/propertyindexvariantmap.h" #include "blackmisc/registermetadata.h" +#include "blackmisc/variantlist.h" #include "blackmisc/variant.h" #include "test.h" @@ -50,6 +52,9 @@ namespace BlackMiscTest //! Basic unit tests for value objects and variants void variant(); + //! Unit tests for variant lists + void variantList(); + //! Unit tests for value maps and value objects void valueMap(); }; @@ -106,6 +111,29 @@ namespace BlackMiscTest QVERIFY2(compare(station1, station3) != 0, "Station should not be equal"); } + void CTestVariantAndMap::variantList() + { + const CSequence ints { 1, 2, 3 }; + CVariant variant = CVariant::from(ints); + QVERIFY2(variant.canConvert(), "Variant containing list can convert to CVariantList"); + QVERIFY2(variant.convert(qMetaTypeId()), "Variant containing list can convert to CVariantList"); + const CVariantList variantInts = variant.to(); + QVERIFY2(ints.size() == variantInts.size(), "Variant list has same size as original list"); + QVERIFY2(ints[0] == variantInts[0].to(), "Variant list has same element"); + QVERIFY2(variant.canConvert>(), "Variant containing can convert back"); + QVERIFY2(ints == variant.to>(), "Variant list converted back compares equal"); + + const CAirlineIcaoCodeList list { CAirlineIcaoCode("BAW"), CAirlineIcaoCode("DLH"), CAirlineIcaoCode("AAL") }; + variant = CVariant::from(list); + QVERIFY2(variant.canConvert(), "Variant containing list can convert to CVariantList"); + QVERIFY2(variant.convert(qMetaTypeId()), "Variant containing list can convert to CVariantList"); + CVariantList variantList = variant.to(); + QVERIFY2(list.size() == variantList.size(), "Variant list has same size as original list"); + QVERIFY2(list[0] == variantList[0].to(), "Variant list has same element"); + QVERIFY2(variant.canConvert(), "Variant containing can convert back"); + QVERIFY2(list == variant.to(), "Variant list converted back compares equal"); + } + void CTestVariantAndMap::valueMap() { // ATC station