refactor: adapt magic_enum to Qt (#5258)

This commit is contained in:
nerix 2024-03-23 12:22:42 +01:00 committed by GitHub
parent 044d457d20
commit ed20e71db4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 603 additions and 80 deletions

View file

@ -188,6 +188,7 @@
- Dev: Cleaned up and optimized resources. (#5222)
- Dev: Refactor `StreamerMode`. (#5216, #5236)
- Dev: Cleaned up unused code in `MessageElement` and `MessageLayoutElement`. (#5225)
- Dev: Adapted `magic_enum` to Qt's Utf-16 strings. (#5258)
- Dev: `NetworkManager`'s statics are now created in its `init` method. (#5254)
## 2.4.6

View file

@ -1,6 +1,7 @@
#pragma once
#include <magic_enum/magic_enum.hpp>
#include "util/QMagicEnum.hpp"
#include <pajlada/settings.hpp>
#include <QString>
@ -108,10 +109,7 @@ public:
template <typename T2>
EnumStringSetting<Enum> &operator=(Enum newValue)
{
std::string enumName(magic_enum::enum_name(newValue));
auto qEnumName = QString::fromStdString(enumName);
this->setValue(qEnumName.toLower());
this->setValue(qmagicenum::enumNameString(newValue).toLower());
return *this;
}
@ -130,8 +128,8 @@ public:
Enum getEnum()
{
return magic_enum::enum_cast<Enum>(this->getValue().toStdString(),
magic_enum::case_insensitive)
return qmagicenum::enumCast<Enum>(this->getValue(),
qmagicenum::CASE_INSENSITIVE)
.value_or(this->defaultValue);
}

View file

@ -9,6 +9,7 @@
#include "util/AbandonObject.hpp"
#include "util/DebugCount.hpp"
#include "util/PostToThread.hpp"
#include "util/QMagicEnum.hpp"
#include <magic_enum/magic_enum.hpp>
#include <QCryptographicHash>
@ -181,11 +182,9 @@ void NetworkData::emitFinally()
});
}
QLatin1String NetworkData::typeString() const
QString NetworkData::typeString() const
{
auto view = magic_enum::enum_name<NetworkRequestType>(this->requestType);
return QLatin1String{view.data(),
static_cast<QLatin1String::size_type>(view.size())};
return qmagicenum::enumNameString(this->requestType);
}
void load(std::shared_ptr<NetworkData> &&data)

View file

@ -60,7 +60,7 @@ public:
void emitError(NetworkResult &&result);
void emitFinally();
QLatin1String typeString() const;
QString typeString() const;
private:
QString hash_;

View file

@ -30,11 +30,7 @@ QString sendAnnouncementColor(const CommandContext &ctx,
QString colorStr = "";
if (color != HelixAnnouncementColor::Primary)
{
colorStr =
QString::fromStdString(
std::string{
magic_enum::enum_name<HelixAnnouncementColor>(color)})
.toLower();
colorStr = qmagicenum::enumNameString(color).toLower();
}
if (ctx.words.size() < 2)

View file

@ -119,9 +119,12 @@ int c2_register_callback(lua_State *L)
return 0;
}
auto callbackSavedName = QString("c2cb-%1").arg(
magic_enum::enum_name<EventType>(evtType).data());
lua_setfield(L, LUA_REGISTRYINDEX, callbackSavedName.toStdString().c_str());
auto typeName = magic_enum::enum_name(evtType);
std::string callbackSavedName;
callbackSavedName.reserve(5 + typeName.size());
callbackSavedName += "c2cb-";
callbackSavedName += typeName;
lua_setfield(L, LUA_REGISTRYINDEX, callbackSavedName.c_str());
lua_pop(L, 2);

View file

@ -3,6 +3,7 @@
# include "common/QLogging.hpp"
# include "controllers/commands/CommandController.hpp"
# include "util/QMagicEnum.hpp"
extern "C" {
# include <lua.h>
@ -26,7 +27,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
}
else if (!homepageObj.isUndefined())
{
QString type = magic_enum::enum_name(homepageObj.type()).data();
auto type = qmagicenum::enumName(homepageObj.type());
this->errors.emplace_back(
QString("homepage is defined but is not a string (its type is %1)")
.arg(type));
@ -38,7 +39,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
}
else
{
QString type = magic_enum::enum_name(nameObj.type()).data();
auto type = qmagicenum::enumName(nameObj.type());
this->errors.emplace_back(
QString("name is not a string (its type is %1)").arg(type));
}
@ -50,7 +51,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
}
else
{
QString type = magic_enum::enum_name(descrObj.type()).data();
auto type = qmagicenum::enumName(descrObj.type());
this->errors.emplace_back(
QString("description is not a string (its type is %1)").arg(type));
}
@ -64,7 +65,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
const auto &t = authorsArr.at(i);
if (!t.isString())
{
QString type = magic_enum::enum_name(t.type()).data();
auto type = qmagicenum::enumName(t.type());
this->errors.push_back(
QString("authors element #%1 is not a string (it is a %2)")
.arg(i)
@ -76,7 +77,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
}
else
{
QString type = magic_enum::enum_name(authorsObj.type()).data();
auto type = qmagicenum::enumName(authorsObj.type());
this->errors.emplace_back(
QString("authors is not an array (its type is %1)").arg(type));
}
@ -88,7 +89,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
}
else
{
QString type = magic_enum::enum_name(licenseObj.type()).data();
auto type = qmagicenum::enumName(licenseObj.type());
this->errors.emplace_back(
QString("license is not a string (its type is %1)").arg(type));
}
@ -109,7 +110,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
}
else
{
QString type = magic_enum::enum_name(verObj.type()).data();
auto type = qmagicenum::enumName(verObj.type());
this->errors.emplace_back(
QString("version is not a string (its type is %1)").arg(type));
this->version = semver::version(0, 0, 0);
@ -119,7 +120,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
{
if (!permsObj.isArray())
{
QString type = magic_enum::enum_name(permsObj.type()).data();
auto type = qmagicenum::enumName(permsObj.type());
this->errors.emplace_back(
QString("permissions is not an array (its type is %1)")
.arg(type));
@ -132,7 +133,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
const auto &t = permsArr.at(i);
if (!t.isObject())
{
QString type = magic_enum::enum_name(t.type()).data();
auto type = qmagicenum::enumName(t.type());
this->errors.push_back(QString("permissions element #%1 is not "
"an object (its type is %2)")
.arg(i)
@ -161,7 +162,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
{
if (!tagsObj.isArray())
{
QString type = magic_enum::enum_name(tagsObj.type()).data();
auto type = qmagicenum::enumName(tagsObj.type());
this->errors.emplace_back(
QString("tags is not an array (its type is %1)").arg(type));
return;
@ -173,7 +174,7 @@ PluginMeta::PluginMeta(const QJsonObject &obj)
const auto &t = tagsArr.at(i);
if (!t.isString())
{
QString type = magic_enum::enum_name(t.type()).data();
auto type = qmagicenum::enumName(t.type());
this->errors.push_back(
QString("tags element #%1 is not a string (its type is %2)")
.arg(i)

View file

@ -107,14 +107,14 @@ public:
return {};
}
// this uses magic enum to help automatic tooling find usages
auto typeName =
magic_enum::enum_name(lua::api::EventType::CompletionRequested);
std::string cbName;
cbName.reserve(5 + typeName.size());
cbName += "c2cb-";
cbName += typeName;
auto typ =
lua_getfield(this->state_, LUA_REGISTRYINDEX,
QString("c2cb-%1")
.arg(magic_enum::enum_name<lua::api::EventType>(
lua::api::EventType::CompletionRequested)
.data())
.toStdString()
.c_str());
lua_getfield(this->state_, LUA_REGISTRYINDEX, cbName.c_str());
if (typ != LUA_TFUNCTION)
{
lua_pop(this->state_, 1);

View file

@ -1,6 +1,8 @@
#ifdef CHATTERINO_HAVE_PLUGINS
# include "controllers/plugins/PluginPermission.hpp"
# include "util/QMagicEnum.hpp"
# include <magic_enum/magic_enum.hpp>
# include <QJsonObject>
@ -11,14 +13,13 @@ PluginPermission::PluginPermission(const QJsonObject &obj)
auto jsontype = obj.value("type");
if (!jsontype.isString())
{
QString tn = magic_enum::enum_name(jsontype.type()).data();
auto tn = qmagicenum::enumName(jsontype.type());
this->errors.emplace_back(QString("permission type is defined but is "
"not a string (its type is %1)")
.arg(tn));
}
auto strtype = jsontype.toString().toStdString();
auto opt = magic_enum::enum_cast<PluginPermission::Type>(
strtype, magic_enum::case_insensitive);
auto opt = qmagicenum::enumCast<PluginPermission::Type>(
jsontype.toString(), qmagicenum::CASE_INSENSITIVE);
if (!opt.has_value())
{
this->errors.emplace_back(QString("permission type is an unknown (%1)")

View file

@ -6,6 +6,7 @@
#include "providers/seventv/eventapi/Message.hpp"
#include "providers/seventv/SeventvBadges.hpp"
#include "providers/seventv/SeventvCosmetics.hpp"
#include "util/QMagicEnum.hpp"
#include <QJsonArray>
@ -228,7 +229,7 @@ void SeventvEventAPI::handleDispatch(const Dispatch &dispatch)
default: {
qCDebug(chatterinoSeventvEventAPI)
<< "Unknown subscription type:"
<< magic_enum::enum_name(dispatch.type).data()
<< qmagicenum::enumName(dispatch.type)
<< "body:" << dispatch.body;
}
break;

View file

@ -1,5 +1,7 @@
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "util/QMagicEnum.hpp"
#include <QJsonArray>
#include <utility>
@ -7,8 +9,7 @@
namespace chatterino::seventv::eventapi {
Dispatch::Dispatch(QJsonObject obj)
: type(magic_enum::enum_cast<SubscriptionType>(
obj["type"].toString().toStdString())
: type(qmagicenum::enumCast<SubscriptionType>(obj["type"].toString())
.value_or(SubscriptionType::INVALID))
, body(obj["body"].toObject())
, id(this->body["id"].toString())
@ -95,8 +96,8 @@ bool UserConnectionUpdateDispatch::validate() const
CosmeticCreateDispatch::CosmeticCreateDispatch(const Dispatch &dispatch)
: data(dispatch.body["object"]["data"].toObject())
, kind(magic_enum::enum_cast<CosmeticKind>(
dispatch.body["object"]["kind"].toString().toStdString())
, kind(qmagicenum::enumCast<CosmeticKind>(
dispatch.body["object"]["kind"].toString())
.value_or(CosmeticKind::INVALID))
{
}
@ -111,8 +112,7 @@ EntitlementCreateDeleteDispatch::EntitlementCreateDeleteDispatch(
{
const auto obj = dispatch.body["object"].toObject();
this->refID = obj["ref_id"].toString();
this->kind = magic_enum::enum_cast<CosmeticKind>(
obj["kind"].toString().toStdString())
this->kind = qmagicenum::enumCast<CosmeticKind>(obj["kind"].toString())
.value_or(CosmeticKind::INVALID);
const auto userConnections = obj["user"]["connections"].toArray();

View file

@ -1,5 +1,7 @@
#include "providers/seventv/eventapi/Subscription.hpp"
#include "util/QMagicEnum.hpp"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
@ -9,14 +11,15 @@
namespace {
using namespace chatterino;
using namespace chatterino::seventv::eventapi;
const char *typeToString(SubscriptionType type)
QString typeToString(SubscriptionType type)
{
return magic_enum::enum_name(type).data();
return qmagicenum::enumNameString(type);
}
QJsonObject createDataJson(const char *typeName, const Condition &condition)
QJsonObject createDataJson(const QString &typeName, const Condition &condition)
{
QJsonObject data;
data["type"] = typeName;
@ -45,7 +48,7 @@ bool Subscription::operator!=(const Subscription &rhs) const
QByteArray Subscription::encodeSubscribe() const
{
const auto *typeName = typeToString(this->type);
auto typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)Opcode::Subscribe;
root["d"] = createDataJson(typeName, this->condition);
@ -54,7 +57,7 @@ QByteArray Subscription::encodeSubscribe() const
QByteArray Subscription::encodeUnsubscribe() const
{
const auto *typeName = typeToString(this->type);
auto typeName = typeToString(this->type);
QJsonObject root;
root["op"] = (int)Opcode::Unsubscribe;
root["d"] = createDataJson(typeName, this->condition);
@ -66,8 +69,7 @@ QDebug &operator<<(QDebug &dbg, const Subscription &subscription)
std::visit(
[&](const auto &cond) {
dbg << "Subscription{ condition:" << cond
<< "type:" << magic_enum::enum_name(subscription.type).data()
<< '}';
<< "type:" << qmagicenum::enumName(subscription.type) << '}';
},
subscription.condition);
return dbg;

View file

@ -5,6 +5,7 @@
#include "common/network/NetworkResult.hpp"
#include "common/QLogging.hpp"
#include "util/CancellationToken.hpp"
#include "util/QMagicEnum.hpp"
#include <magic_enum/magic_enum.hpp>
#include <QJsonDocument>
@ -1172,9 +1173,7 @@ void Helix::sendChatAnnouncement(
QJsonObject body;
body.insert("message", message);
const auto colorStr =
std::string{magic_enum::enum_name<HelixAnnouncementColor>(color)};
body.insert("color", QString::fromStdString(colorStr).toLower());
body.insert("color", qmagicenum::enumNameString(color).toLower());
this->makePost("chat/announcements", urlQuery)
.json(body)

View file

@ -1,5 +1,7 @@
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
#include "util/QMagicEnum.hpp"
namespace chatterino {
PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
@ -7,7 +9,7 @@ PubSubAutoModQueueMessage::PubSubAutoModQueueMessage(const QJsonObject &root)
, data(root.value("data").toObject())
, status(this->data.value("status").toString())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
auto oType = qmagicenum::enumCast<Type>(this->typeString);
if (oType.has_value())
{
this->type = oType.value();

View file

@ -1,5 +1,7 @@
#include "providers/twitch/pubsubmessages/Base.hpp"
#include "util/QMagicEnum.hpp"
namespace chatterino {
PubSubMessage::PubSubMessage(QJsonObject _object)
@ -9,7 +11,7 @@ PubSubMessage::PubSubMessage(QJsonObject _object)
, error(this->object.value("error").toString())
, typeString(this->object.value("type").toString())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
auto oType = qmagicenum::enumCast<Type>(this->typeString);
if (oType.has_value())
{
this->type = oType.value();

View file

@ -1,5 +1,7 @@
#include "providers/twitch/pubsubmessages/ChannelPoints.hpp"
#include "util/QMagicEnum.hpp"
namespace chatterino {
PubSubCommunityPointsChannelV1Message::PubSubCommunityPointsChannelV1Message(
@ -7,7 +9,7 @@ PubSubCommunityPointsChannelV1Message::PubSubCommunityPointsChannelV1Message(
: typeString(root.value("type").toString())
, data(root.value("data").toObject())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
auto oType = qmagicenum::enumCast<Type>(this->typeString);
if (oType.has_value())
{
this->type = oType.value();

View file

@ -1,5 +1,7 @@
#include "providers/twitch/pubsubmessages/ChatModeratorAction.hpp"
#include "util/QMagicEnum.hpp"
namespace chatterino {
PubSubChatModeratorActionMessage::PubSubChatModeratorActionMessage(
@ -7,7 +9,7 @@ PubSubChatModeratorActionMessage::PubSubChatModeratorActionMessage(
: typeString(root.value("type").toString())
, data(root.value("data").toObject())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
auto oType = qmagicenum::enumCast<Type>(this->typeString);
if (oType.has_value())
{
this->type = oType.value();

View file

@ -1,5 +1,7 @@
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
#include "util/QMagicEnum.hpp"
#include <QDateTime>
#include <QJsonArray>
@ -8,8 +10,7 @@ namespace chatterino {
PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
: typeString(root.value("type").toString())
{
if (const auto oType =
magic_enum::enum_cast<Type>(this->typeString.toStdString());
if (const auto oType = qmagicenum::enumCast<Type>(this->typeString);
oType.has_value())
{
this->type = oType.value();
@ -75,8 +76,8 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
this->updatedByUserDisplayName = updatedBy.value("display_name").toString();
this->treatmentString = data.value("treatment").toString();
if (const auto oTreatment = magic_enum::enum_cast<Treatment>(
this->treatmentString.toStdString());
if (const auto oTreatment =
qmagicenum::enumCast<Treatment>(this->treatmentString);
oTreatment.has_value())
{
this->treatment = oTreatment.value();
@ -84,8 +85,8 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
this->evasionEvaluationString =
data.value("ban_evasion_evaluation").toString();
if (const auto oEvaluation = magic_enum::enum_cast<EvasionEvaluation>(
this->evasionEvaluationString.toStdString());
if (const auto oEvaluation = qmagicenum::enumCast<EvasionEvaluation>(
this->evasionEvaluationString);
oEvaluation.has_value())
{
this->evasionEvaluation = oEvaluation.value();
@ -93,8 +94,8 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
for (const auto &rType : data.value("types").toArray())
{
if (const auto oRestriction = magic_enum::enum_cast<RestrictionType>(
rType.toString().toStdString());
if (const auto oRestriction =
qmagicenum::enumCast<RestrictionType>(rType.toString());
oRestriction.has_value())
{
this->restrictionTypes.set(oRestriction.value());

View file

@ -1,11 +1,13 @@
#include "providers/twitch/pubsubmessages/Whisper.hpp"
#include "util/QMagicEnum.hpp"
namespace chatterino {
PubSubWhisperMessage::PubSubWhisperMessage(const QJsonObject &root)
: typeString(root.value("type").toString())
{
auto oType = magic_enum::enum_cast<Type>(this->typeString.toStdString());
auto oType = qmagicenum::enumCast<Type>(this->typeString);
if (oType.has_value())
{
this->type = oType.value();

313
src/util/QMagicEnum.hpp Normal file
View file

@ -0,0 +1,313 @@
#pragma once
#include <magic_enum/magic_enum.hpp>
#include <QString>
#include <QStringView>
namespace chatterino::qmagicenum::detail {
template <bool, typename R>
struct EnableIfEnum {
};
template <typename R>
struct EnableIfEnum<true, R> {
using type = R;
};
template <typename T, typename R, typename BinaryPredicate = std::equal_to<>,
typename D = std::decay_t<T>>
using enable_if_t = typename EnableIfEnum<
std::is_enum_v<D> &&
std::is_invocable_r_v<bool, BinaryPredicate, QChar, QChar>,
R>::type;
template <std::size_t N>
consteval QStringView fromArray(const std::array<char16_t, N> &arr)
{
return QStringView{arr.data(), static_cast<QStringView::size_type>(N - 1)};
}
// Only the latin1 subset may be used right now, since it's easily convertible
template <std::size_t N>
consteval bool isLatin1(std::string_view maybe)
{
for (std::size_t i = 0; i < N; i++)
{
if (maybe[i] < 0x20 || maybe[i] > 0x7e)
{
return false;
}
}
return true;
}
template <typename BinaryPredicate>
inline constexpr bool eq(
QStringView a, QStringView b,
[[maybe_unused]] BinaryPredicate &&
p) noexcept(magic_enum::detail::is_nothrow_invocable<BinaryPredicate>())
{
// Note: operator== isn't constexpr
if (a.size() != b.size())
{
return false;
}
for (QStringView::size_type i = 0; i < a.size(); i++)
{
if (!p(a[i], b[i]))
{
return false;
}
}
return true;
}
template <typename C, typename E, E V>
consteval auto enumNameStorage()
{
constexpr auto utf8 = magic_enum::enum_name<V>();
static_assert(isLatin1<utf8.size()>(utf8),
"Can't convert non-latin1 UTF8 to UTF16");
std::array<C, utf8.size() + 1> storage;
for (std::size_t i = 0; i < utf8.size(); i++)
{
storage[i] = static_cast<C>(utf8[i]);
}
storage[utf8.size()] = 0;
return storage;
}
template <typename E, E V>
inline constexpr auto ENUM_NAME_STORAGE = enumNameStorage<char16_t, E, V>();
template <typename E, magic_enum::detail::enum_subtype S, std::size_t... I>
consteval auto namesStorage(std::index_sequence<I...> /*unused*/)
{
return std::array<QStringView, sizeof...(I)>{{detail::fromArray(
ENUM_NAME_STORAGE<E, magic_enum::enum_values<E, S>()[I]>)...}};
}
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
inline constexpr auto NAMES_STORAGE = namesStorage<E, S>(
std::make_index_sequence<magic_enum::enum_count<E, S>()>{});
template <typename E, magic_enum::detail::enum_subtype S,
typename D = std::decay_t<E>>
using NamesStorage = decltype((NAMES_STORAGE<D, S>));
template <typename Op = std::equal_to<>>
class CaseInsensitive
{
static constexpr QChar toLower(QChar c) noexcept
{
return (c >= u'A' && c <= u'Z')
? QChar(c.unicode() + static_cast<char16_t>(u'a' - u'A'))
: c;
}
public:
template <typename L, typename R>
constexpr std::enable_if_t<std::is_same_v<std::decay_t<L>, QChar> &&
std::is_same_v<std::decay_t<R>, QChar>,
bool>
operator()(L lhs, R rhs) const noexcept
{
return Op{}(toLower(lhs), toLower(rhs));
}
};
} // namespace chatterino::qmagicenum::detail
namespace chatterino::qmagicenum {
/// @brief Get the name of an enum value
///
/// This version is much lighter on the compile times and is not restricted to the enum_range limitation.
///
/// @tparam V The enum value
/// @returns The name as a string view
template <auto V>
[[nodiscard]] consteval detail::enable_if_t<decltype(V), QStringView>
enumName() noexcept
{
return QStringView{
detail::fromArray(detail::ENUM_NAME_STORAGE<decltype(V), V>)};
}
/// @brief Get the name of an enum value
///
/// @param value The enum value
/// @returns The name as a string view. If @a value does not have name or the
/// value is out of range an empty string is returned.
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
[[nodiscard]] constexpr detail::enable_if_t<E, QStringView> enumName(
E value) noexcept
{
using D = std::decay_t<E>;
if (const auto i = magic_enum::enum_index<D, S>(value))
{
return detail::NAMES_STORAGE<D, S>[*i];
}
return {};
}
/// @brief Gets a static QString from @a view.
///
/// @pre @a view must be a static string view (i.e. it must be valid throughout
/// the entire duration of the program).
///
/// @param view The view to turn into a static string
/// @returns Qt6: A static string (never gets freed), Qt5: regular string
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
[[nodiscard]] inline QString staticString(QStringView view) noexcept
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
return QString(QStringPrivate(nullptr, const_cast<char16_t *>(view.utf16()),
view.size()));
}
#else
[[nodiscard]] inline QString staticString(QStringView view)
{
return view.toString();
}
#endif
/// @brief Get the name of an enum value
///
/// This version is much lighter on the compile times and is not restricted to
/// the enum_range limitation.
///
/// @tparam V The enum value
/// @returns The name as a string. The returned string is static.
template <auto V>
[[nodiscard]] inline detail::enable_if_t<decltype(V), QString>
enumNameString() noexcept
{
return staticString(enumName<V>());
}
/// @brief Get the name of an enum value
///
/// This version is much lighter on the compile times and is not restricted to
/// the enum_range limitation.
///
/// @tparam V The enum value
/// @returns The name as a string. If @a value does not have name or the
/// value is out of range an empty string is returned.
/// The returned string is static.
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
[[nodiscard]] inline detail::enable_if_t<E, QString> enumNameString(
E value) noexcept
{
using D = std::decay_t<E>;
return staticString(enumName<D, S>(value));
}
/// @brief Gets the enum value from a name
///
/// @tparam E The enum type to parse the @a name as
/// @param name The name of the enum value to parse
/// @param p A predicate to compare characters of a string
/// (defaults to std::equal_to)
/// @returns A `std::optional` of the parsed value. If no value was parsed,
/// `std::nullopt` is returned.
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>,
typename BinaryPredicate = std::equal_to<>>
[[nodiscard]] constexpr detail::enable_if_t<E, std::optional<std::decay_t<E>>,
BinaryPredicate>
enumCast(QStringView name,
[[maybe_unused]] BinaryPredicate p =
{}) noexcept(magic_enum::detail::
is_nothrow_invocable<BinaryPredicate>())
{
using D = std::decay_t<E>;
if constexpr (magic_enum::enum_count<D, S>() == 0)
{
static_cast<void>(name);
return std::nullopt; // Empty enum.
}
for (std::size_t i = 0; i < magic_enum::enum_count<D, S>(); i++)
{
if (detail::eq(name, detail::NAMES_STORAGE<D, S>[i], p))
{
return magic_enum::enum_value<D, S>(i);
}
}
return std::nullopt; // Invalid value or out of range.
}
/// @brief Constructs a name from the @a flags
///
/// @param flags The combined flags to construct the name from
/// @param sep A separator between each flag (defaults to u'|')
/// @returns A string containing all names separated by @a sep. If any flag in
/// @a flags is out of rage or does not have a name, an empty string
/// is returned.
template <typename E>
[[nodiscard]] inline detail::enable_if_t<E, QString> enumFlagsName(
E flags, char16_t sep = u'|')
{
using D = std::decay_t<E>;
using U = std::underlying_type_t<D>;
constexpr auto S = magic_enum::detail::enum_subtype::flags; // NOLINT
QString name;
auto checkValue = U{0};
for (std::size_t i = 0; i < magic_enum::enum_count<D, S>(); ++i)
{
const auto v = static_cast<U>(magic_enum::enum_value<D, S>(i));
if ((static_cast<U>(flags) & v) != 0)
{
const auto n = detail::NAMES_STORAGE<D, S>[i];
if (!n.empty())
{
checkValue |= v;
if (!name.isEmpty())
{
name.append(sep);
}
name.append(n);
}
else
{
return {}; // Value out of range.
}
}
}
if (checkValue != 0 && checkValue == static_cast<U>(flags))
{
return name;
}
return {}; // Invalid value or out of range.
}
/// @brief Get the names of all values from @a E.
///
/// @tparam E The enum type
/// @returns A `std::array` of all names (`QStringView`s)
template <typename E,
magic_enum::detail::enum_subtype S = magic_enum::detail::subtype_v<E>>
[[nodiscard]] constexpr auto enumNames() noexcept
-> detail::enable_if_t<E, detail::NamesStorage<E, S>>
{
return detail::NAMES_STORAGE<std::decay_t<E>, S>;
}
/// Allows you to write qmagicenum::enumCast<foo>("bar", qmagicenum::CASE_INSENSITIVE)
inline constexpr auto CASE_INSENSITIVE = detail::CaseInsensitive<>{};
} // namespace chatterino::qmagicenum

View file

@ -33,6 +33,7 @@
#include "util/DistanceBetweenPoints.hpp"
#include "util/Helpers.hpp"
#include "util/IncognitoBrowser.hpp"
#include "util/QMagicEnum.hpp"
#include "util/Twitch.hpp"
#include "widgets/dialogs/ReplyThreadPopup.hpp"
#include "widgets/dialogs/SettingsDialog.hpp"
@ -266,8 +267,7 @@ void addHiddenContextMenuItems(QMenu *menu,
jsonObject["id"] = message->id;
jsonObject["searchText"] = message->searchText;
jsonObject["messageText"] = message->messageText;
jsonObject["flags"] = QString::fromStdString(
magic_enum::enum_flags_name(message->flags.value()));
jsonObject["flags"] = qmagicenum::enumFlagsName(message->flags.value());
jsonDocument.setObject(jsonObject);

View file

@ -1243,7 +1243,7 @@ void GeneralPage::initLayout(GeneralPageView &layout)
helixTimegateModerators->minimumSizeHint().width());
layout.addDropdownEnumClass<ChatSendProtocol>(
"Chat send protocol", magic_enum::enum_names<ChatSendProtocol>(),
"Chat send protocol", qmagicenum::enumNames<ChatSendProtocol>(),
s.chatSendProtocol,
"'Helix' will use Twitch's Helix API to send message. 'IRC' will use "
"IRC to send messages.",
@ -1256,7 +1256,7 @@ void GeneralPage::initLayout(GeneralPageView &layout)
auto *soundBackend = layout.addDropdownEnumClass<SoundBackend>(
"Sound backend (requires restart)",
magic_enum::enum_names<SoundBackend>(), s.soundBackend,
qmagicenum::enumNames<SoundBackend>(), s.soundBackend,
"Change this only if you're noticing issues with sound playback on "
"your system",
{});

View file

@ -276,7 +276,7 @@ public:
template <typename T, std::size_t N>
ComboBox *addDropdownEnumClass(const QString &text,
const std::array<std::string_view, N> &items,
const std::array<QStringView, N> &items,
EnumStringSetting<T> &setting,
QString toolTipText,
const QString &defaultValueText)
@ -285,7 +285,7 @@ public:
for (const auto &item : items)
{
combo->addItem(QString::fromStdString(std::string(item)));
combo->addItem(item.toString());
}
if (!defaultValueText.isEmpty())
@ -296,8 +296,7 @@ public:
setting.connect(
[&setting, combo](const QString &value) {
auto enumValue =
magic_enum::enum_cast<T>(value.toStdString(),
magic_enum::case_insensitive)
qmagicenum::enumCast<T>(value, qmagicenum::CASE_INSENSITIVE)
.value_or(setting.defaultValue);
auto i = magic_enum::enum_integer(enumValue);

View file

@ -40,6 +40,7 @@ set(test_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/SplitInput.cpp
${CMAKE_CURRENT_LIST_DIR}/src/LinkInfo.cpp
${CMAKE_CURRENT_LIST_DIR}/src/MessageLayout.cpp
${CMAKE_CURRENT_LIST_DIR}/src/QMagicEnum.cpp
# Add your new file above this line!
)

198
tests/src/QMagicEnum.cpp Normal file
View file

@ -0,0 +1,198 @@
#include "util/QMagicEnum.hpp"
#include "common/FlagsEnum.hpp"
#include "common/Literals.hpp"
#include <gtest/gtest.h>
using namespace chatterino;
using namespace literals;
using qmagicenum::enumCast;
using qmagicenum::enumFlagsName;
using qmagicenum::enumName;
using qmagicenum::enumNames;
using qmagicenum::enumNameString;
namespace {
enum class MyEnum {
Foo,
Bar,
Baz,
};
enum class MyFlag {
None = 0,
One = 1,
Two = 2,
Four = 4,
Eight = 8,
};
using MyFlags = chatterino::FlagsEnum<MyFlag>;
enum class MyCustom {
Default = 1,
First = 4,
Second = 9,
};
enum MyOpen {
OpenOne = 11,
OpenTwo = 12,
OpenThree = 13,
};
consteval bool eq(QStringView a, QStringView b)
{
return qmagicenum::detail::eq(a, b, std::equal_to<>());
}
template <typename E>
consteval bool checkConst(E value, QStringView expectedName)
{
return eq(enumName(value), expectedName) &&
enumCast<E>(expectedName) == value;
}
template <typename E>
consteval bool checkInsensitive(E value, QStringView possible)
{
return enumCast<E>(possible, qmagicenum::CASE_INSENSITIVE) == value;
}
template <typename E, std::size_t N = enumNames<E>().size()>
consteval bool checkValues(std::array<QStringView, N> values)
{
constexpr auto got = enumNames<E>();
if (got.size() != N)
{
return false;
}
for (size_t i = 0; i < N; i++)
{
if (!eq(got.at(i), values.at(i)))
{
return false;
}
}
return true;
}
} // namespace
template <>
struct magic_enum::customize::enum_range<MyFlag> {
static constexpr bool is_flags = true; // NOLINT
};
template <>
constexpr magic_enum::customize::customize_t
magic_enum::customize::enum_name<MyCustom>(MyCustom value) noexcept
{
switch (value)
{
case MyCustom::First:
return "myfirst";
case MyCustom::Second:
return "mysecond.*";
default:
return default_tag;
}
}
TEST(QMagicEnum, basic)
{
static_assert(eq(enumName<MyEnum::Foo>(), u"Foo"));
static_assert(eq(enumName<MyEnum::Bar>(), u"Bar"));
static_assert(eq(enumName<MyEnum::Baz>(), u"Baz"));
static_assert(checkConst(MyEnum::Foo, u"Foo"));
static_assert(checkConst(MyEnum::Bar, u"Bar"));
static_assert(checkConst(MyEnum::Baz, u"Baz"));
static_assert(eq(enumName(static_cast<MyEnum>(16)), u""));
static_assert(checkValues<MyEnum>({u"Foo", u"Bar", u"Baz"}));
}
TEST(QMagicEnum, flags)
{
static_assert(eq(enumName<MyFlag::None>(), u"None"));
static_assert(eq(enumName<MyFlag::One>(), u"One"));
static_assert(eq(enumName<MyFlag::Two>(), u"Two"));
static_assert(eq(enumName<MyFlag::Four>(), u"Four"));
static_assert(eq(enumName<MyFlag::Eight>(), u"Eight"));
static_assert(!magic_enum::enum_index<MyFlag>(MyFlag::None).has_value());
static_assert(eq(enumName(MyFlag::None), u""));
static_assert(checkConst(MyFlag::One, u"One"));
static_assert(checkConst(MyFlag::Two, u"Two"));
static_assert(checkConst(MyFlag::Four, u"Four"));
static_assert(checkConst(MyFlag::Eight, u"Eight"));
static_assert(checkConst(MyFlag::Eight, u"Eight"));
static_assert(eq(enumName(static_cast<MyFlag>(16)), u""));
static_assert(checkValues<MyFlag>({u"One", u"Two", u"Four", u"Eight"}));
}
TEST(QMagicEnum, enumNameString)
{
ASSERT_EQ(enumNameString<MyEnum::Baz>(), u"Baz");
ASSERT_EQ(enumNameString<MyFlag::None>(), u"None");
ASSERT_EQ(enumNameString<MyFlag::Four>(), u"Four");
ASSERT_EQ(enumNameString(MyEnum::Bar), u"Bar");
ASSERT_EQ(enumNameString(MyFlag::None), u"");
ASSERT_EQ(enumNameString(MyFlag::One), u"One");
ASSERT_EQ(enumNameString(MyCustom::Second), u"mysecond.*");
ASSERT_EQ(enumNameString(OpenTwo), u"OpenTwo");
}
TEST(QMagicEnum, enumFlagsName)
{
ASSERT_EQ(enumFlagsName(MyFlag::Eight), u"Eight"_s);
ASSERT_EQ(enumFlagsName(MyFlag::None), u""_s);
ASSERT_EQ(enumFlagsName(MyFlags{MyFlag::Eight, MyFlag::Four}.value(), u'+'),
u"Four+Eight"_s);
ASSERT_EQ(enumFlagsName(
MyFlags{MyFlag::Eight, MyFlag::One, MyFlag::Two, MyFlag::Four}
.value()),
u"One|Two|Four|Eight"_s);
ASSERT_EQ(
enumFlagsName(MyFlags{MyFlag::One, static_cast<MyFlag>(16)}.value()),
u""_s);
}
TEST(QMagicEnum, renamed)
{
static_assert(eq(enumName<MyCustom::Default>(), u"Default"));
static_assert(eq(enumName<MyCustom::First>(), u"myfirst"));
static_assert(eq(enumName<MyCustom::Second>(), u"mysecond.*"));
static_assert(checkConst(MyCustom::Default, u"Default"));
static_assert(checkConst(MyCustom::First, u"myfirst"));
static_assert(checkConst(MyCustom::Second, u"mysecond.*"));
static_assert(eq(enumName(static_cast<MyCustom>(16)), u""));
static_assert(
checkValues<MyCustom>({u"Default", u"myfirst", u"mysecond.*"}));
}
TEST(QMagicEnum, open)
{
static_assert(eq(enumName<OpenOne>(), u"OpenOne"));
static_assert(eq(enumName<OpenTwo>(), u"OpenTwo"));
static_assert(eq(enumName<OpenThree>(), u"OpenThree"));
static_assert(checkConst(OpenOne, u"OpenOne"));
static_assert(checkConst(OpenTwo, u"OpenTwo"));
static_assert(checkConst(OpenThree, u"OpenThree"));
static_assert(eq(enumName(static_cast<MyOpen>(16)), u""));
static_assert(checkValues<MyOpen>({u"OpenOne", u"OpenTwo", u"OpenThree"}));
}
TEST(QMagicEnum, caseInsensitive)
{
static_assert(checkInsensitive(MyEnum::Foo, u"foo"));
static_assert(checkInsensitive(MyEnum::Bar, u"BAR"));
static_assert(checkInsensitive(MyFlag::Four, u"fOUR"));
static_assert(checkInsensitive(MyCustom::Second, u"MySecond.*"));
static_assert(checkInsensitive(OpenOne, u"openone"));
}