feat: implement display name specializations for enums (#6238)
This commit is contained in:
@@ -46,6 +46,7 @@
|
|||||||
- Dev: Added a `run-and-kill.sh` script to help debug crash-on-exit bugs. (#6188)
|
- Dev: Added a `run-and-kill.sh` script to help debug crash-on-exit bugs. (#6188)
|
||||||
- Dev: Refactored the `TimeoutStackStyle` enum into its own file. (#6216)
|
- Dev: Refactored the `TimeoutStackStyle` enum into its own file. (#6216)
|
||||||
- Dev: Refactored `Notebook`-related enums into their own file. (#6220)
|
- Dev: Refactored `Notebook`-related enums into their own file. (#6220)
|
||||||
|
- Dev: Implemented customizable display names for enums. (#6238)
|
||||||
- Dev: Refactored event API initialization away from Application and into TwitchIrcServer. (#6198)
|
- Dev: Refactored event API initialization away from Application and into TwitchIrcServer. (#6198)
|
||||||
- Dev: Updated GoogleTest to v1.17.0. (#6180)
|
- Dev: Updated GoogleTest to v1.17.0. (#6180)
|
||||||
- Dev: Mini refactor of `TwitchAccount`. (#6182)
|
- Dev: Mini refactor of `TwitchAccount`. (#6182)
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ enum class TabStyle : std::uint8_t {
|
|||||||
Compact,
|
Compact,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Settings which are availlable for reading and writing on the gui thread.
|
/// Settings which are available for reading and writing on the gui thread.
|
||||||
// These settings are still accessed concurrently in the code but it is bad practice.
|
// These settings are still accessed concurrently in the code but it is bad practice.
|
||||||
class Settings
|
class Settings
|
||||||
{
|
{
|
||||||
@@ -294,8 +294,6 @@ public:
|
|||||||
"/behaviour/autoSubToParticipatedThreads",
|
"/behaviour/autoSubToParticipatedThreads",
|
||||||
true,
|
true,
|
||||||
};
|
};
|
||||||
// BoolSetting twitchSeperateWriteConnection =
|
|
||||||
// {"/behaviour/twitchSeperateWriteConnection", false};
|
|
||||||
|
|
||||||
// Auto-completion
|
// Auto-completion
|
||||||
BoolSetting onlyFetchChattersForSmallerStreamers = {
|
BoolSetting onlyFetchChattersForSmallerStreamers = {
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/QMagicEnum.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace chatterino::qmagicenum {
|
||||||
|
|
||||||
|
using customize_t = std::optional<std::string_view>;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
namespace tag {
|
||||||
|
|
||||||
|
struct DisplayName {
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tag
|
||||||
|
|
||||||
|
/// Builds the tagged data for a specific enum value (V), type (E), and Tag.
|
||||||
|
///
|
||||||
|
/// If the Tag is DisplayName, attempts to obtain a custom display name for the enum value V by calling
|
||||||
|
/// qmagicenumDisplayName(V). If no customization is provided, falls back to magic_enum's name for the value.
|
||||||
|
///
|
||||||
|
/// @tparam E Enum type
|
||||||
|
/// @tparam Tag Tag type (e.g. DisplayName)
|
||||||
|
/// @tparam V Enum value
|
||||||
|
/// @returns A compile-time static string containing the tagged data for the enum value
|
||||||
|
template <typename E, typename Tag, E V>
|
||||||
|
constexpr auto buildEnumValueTaggedData() noexcept
|
||||||
|
{
|
||||||
|
[[maybe_unused]] constexpr auto custom = [] {
|
||||||
|
static_assert(
|
||||||
|
std::is_same_v<Tag, tag::DisplayName>,
|
||||||
|
"unhandled tag in QMagicEnumTagged.hpp::enumTaggedDataValue");
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<Tag, tag::DisplayName>)
|
||||||
|
{
|
||||||
|
return qmagicenumDisplayName(V);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
if constexpr (custom.has_value())
|
||||||
|
{
|
||||||
|
constexpr auto name = *custom;
|
||||||
|
static_assert(!name.empty(),
|
||||||
|
"qmagicenum::customize must return a non-empty string.");
|
||||||
|
return magic_enum::detail::static_str<name.size()>{name};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No specialization for this enum value; fall back to magic_enum's value name
|
||||||
|
return magic_enum::detail::enum_name_v<E, V>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a static std::string_view for each (enum value, Tag) pair.
|
||||||
|
///
|
||||||
|
/// For example, for MyEnum { Foo, Bar } and DisplayName tag, this will generate:
|
||||||
|
/// TAGGED_DATA_STORAGE_MyEnum_DisplayName_Foo = "Custom display name for Foo"
|
||||||
|
/// TAGGED_DATA_STORAGE_MyEnum_DisplayName_Bar = "Custom display name for Bar"
|
||||||
|
template <typename E, typename Tag, E V>
|
||||||
|
inline constexpr auto TAGGED_DATA_STORAGE =
|
||||||
|
buildEnumValueTaggedData<E, Tag, V>();
|
||||||
|
|
||||||
|
/// Converts the static string representation of an enum value + tag into a std::array<char16_t>
|
||||||
|
template <typename C, typename E, typename Tag, E V>
|
||||||
|
consteval auto enumTaggedDataStorage()
|
||||||
|
{
|
||||||
|
constexpr std::string_view utf8 = TAGGED_DATA_STORAGE<decltype(V), Tag, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a std::array<char16_t> for each (enum value, Tag) pair.
|
||||||
|
///
|
||||||
|
/// For example, for MyEnum { Foo, Bar } and DisplayName tag, this will generate:
|
||||||
|
/// TAGGED_DATA_MyEnum_DisplayName_Foo = "Custom display name for Foo" (char16_t array)
|
||||||
|
/// TAGGED_DATA_MyEnum_DisplayName_Bar = "Custom display name for Bar" (char16_t array)
|
||||||
|
template <typename E, typename Tag, E V>
|
||||||
|
inline constexpr auto TAGGED_DATA =
|
||||||
|
enumTaggedDataStorage<char16_t, E, Tag, V>();
|
||||||
|
|
||||||
|
/// Builds a std::array<QStringView> for an enum type and tag, holding the tagged data for each enum value.
|
||||||
|
///
|
||||||
|
/// For example, for MyEnum { Foo, Bar } and DisplayName tag, this will generate:
|
||||||
|
/// INDEXED_DATA_STORAGE_MyEnum_DisplayName[0] = "Custom display name for Foo"
|
||||||
|
/// INDEXED_DATA_STORAGE_MyEnum_DisplayName[1] = "Custom display name for Bar"
|
||||||
|
template <typename E, typename Tag, std::size_t... I>
|
||||||
|
consteval auto taggedDataStorage(std::index_sequence<I...> /*unused*/)
|
||||||
|
{
|
||||||
|
return std::array<QStringView, sizeof...(I)>{{detail::fromArray(
|
||||||
|
TAGGED_DATA<E, Tag, magic_enum::enum_values<E>()[I]>)...}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a std::array<QStringView> for a given enum type and tag, allowing indexed access to tagged data.
|
||||||
|
///
|
||||||
|
/// For example, for MyEnum { Foo, Bar } and DisplayName tag, this will generate:
|
||||||
|
/// INDEXED_TAGGED_DATA_MyEnum_DisplayName[0] = "Custom display name for Foo"
|
||||||
|
/// INDEXED_TAGGED_DATA_MyEnum_DisplayName[1] = "Custom display name for Bar"
|
||||||
|
template <typename E, typename Tag>
|
||||||
|
inline constexpr auto INDEXED_TAGGED_DATA = taggedDataStorage<E, Tag>(
|
||||||
|
std::make_index_sequence<magic_enum::enum_count<E>()>{});
|
||||||
|
|
||||||
|
/// Get the tagged data for a specific enum value and tag at compile time.
|
||||||
|
///
|
||||||
|
/// @tparam V Enum value
|
||||||
|
/// @tparam Tag Tag type
|
||||||
|
/// @returns The tagged data as a QStringView for the enum value.
|
||||||
|
template <detail::IsEnum auto V, typename Tag>
|
||||||
|
[[nodiscard]] consteval QStringView enumTaggedData() noexcept
|
||||||
|
{
|
||||||
|
return QStringView{
|
||||||
|
detail::fromArray(detail::TAGGED_DATA<decltype(V), Tag, V>)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the tagged data for a runtime enum value and tag.
|
||||||
|
///
|
||||||
|
/// @tparam Tag Tag type
|
||||||
|
/// @param value Enum value
|
||||||
|
/// @returns The tagged data as a QStringView for the enum value, or an empty view if value is invalid.
|
||||||
|
template <detail::IsEnum E, typename Tag>
|
||||||
|
[[nodiscard]] constexpr QStringView enumTaggedData(E value) noexcept
|
||||||
|
{
|
||||||
|
using D = std::decay_t<E>;
|
||||||
|
|
||||||
|
if (const auto i = magic_enum::enum_index<D>(value))
|
||||||
|
{
|
||||||
|
return detail::INDEXED_TAGGED_DATA<D, Tag>[*i];
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/// Get the display name of a specific enum value as a QStringView at compile time.
|
||||||
|
///
|
||||||
|
/// If the enum type provides a qmagicenumDisplayName specialization, it is used. Otherwise, fall back to magic_enum's name.
|
||||||
|
///
|
||||||
|
/// @tparam V Enum value
|
||||||
|
/// @returns Display name as QStringView, or name as fallback.
|
||||||
|
template <detail::IsEnum auto V>
|
||||||
|
[[nodiscard]] consteval QStringView enumDisplayName() noexcept
|
||||||
|
{
|
||||||
|
if constexpr (requires { qmagicenumDisplayName(V); })
|
||||||
|
{
|
||||||
|
return detail::enumTaggedData<V, detail::tag::DisplayName>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to our qmagicenum "enum name" implementation
|
||||||
|
return enumName<V>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the display name of a runtime enum value as a QStringView.
|
||||||
|
///
|
||||||
|
/// If the enum type provides a qmagicenumDisplayName specialization, it is used. Otherwise, fall back to magic_enum's name.
|
||||||
|
///
|
||||||
|
/// @param value Enum value
|
||||||
|
/// @returns Display name as QStringView, or name as fallback, or an empty string if not available.
|
||||||
|
template <detail::IsEnum E>
|
||||||
|
[[nodiscard]] constexpr QStringView enumDisplayName(E value) noexcept
|
||||||
|
{
|
||||||
|
if constexpr (requires { qmagicenumDisplayName(value); })
|
||||||
|
{
|
||||||
|
using D = std::decay_t<E>;
|
||||||
|
|
||||||
|
return detail::enumTaggedData<D, detail::tag::DisplayName>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to our qmagicenum "enum name" implementation
|
||||||
|
return enumName(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the display name of a specific enum value as a static QString.
|
||||||
|
///
|
||||||
|
/// If the enum type provides a qmagicenumDisplayName specialization, it is used. Otherwise, fall back to magic_enum's name.
|
||||||
|
///
|
||||||
|
/// @tparam V Enum value
|
||||||
|
/// @returns Display name as QString. The returned string is static.
|
||||||
|
template <detail::IsEnum auto V>
|
||||||
|
[[nodiscard]] inline QString enumDisplayNameString() noexcept
|
||||||
|
{
|
||||||
|
return detail::staticString(enumDisplayName<V>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the display name of a runtime enum value as a static QString.
|
||||||
|
///
|
||||||
|
/// If the enum type provides a qmagicenumDisplayName specialization, it is used. Otherwise, fall back to magic_enum's name.
|
||||||
|
///
|
||||||
|
/// @param value Enum value
|
||||||
|
/// @returns Display name as QString. Returns an empty string if value is invalid. The returned string is static.
|
||||||
|
template <detail::IsEnum E>
|
||||||
|
[[nodiscard]] inline QString enumDisplayNameString(E value) noexcept
|
||||||
|
{
|
||||||
|
using D = std::decay_t<E>;
|
||||||
|
|
||||||
|
return detail::staticString(enumDisplayName<D>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino::qmagicenum
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/ChatterinoSetting.hpp"
|
#include "common/ChatterinoSetting.hpp"
|
||||||
|
#include "common/QLogging.hpp"
|
||||||
#include "util/QMagicEnum.hpp"
|
#include "util/QMagicEnum.hpp"
|
||||||
|
#include "util/QMagicEnumTagged.hpp"
|
||||||
#include "widgets/settingspages/GeneralPageView.hpp"
|
#include "widgets/settingspages/GeneralPageView.hpp"
|
||||||
|
|
||||||
#include <pajlada/signals/signalholder.hpp>
|
#include <pajlada/signals/signalholder.hpp>
|
||||||
#include <QBoxLayout>
|
#include <QBoxLayout>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
|
#include <QDebug>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -59,10 +62,14 @@ public:
|
|||||||
auto *lbl = new QLabel(label % ":");
|
auto *lbl = new QLabel(label % ":");
|
||||||
auto *combo = new ComboBox;
|
auto *combo = new ComboBox;
|
||||||
combo->setFocusPolicy(Qt::StrongFocus);
|
combo->setFocusPolicy(Qt::StrongFocus);
|
||||||
for (const auto &item : qmagicenum::enumNames<T>())
|
|
||||||
|
for (const auto value : magic_enum::enum_values<T>())
|
||||||
{
|
{
|
||||||
combo->addItem(item.toString());
|
combo->addItem(
|
||||||
|
qmagicenum::enumDisplayNameString(value),
|
||||||
|
QVariant(static_cast<std::underlying_type_t<T>>(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this can probably use some other size hint/size strategy
|
// TODO: this can probably use some other size hint/size strategy
|
||||||
combo->setMinimumWidth(combo->minimumSizeHint().width());
|
combo->setMinimumWidth(combo->minimumSizeHint().width());
|
||||||
|
|
||||||
@@ -87,11 +94,18 @@ public:
|
|||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
combo, &QComboBox::currentTextChanged,
|
combo, &QComboBox::currentTextChanged,
|
||||||
[&setting](const auto &newText) {
|
[label, combo, &setting](const auto &newText) {
|
||||||
// The setter for EnumStringSetting does not check that this value is valid
|
bool ok = true;
|
||||||
// Instead, it's up to the getters to make sure that the setting is legic - see the enum_cast above
|
auto enumValue = combo->currentData().toInt(&ok);
|
||||||
// You could also use the settings `getEnum` function
|
if (!ok)
|
||||||
setting = newText;
|
{
|
||||||
|
qCWarning(chatterinoWidget)
|
||||||
|
<< "Combo" << label << " with value" << newText
|
||||||
|
<< "did not contain an intable UserRole data";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setting = qmagicenum::enumNameString(static_cast<T>(enumValue));
|
||||||
});
|
});
|
||||||
|
|
||||||
return widget;
|
return widget;
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
#include "common/FlagsEnum.hpp"
|
#include "common/FlagsEnum.hpp"
|
||||||
#include "common/Literals.hpp"
|
#include "common/Literals.hpp"
|
||||||
#include "Test.hpp"
|
#include "Test.hpp"
|
||||||
|
#include "util/QMagicEnumTagged.hpp"
|
||||||
|
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
using namespace literals;
|
using namespace literals;
|
||||||
|
|
||||||
using qmagicenum::enumCast;
|
using qmagicenum::enumCast;
|
||||||
|
using qmagicenum::enumDisplayName;
|
||||||
|
using qmagicenum::enumDisplayNameString;
|
||||||
using qmagicenum::enumFlagsName;
|
using qmagicenum::enumFlagsName;
|
||||||
using qmagicenum::enumName;
|
using qmagicenum::enumName;
|
||||||
using qmagicenum::enumNames;
|
using qmagicenum::enumNames;
|
||||||
@@ -38,6 +41,19 @@ enum class MyCustom {
|
|||||||
Second = 9,
|
Second = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr chatterino::qmagicenum::customize_t qmagicenumDisplayName(
|
||||||
|
MyCustom value) noexcept
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case MyCustom::First:
|
||||||
|
return "First (Display Name)";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum MyOpen {
|
enum MyOpen {
|
||||||
OpenOne = 11,
|
OpenOne = 11,
|
||||||
OpenTwo = 12,
|
OpenTwo = 12,
|
||||||
@@ -198,3 +214,47 @@ TEST(QMagicEnum, caseInsensitive)
|
|||||||
static_assert(checkInsensitive(MyCustom::Second, u"MySecond.*"));
|
static_assert(checkInsensitive(MyCustom::Second, u"MySecond.*"));
|
||||||
static_assert(checkInsensitive(OpenOne, u"openone"));
|
static_assert(checkInsensitive(OpenOne, u"openone"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(QMagicEnumTagged, displayName)
|
||||||
|
{
|
||||||
|
// MyCustom has a display name specialization for First, but not for Default or Second
|
||||||
|
static_assert(eq(enumDisplayName<MyCustom::Default>(), u"Default"));
|
||||||
|
static_assert(eq(enumDisplayName(MyCustom::Default), u"Default"));
|
||||||
|
static_assert(eq(enumDisplayName(static_cast<MyCustom>(1)), u"Default"));
|
||||||
|
static_assert(
|
||||||
|
eq(enumDisplayName<MyCustom::First>(), u"First (Display Name)"));
|
||||||
|
static_assert(
|
||||||
|
eq(enumDisplayName(MyCustom::First), u"First (Display Name)"));
|
||||||
|
static_assert(
|
||||||
|
eq(enumDisplayName(static_cast<MyCustom>(4)), u"First (Display Name)"));
|
||||||
|
static_assert(eq(enumDisplayName<MyCustom::Second>(), u"mysecond.*"));
|
||||||
|
static_assert(eq(enumDisplayName(MyCustom::Second), u"mysecond.*"));
|
||||||
|
static_assert(eq(enumDisplayName(static_cast<MyCustom>(9)), u"mysecond.*"));
|
||||||
|
|
||||||
|
// MyFlag does not have a display name specialization
|
||||||
|
static_assert(eq(enumDisplayName<MyFlag::None>(), u"None"));
|
||||||
|
static_assert(eq(enumDisplayName<MyFlag::One>(), u"One"));
|
||||||
|
static_assert(eq(enumDisplayName<MyFlag::Two>(), u"Two"));
|
||||||
|
static_assert(eq(enumDisplayName<MyFlag::Four>(), u"Four"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(QMagicEnumTagged, enumDisplayNameString)
|
||||||
|
{
|
||||||
|
auto withSpecDN = enumDisplayNameString<MyCustom::First>();
|
||||||
|
ASSERT_EQ(withSpecDN, u"First (Display Name)");
|
||||||
|
|
||||||
|
auto withSpec = enumName<MyCustom::First>();
|
||||||
|
ASSERT_EQ(withSpec, u"myfirst");
|
||||||
|
|
||||||
|
auto withoutSpecDN = enumDisplayNameString<MyFlag::Eight>();
|
||||||
|
ASSERT_EQ(withoutSpecDN, u"Eight");
|
||||||
|
|
||||||
|
auto withoutSpec = enumName<MyFlag::Eight>();
|
||||||
|
ASSERT_EQ(withoutSpec, u"Eight");
|
||||||
|
|
||||||
|
auto secondWithSpecDN = enumDisplayNameString<MyCustom::Second>();
|
||||||
|
ASSERT_EQ(secondWithSpecDN, u"mysecond.*");
|
||||||
|
|
||||||
|
auto secondWithSpec = enumName<MyCustom::Second>();
|
||||||
|
ASSERT_EQ(secondWithSpec, u"mysecond.*");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user