fix: don't indent inner namespaces (#6235)
This commit is contained in:
+1
-1
@@ -45,7 +45,7 @@ IncludeCategories:
|
||||
# Third party library includes
|
||||
- Regex: "^<([a-zA-Z_0-9-]+/)*[a-zA-Z_0-9-]+.h(pp)?>$"
|
||||
Priority: 3
|
||||
NamespaceIndentation: Inner
|
||||
NamespaceIndentation: None
|
||||
PointerBindsToType: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
Standard: Auto
|
||||
|
||||
+2
-2
@@ -54,10 +54,10 @@ class IStreamerMode;
|
||||
class ITwitchUsers;
|
||||
class NativeMessagingServer;
|
||||
namespace pronouns {
|
||||
class Pronouns;
|
||||
class Pronouns;
|
||||
} // namespace pronouns
|
||||
namespace eventsub {
|
||||
class IController;
|
||||
class IController;
|
||||
} // namespace eventsub
|
||||
|
||||
class IApplication
|
||||
|
||||
+157
-162
@@ -40,209 +40,204 @@
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
void installCustomPalette()
|
||||
{
|
||||
// borrowed from
|
||||
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
|
||||
auto dark = QApplication::palette();
|
||||
void installCustomPalette()
|
||||
{
|
||||
// borrowed from
|
||||
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
|
||||
auto dark = QApplication::palette();
|
||||
|
||||
dark.setColor(QPalette::Window, QColor(22, 22, 22));
|
||||
dark.setColor(QPalette::WindowText, Qt::white);
|
||||
dark.setColor(QPalette::Text, Qt::white);
|
||||
dark.setColor(QPalette::Base, QColor("#333"));
|
||||
dark.setColor(QPalette::AlternateBase, QColor("#444"));
|
||||
dark.setColor(QPalette::ToolTipBase, Qt::white);
|
||||
dark.setColor(QPalette::ToolTipText, Qt::black);
|
||||
dark.setColor(QPalette::Dark, QColor(35, 35, 35));
|
||||
dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
|
||||
dark.setColor(QPalette::Button, QColor(70, 70, 70));
|
||||
dark.setColor(QPalette::ButtonText, Qt::white);
|
||||
dark.setColor(QPalette::BrightText, Qt::red);
|
||||
dark.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||
dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||
dark.setColor(QPalette::HighlightedText, Qt::white);
|
||||
dark.setColor(QPalette::PlaceholderText, QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Window, QColor(22, 22, 22));
|
||||
dark.setColor(QPalette::WindowText, Qt::white);
|
||||
dark.setColor(QPalette::Text, Qt::white);
|
||||
dark.setColor(QPalette::Base, QColor("#333"));
|
||||
dark.setColor(QPalette::AlternateBase, QColor("#444"));
|
||||
dark.setColor(QPalette::ToolTipBase, Qt::white);
|
||||
dark.setColor(QPalette::ToolTipText, Qt::black);
|
||||
dark.setColor(QPalette::Dark, QColor(35, 35, 35));
|
||||
dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
|
||||
dark.setColor(QPalette::Button, QColor(70, 70, 70));
|
||||
dark.setColor(QPalette::ButtonText, Qt::white);
|
||||
dark.setColor(QPalette::BrightText, Qt::red);
|
||||
dark.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||
dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||
dark.setColor(QPalette::HighlightedText, Qt::white);
|
||||
dark.setColor(QPalette::PlaceholderText, QColor(127, 127, 127));
|
||||
|
||||
dark.setColor(QPalette::Disabled, QPalette::Highlight,
|
||||
QColor(80, 80, 80));
|
||||
dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
|
||||
QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Disabled, QPalette::ButtonText,
|
||||
QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Disabled, QPalette::Text,
|
||||
QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Disabled, QPalette::WindowText,
|
||||
QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
|
||||
dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
|
||||
QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Disabled, QPalette::ButtonText,
|
||||
QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
|
||||
dark.setColor(QPalette::Disabled, QPalette::WindowText,
|
||||
QColor(127, 127, 127));
|
||||
|
||||
QApplication::setPalette(dark);
|
||||
}
|
||||
QApplication::setPalette(dark);
|
||||
}
|
||||
|
||||
void initQt()
|
||||
{
|
||||
// set up the QApplication flags
|
||||
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
|
||||
void initQt()
|
||||
{
|
||||
// set up the QApplication flags
|
||||
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Avoid promoting child widgets to child windows
|
||||
// This causes bugs with frameless windows as not all child events
|
||||
// get sent to the parent - effectively making the window immovable.
|
||||
QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
|
||||
// Avoid promoting child widgets to child windows
|
||||
// This causes bugs with frameless windows as not all child events
|
||||
// get sent to the parent - effectively making the window immovable.
|
||||
QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
|
||||
#endif
|
||||
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
QApplication::setWindowIcon(QIcon(":/icon.ico"));
|
||||
QApplication::setWindowIcon(QIcon(":/icon.ico"));
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// On the Mac/Cocoa platform this attribute is enabled by default
|
||||
// We override it to ensure shortcuts show in context menus on that platform
|
||||
QApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus,
|
||||
false);
|
||||
// On the Mac/Cocoa platform this attribute is enabled by default
|
||||
// We override it to ensure shortcuts show in context menus on that platform
|
||||
QApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false);
|
||||
#endif
|
||||
|
||||
installCustomPalette();
|
||||
}
|
||||
installCustomPalette();
|
||||
}
|
||||
|
||||
void showLastCrashDialog(const Args &args, const Paths &paths)
|
||||
void showLastCrashDialog(const Args &args, const Paths &paths)
|
||||
{
|
||||
auto *dialog = new LastRunCrashDialog(args, paths);
|
||||
// Use exec() over open() to block the app from being loaded
|
||||
// and to be able to set the safe mode.
|
||||
dialog->exec();
|
||||
}
|
||||
|
||||
void createRunningFile(const QString &path)
|
||||
{
|
||||
QFile runningFile(path);
|
||||
|
||||
runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
runningFile.flush();
|
||||
runningFile.close();
|
||||
}
|
||||
|
||||
void removeRunningFile(const QString &path)
|
||||
{
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point signalsInitTime;
|
||||
|
||||
[[noreturn]] void handleSignal(int signum)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
if (std::chrono::steady_clock::now() - signalsInitTime > 30s &&
|
||||
getApp()->getCrashHandler()->shouldRecover())
|
||||
{
|
||||
auto *dialog = new LastRunCrashDialog(args, paths);
|
||||
// Use exec() over open() to block the app from being loaded
|
||||
// and to be able to set the safe mode.
|
||||
dialog->exec();
|
||||
}
|
||||
|
||||
void createRunningFile(const QString &path)
|
||||
{
|
||||
QFile runningFile(path);
|
||||
|
||||
runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
runningFile.flush();
|
||||
runningFile.close();
|
||||
}
|
||||
|
||||
void removeRunningFile(const QString &path)
|
||||
{
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point signalsInitTime;
|
||||
|
||||
[[noreturn]] void handleSignal(int signum)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
if (std::chrono::steady_clock::now() - signalsInitTime > 30s &&
|
||||
getApp()->getCrashHandler()->shouldRecover())
|
||||
{
|
||||
QProcess proc;
|
||||
QProcess proc;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// On macOS, programs are bundled into ".app" Application bundles,
|
||||
// when restarting Chatterino that bundle should be opened with the "open"
|
||||
// terminal command instead of directly starting the underlying executable,
|
||||
// as those are 2 different things for the OS and i.e. do not use
|
||||
// the same dock icon (resulting in a second Chatterino icon on restarting)
|
||||
CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
||||
CFStringRef macPath =
|
||||
CFURLCopyFileSystemPath(appUrlRef, kCFURLPOSIXPathStyle);
|
||||
const char *pathPtr =
|
||||
CFStringGetCStringPtr(macPath, CFStringGetSystemEncoding());
|
||||
// On macOS, programs are bundled into ".app" Application bundles,
|
||||
// when restarting Chatterino that bundle should be opened with the "open"
|
||||
// terminal command instead of directly starting the underlying executable,
|
||||
// as those are 2 different things for the OS and i.e. do not use
|
||||
// the same dock icon (resulting in a second Chatterino icon on restarting)
|
||||
CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
||||
CFStringRef macPath =
|
||||
CFURLCopyFileSystemPath(appUrlRef, kCFURLPOSIXPathStyle);
|
||||
const char *pathPtr =
|
||||
CFStringGetCStringPtr(macPath, CFStringGetSystemEncoding());
|
||||
|
||||
proc.setProgram("open");
|
||||
proc.setArguments({pathPtr, "-n", "--args", "--crash-recovery"});
|
||||
proc.setProgram("open");
|
||||
proc.setArguments({pathPtr, "-n", "--args", "--crash-recovery"});
|
||||
|
||||
CFRelease(appUrlRef);
|
||||
CFRelease(macPath);
|
||||
CFRelease(appUrlRef);
|
||||
CFRelease(macPath);
|
||||
#else
|
||||
proc.setProgram(QApplication::applicationFilePath());
|
||||
proc.setArguments({"--crash-recovery"});
|
||||
proc.setProgram(QApplication::applicationFilePath());
|
||||
proc.setArguments({"--crash-recovery"});
|
||||
#endif
|
||||
|
||||
proc.startDetached();
|
||||
}
|
||||
|
||||
std::_Exit(signum);
|
||||
proc.startDetached();
|
||||
}
|
||||
|
||||
// We want to restart Chatterino when it crashes and the setting is set to
|
||||
// true.
|
||||
void initSignalHandler()
|
||||
{
|
||||
#if defined(NDEBUG) && !defined(CHATTERINO_WITH_CRASHPAD)
|
||||
signalsInitTime = std::chrono::steady_clock::now();
|
||||
std::_Exit(signum);
|
||||
}
|
||||
|
||||
signal(SIGSEGV, handleSignal);
|
||||
// We want to restart Chatterino when it crashes and the setting is set to
|
||||
// true.
|
||||
void initSignalHandler()
|
||||
{
|
||||
#if defined(NDEBUG) && !defined(CHATTERINO_WITH_CRASHPAD)
|
||||
signalsInitTime = std::chrono::steady_clock::now();
|
||||
|
||||
signal(SIGSEGV, handleSignal);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
auto *sigintHandler = new UnixSignalHandler(SIGINT);
|
||||
QObject::connect(sigintHandler, &UnixSignalHandler::signalFired, [] {
|
||||
qCInfo(chatterinoApp)
|
||||
<< "Received SIGINT, request application quit";
|
||||
QApplication::quit();
|
||||
});
|
||||
auto *sigtermHandler = new UnixSignalHandler(SIGTERM);
|
||||
QObject::connect(sigtermHandler, &UnixSignalHandler::signalFired, [] {
|
||||
qCInfo(chatterinoApp)
|
||||
<< "Received SIGTERM, request application quit";
|
||||
QApplication::quit();
|
||||
});
|
||||
auto *sigintHandler = new UnixSignalHandler(SIGINT);
|
||||
QObject::connect(sigintHandler, &UnixSignalHandler::signalFired, [] {
|
||||
qCInfo(chatterinoApp) << "Received SIGINT, request application quit";
|
||||
QApplication::quit();
|
||||
});
|
||||
auto *sigtermHandler = new UnixSignalHandler(SIGTERM);
|
||||
QObject::connect(sigtermHandler, &UnixSignalHandler::signalFired, [] {
|
||||
qCInfo(chatterinoApp) << "Received SIGTERM, request application quit";
|
||||
QApplication::quit();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// We delete cache files that haven't been modified in 14 days. This strategy may be
|
||||
// improved in the future.
|
||||
void clearCache(const QDir &dir)
|
||||
// We delete cache files that haven't been modified in 14 days. This strategy may be
|
||||
// improved in the future.
|
||||
void clearCache(const QDir &dir)
|
||||
{
|
||||
size_t deletedCount = 0;
|
||||
for (const auto &info : dir.entryInfoList(QDir::Files))
|
||||
{
|
||||
size_t deletedCount = 0;
|
||||
for (const auto &info : dir.entryInfoList(QDir::Files))
|
||||
if (info.lastModified().addDays(14) < QDateTime::currentDateTime())
|
||||
{
|
||||
if (info.lastModified().addDays(14) < QDateTime::currentDateTime())
|
||||
bool res = QFile(info.absoluteFilePath()).remove();
|
||||
if (res)
|
||||
{
|
||||
bool res = QFile(info.absoluteFilePath()).remove();
|
||||
if (res)
|
||||
{
|
||||
++deletedCount;
|
||||
}
|
||||
++deletedCount;
|
||||
}
|
||||
}
|
||||
qCDebug(chatterinoCache)
|
||||
<< "Deleted" << deletedCount << "files in" << dir.path();
|
||||
}
|
||||
qCDebug(chatterinoCache)
|
||||
<< "Deleted" << deletedCount << "files in" << dir.path();
|
||||
}
|
||||
|
||||
// We delete all but the five most recent crashdumps. This strategy may be
|
||||
// improved in the future.
|
||||
void clearCrashes(QDir dir)
|
||||
// We delete all but the five most recent crashdumps. This strategy may be
|
||||
// improved in the future.
|
||||
void clearCrashes(QDir dir)
|
||||
{
|
||||
// crashpad crashdumps are stored inside the Crashes/report directory
|
||||
if (!dir.cd("reports"))
|
||||
{
|
||||
// crashpad crashdumps are stored inside the Crashes/report directory
|
||||
if (!dir.cd("reports"))
|
||||
{
|
||||
// no reports directory exists = no files to delete
|
||||
return;
|
||||
}
|
||||
|
||||
dir.setNameFilters({"*.dmp"});
|
||||
|
||||
size_t deletedCount = 0;
|
||||
// TODO: use std::views::drop once supported by all compilers
|
||||
size_t filesToSkip = 5;
|
||||
for (auto &&info : dir.entryInfoList(QDir::Files, QDir::Time))
|
||||
{
|
||||
if (filesToSkip > 0)
|
||||
{
|
||||
filesToSkip--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (QFile(info.absoluteFilePath()).remove())
|
||||
{
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
qCDebug(chatterinoApp) << "Deleted" << deletedCount << "crashdumps";
|
||||
// no reports directory exists = no files to delete
|
||||
return;
|
||||
}
|
||||
|
||||
dir.setNameFilters({"*.dmp"});
|
||||
|
||||
size_t deletedCount = 0;
|
||||
// TODO: use std::views::drop once supported by all compilers
|
||||
size_t filesToSkip = 5;
|
||||
for (auto &&info : dir.entryInfoList(QDir::Files, QDir::Time))
|
||||
{
|
||||
if (filesToSkip > 0)
|
||||
{
|
||||
filesToSkip--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (QFile(info.absoluteFilePath()).remove())
|
||||
{
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
qCDebug(chatterinoApp) << "Deleted" << deletedCount << "crashdumps";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void runGui(QApplication &a, const Paths &paths, Settings &settings,
|
||||
|
||||
+43
-43
@@ -10,61 +10,61 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void warn(const char *envName, const QString &envString, T defaultValue)
|
||||
{
|
||||
const auto typeName = QString::fromStdString(
|
||||
std::string(type_name<decltype(defaultValue)>()));
|
||||
template <typename T>
|
||||
void warn(const char *envName, const QString &envString, T defaultValue)
|
||||
{
|
||||
const auto typeName = QString::fromStdString(
|
||||
std::string(type_name<decltype(defaultValue)>()));
|
||||
|
||||
qCWarning(chatterinoEnv).noquote()
|
||||
<< QStringLiteral(
|
||||
"Cannot parse value '%1' of environment variable '%2' "
|
||||
"as a %3, reverting to default value '%4'")
|
||||
.arg(envString)
|
||||
.arg(envName)
|
||||
.arg(typeName)
|
||||
.arg(defaultValue);
|
||||
qCWarning(chatterinoEnv).noquote()
|
||||
<< QStringLiteral(
|
||||
"Cannot parse value '%1' of environment variable '%2' "
|
||||
"as a %3, reverting to default value '%4'")
|
||||
.arg(envString)
|
||||
.arg(envName)
|
||||
.arg(typeName)
|
||||
.arg(defaultValue);
|
||||
}
|
||||
|
||||
std::optional<QString> readOptionalStringEnv(const char *envName)
|
||||
{
|
||||
auto envString = qEnvironmentVariable(envName);
|
||||
if (!envString.isEmpty())
|
||||
{
|
||||
return envString;
|
||||
}
|
||||
|
||||
std::optional<QString> readOptionalStringEnv(const char *envName)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uint16_t readPortEnv(const char *envName, uint16_t defaultValue)
|
||||
{
|
||||
auto envString = qEnvironmentVariable(envName);
|
||||
if (!envString.isEmpty())
|
||||
{
|
||||
auto envString = qEnvironmentVariable(envName);
|
||||
if (!envString.isEmpty())
|
||||
bool ok = false;
|
||||
auto val = envString.toUShort(&ok);
|
||||
if (ok)
|
||||
{
|
||||
return envString;
|
||||
return val;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
warn(envName, envString, defaultValue);
|
||||
}
|
||||
|
||||
uint16_t readPortEnv(const char *envName, uint16_t defaultValue)
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool readBoolEnv(const char *envName, bool defaultValue)
|
||||
{
|
||||
auto envString = qEnvironmentVariable(envName);
|
||||
if (!envString.isEmpty())
|
||||
{
|
||||
auto envString = qEnvironmentVariable(envName);
|
||||
if (!envString.isEmpty())
|
||||
{
|
||||
bool ok = false;
|
||||
auto val = envString.toUShort(&ok);
|
||||
if (ok)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
warn(envName, envString, defaultValue);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
return QVariant(envString).toBool();
|
||||
}
|
||||
|
||||
bool readBoolEnv(const char *envName, bool defaultValue)
|
||||
{
|
||||
auto envString = qEnvironmentVariable(envName);
|
||||
if (!envString.isEmpty())
|
||||
{
|
||||
return QVariant(envString).toBool();
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -11,85 +11,85 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
QJsonArray loadWindowArray(const QString &settingsPath)
|
||||
QJsonArray loadWindowArray(const QString &settingsPath)
|
||||
{
|
||||
QFile file(settingsPath);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
QJsonDocument document = QJsonDocument::fromJson(data);
|
||||
QJsonArray windows_arr = document.object().value("windows").toArray();
|
||||
return windows_arr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T loadNodes(const QJsonObject &obj)
|
||||
{
|
||||
static_assert("loadNodes must be called with the SplitNodeDescriptor "
|
||||
"or ContainerNodeDescriptor type");
|
||||
}
|
||||
|
||||
template <>
|
||||
SplitNodeDescriptor loadNodes(const QJsonObject &root)
|
||||
{
|
||||
SplitNodeDescriptor descriptor;
|
||||
|
||||
descriptor.flexH_ = root.value("flexh").toDouble(1.0);
|
||||
descriptor.flexV_ = root.value("flexv").toDouble(1.0);
|
||||
|
||||
auto data = root.value("data").toObject();
|
||||
|
||||
SplitDescriptor::loadFromJSON(descriptor, root, data);
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
template <>
|
||||
ContainerNodeDescriptor loadNodes(const QJsonObject &root)
|
||||
{
|
||||
ContainerNodeDescriptor descriptor;
|
||||
|
||||
descriptor.flexH_ = root.value("flexh").toDouble(1.0);
|
||||
descriptor.flexV_ = root.value("flexv").toDouble(1.0);
|
||||
|
||||
descriptor.vertical_ = root.value("type").toString() == "vertical";
|
||||
|
||||
for (QJsonValue _val : root.value("items").toArray())
|
||||
{
|
||||
QFile file(settingsPath);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
QJsonDocument document = QJsonDocument::fromJson(data);
|
||||
QJsonArray windows_arr = document.object().value("windows").toArray();
|
||||
return windows_arr;
|
||||
}
|
||||
auto _obj = _val.toObject();
|
||||
|
||||
template <typename T>
|
||||
T loadNodes(const QJsonObject &obj)
|
||||
{
|
||||
static_assert("loadNodes must be called with the SplitNodeDescriptor "
|
||||
"or ContainerNodeDescriptor type");
|
||||
}
|
||||
|
||||
template <>
|
||||
SplitNodeDescriptor loadNodes(const QJsonObject &root)
|
||||
{
|
||||
SplitNodeDescriptor descriptor;
|
||||
|
||||
descriptor.flexH_ = root.value("flexh").toDouble(1.0);
|
||||
descriptor.flexV_ = root.value("flexv").toDouble(1.0);
|
||||
|
||||
auto data = root.value("data").toObject();
|
||||
|
||||
SplitDescriptor::loadFromJSON(descriptor, root, data);
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
template <>
|
||||
ContainerNodeDescriptor loadNodes(const QJsonObject &root)
|
||||
{
|
||||
ContainerNodeDescriptor descriptor;
|
||||
|
||||
descriptor.flexH_ = root.value("flexh").toDouble(1.0);
|
||||
descriptor.flexV_ = root.value("flexv").toDouble(1.0);
|
||||
|
||||
descriptor.vertical_ = root.value("type").toString() == "vertical";
|
||||
|
||||
for (QJsonValue _val : root.value("items").toArray())
|
||||
auto _type = _obj.value("type");
|
||||
if (_type == "split")
|
||||
{
|
||||
auto _obj = _val.toObject();
|
||||
|
||||
auto _type = _obj.value("type");
|
||||
if (_type == "split")
|
||||
{
|
||||
descriptor.items_.emplace_back(
|
||||
loadNodes<SplitNodeDescriptor>(_obj));
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor.items_.emplace_back(
|
||||
loadNodes<ContainerNodeDescriptor>(_obj));
|
||||
}
|
||||
descriptor.items_.emplace_back(
|
||||
loadNodes<SplitNodeDescriptor>(_obj));
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
const QList<QUuid> loadFilters(QJsonValue val)
|
||||
{
|
||||
QList<QUuid> filterIds;
|
||||
|
||||
if (!val.isUndefined())
|
||||
else
|
||||
{
|
||||
const auto array = val.toArray();
|
||||
filterIds.reserve(array.size());
|
||||
for (const auto &id : array)
|
||||
{
|
||||
filterIds.append(QUuid::fromString(id.toString()));
|
||||
}
|
||||
descriptor.items_.emplace_back(
|
||||
loadNodes<ContainerNodeDescriptor>(_obj));
|
||||
}
|
||||
|
||||
return filterIds;
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
const QList<QUuid> loadFilters(QJsonValue val)
|
||||
{
|
||||
QList<QUuid> filterIds;
|
||||
|
||||
if (!val.isUndefined())
|
||||
{
|
||||
const auto array = val.toArray();
|
||||
filterIds.reserve(array.size());
|
||||
for (const auto &id : array)
|
||||
{
|
||||
filterIds.append(QUuid::fromString(id.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
return filterIds;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SplitDescriptor::loadFromJSON(SplitDescriptor &descriptor,
|
||||
|
||||
@@ -8,20 +8,20 @@ struct CommandContext;
|
||||
|
||||
namespace commands {
|
||||
|
||||
QString emoteOnly(const CommandContext &ctx);
|
||||
QString emoteOnlyOff(const CommandContext &ctx);
|
||||
QString emoteOnly(const CommandContext &ctx);
|
||||
QString emoteOnlyOff(const CommandContext &ctx);
|
||||
|
||||
QString subscribers(const CommandContext &ctx);
|
||||
QString subscribersOff(const CommandContext &ctx);
|
||||
QString subscribers(const CommandContext &ctx);
|
||||
QString subscribersOff(const CommandContext &ctx);
|
||||
|
||||
QString slow(const CommandContext &ctx);
|
||||
QString slowOff(const CommandContext &ctx);
|
||||
QString slow(const CommandContext &ctx);
|
||||
QString slowOff(const CommandContext &ctx);
|
||||
|
||||
QString followers(const CommandContext &ctx);
|
||||
QString followersOff(const CommandContext &ctx);
|
||||
QString followers(const CommandContext &ctx);
|
||||
QString followersOff(const CommandContext &ctx);
|
||||
|
||||
QString uniqueChat(const CommandContext &ctx);
|
||||
QString uniqueChatOff(const CommandContext &ctx);
|
||||
QString uniqueChat(const CommandContext &ctx);
|
||||
QString uniqueChatOff(const CommandContext &ctx);
|
||||
|
||||
} // namespace commands
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace chatterino {
|
||||
|
||||
namespace completion {
|
||||
class Source;
|
||||
class Source;
|
||||
} // namespace completion
|
||||
|
||||
/// @brief Represents the kind of completion occurring
|
||||
|
||||
@@ -11,23 +11,23 @@ namespace chatterino::completion {
|
||||
|
||||
namespace {
|
||||
|
||||
void addCommand(const QString &command, std::vector<CommandItem> &out)
|
||||
void addCommand(const QString &command, std::vector<CommandItem> &out)
|
||||
{
|
||||
if (command.startsWith('/') || command.startsWith('.'))
|
||||
{
|
||||
if (command.startsWith('/') || command.startsWith('.'))
|
||||
{
|
||||
out.push_back({
|
||||
.name = command.mid(1),
|
||||
.prefix = command.at(0),
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
out.push_back({
|
||||
.name = command,
|
||||
.prefix = "",
|
||||
});
|
||||
}
|
||||
out.push_back({
|
||||
.name = command.mid(1),
|
||||
.prefix = command.at(0),
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
out.push_back({
|
||||
.name = command,
|
||||
.prefix = "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -17,37 +17,36 @@ namespace chatterino::completion {
|
||||
|
||||
namespace {
|
||||
|
||||
void addEmotes(std::vector<EmoteItem> &out, const EmoteMap &map,
|
||||
const QString &providerName)
|
||||
void addEmotes(std::vector<EmoteItem> &out, const EmoteMap &map,
|
||||
const QString &providerName)
|
||||
{
|
||||
for (auto &&emote : map)
|
||||
{
|
||||
for (auto &&emote : map)
|
||||
{
|
||||
out.push_back({.emote = emote.second,
|
||||
.searchName = emote.first.string,
|
||||
.tabCompletionName = emote.first.string,
|
||||
.displayName = emote.second->name.string,
|
||||
.providerName = providerName,
|
||||
.isEmoji = false});
|
||||
}
|
||||
out.push_back({.emote = emote.second,
|
||||
.searchName = emote.first.string,
|
||||
.tabCompletionName = emote.first.string,
|
||||
.displayName = emote.second->name.string,
|
||||
.providerName = providerName,
|
||||
.isEmoji = false});
|
||||
}
|
||||
}
|
||||
|
||||
void addEmojis(std::vector<EmoteItem> &out,
|
||||
const std::vector<EmojiPtr> &map)
|
||||
void addEmojis(std::vector<EmoteItem> &out, const std::vector<EmojiPtr> &map)
|
||||
{
|
||||
for (const auto &emoji : map)
|
||||
{
|
||||
for (const auto &emoji : map)
|
||||
for (auto &&shortCode : emoji->shortCodes)
|
||||
{
|
||||
for (auto &&shortCode : emoji->shortCodes)
|
||||
{
|
||||
out.push_back(
|
||||
{.emote = emoji->emote,
|
||||
.searchName = shortCode,
|
||||
.tabCompletionName = QStringLiteral(":%1:").arg(shortCode),
|
||||
.displayName = shortCode,
|
||||
.providerName = "Emoji",
|
||||
.isEmoji = true});
|
||||
}
|
||||
};
|
||||
}
|
||||
out.push_back(
|
||||
{.emote = emoji->emote,
|
||||
.searchName = shortCode,
|
||||
.tabCompletionName = QStringLiteral(":%1:").arg(shortCode),
|
||||
.displayName = shortCode,
|
||||
.providerName = "Emoji",
|
||||
.isEmoji = true});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ namespace chatterino::completion {
|
||||
|
||||
namespace {
|
||||
|
||||
size_t sizeWithinLimit(size_t size, size_t limit)
|
||||
size_t sizeWithinLimit(size_t size, size_t limit)
|
||||
{
|
||||
if (limit == 0)
|
||||
{
|
||||
if (limit == 0)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
return std::min(size, limit);
|
||||
return size;
|
||||
}
|
||||
return std::min(size, limit);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -11,149 +11,148 @@
|
||||
|
||||
namespace chatterino::completion {
|
||||
namespace {
|
||||
/**
|
||||
* @brief This function calculates the "cost" of the changes that need to
|
||||
* be done to the query to make it the value.
|
||||
*
|
||||
* By default an emote with more differences in character casing from the
|
||||
* query will get a higher cost, each additional letter also increases cost.
|
||||
*
|
||||
* @param prioritizeUpper If set, then differences in casing don't matter, but
|
||||
* instead the more lowercase letters an emote contains, the higher cost it
|
||||
* will get. Additional letters also increase the cost in this mode.
|
||||
*
|
||||
* @return How different the emote is from query. Values in the range [-10,
|
||||
* \infty].
|
||||
*/
|
||||
int costOfEmote(QStringView query, QStringView emote, bool prioritizeUpper)
|
||||
/**
|
||||
* @brief This function calculates the "cost" of the changes that need to
|
||||
* be done to the query to make it the value.
|
||||
*
|
||||
* By default an emote with more differences in character casing from the
|
||||
* query will get a higher cost, each additional letter also increases cost.
|
||||
*
|
||||
* @param prioritizeUpper If set, then differences in casing don't matter, but
|
||||
* instead the more lowercase letters an emote contains, the higher cost it
|
||||
* will get. Additional letters also increase the cost in this mode.
|
||||
*
|
||||
* @return How different the emote is from query. Values in the range [-10,
|
||||
* \infty].
|
||||
*/
|
||||
int costOfEmote(QStringView query, QStringView emote, bool prioritizeUpper)
|
||||
{
|
||||
int score = 0;
|
||||
|
||||
if (prioritizeUpper)
|
||||
{
|
||||
int score = 0;
|
||||
|
||||
if (prioritizeUpper)
|
||||
// We are in case 3, push 'more uppercase' emotes to the top
|
||||
for (const auto i : emote)
|
||||
{
|
||||
// We are in case 3, push 'more uppercase' emotes to the top
|
||||
for (const auto i : emote)
|
||||
{
|
||||
score += int(!i.isUpper());
|
||||
}
|
||||
score += int(!i.isUpper());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Push more matching emotes to the top
|
||||
int len = std::min(emote.size(), query.size());
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
// Different casing gets a higher cost score
|
||||
score += query.at(i).isUpper() ^ emote.at(i).isUpper();
|
||||
}
|
||||
}
|
||||
// No case differences, put this at the top
|
||||
if (score == 0)
|
||||
{
|
||||
score = -10;
|
||||
}
|
||||
|
||||
auto diff = emote.size() - query.size();
|
||||
if (diff > 0)
|
||||
{
|
||||
// Case changes are way less changes to the user compared to adding characters
|
||||
score += diff * 100;
|
||||
}
|
||||
return score;
|
||||
};
|
||||
|
||||
// This contains the brains of emote tab completion. Updates output to sorted completions.
|
||||
// Ensure that the query string is already normalized, that is doesn't have a leading ':'
|
||||
// matchingFunction is used for testing if the emote should be included in the search.
|
||||
void completeEmotes(
|
||||
const std::vector<EmoteItem> &items, std::vector<EmoteItem> &output,
|
||||
QStringView query, bool ignoreColonForCost,
|
||||
const std::function<bool(EmoteItem, Qt::CaseSensitivity)>
|
||||
&matchingFunction)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Given these emotes: pajaW, PAJAW
|
||||
// There are a few cases of input:
|
||||
// 1. "pajaw" expect {pajaW, PAJAW} - no uppercase characters, do regular case insensitive search
|
||||
// 2. "PA" expect {PAJAW} - uppercase characters, case sensitive search gives results
|
||||
// 3. "Pajaw" expect {PAJAW, pajaW} - case sensitive search doesn't give results, need to use sorting
|
||||
// 4. "NOTHING" expect {} - no results
|
||||
// 5. "nothing" expect {} - same as 4 but first search is case insensitive
|
||||
// Push more matching emotes to the top
|
||||
int len = std::min(emote.size(), query.size());
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
// Different casing gets a higher cost score
|
||||
score += query.at(i).isUpper() ^ emote.at(i).isUpper();
|
||||
}
|
||||
}
|
||||
// No case differences, put this at the top
|
||||
if (score == 0)
|
||||
{
|
||||
score = -10;
|
||||
}
|
||||
|
||||
// Check if the query contains any uppercase characters
|
||||
// This tells us if we're in case 1 or 5 vs all others
|
||||
bool haveUpper =
|
||||
std::any_of(query.begin(), query.end(), [](const QChar &c) {
|
||||
return c.isUpper();
|
||||
});
|
||||
auto diff = emote.size() - query.size();
|
||||
if (diff > 0)
|
||||
{
|
||||
// Case changes are way less changes to the user compared to adding characters
|
||||
score += diff * 100;
|
||||
}
|
||||
return score;
|
||||
};
|
||||
|
||||
// First search, for case 1 it will be case insensitive,
|
||||
// for cases 2, 3 and 4 it will be case sensitive
|
||||
// This contains the brains of emote tab completion. Updates output to sorted completions.
|
||||
// Ensure that the query string is already normalized, that is doesn't have a leading ':'
|
||||
// matchingFunction is used for testing if the emote should be included in the search.
|
||||
void completeEmotes(
|
||||
const std::vector<EmoteItem> &items, std::vector<EmoteItem> &output,
|
||||
QStringView query, bool ignoreColonForCost,
|
||||
const std::function<bool(EmoteItem, Qt::CaseSensitivity)> &matchingFunction)
|
||||
{
|
||||
// Given these emotes: pajaW, PAJAW
|
||||
// There are a few cases of input:
|
||||
// 1. "pajaw" expect {pajaW, PAJAW} - no uppercase characters, do regular case insensitive search
|
||||
// 2. "PA" expect {PAJAW} - uppercase characters, case sensitive search gives results
|
||||
// 3. "Pajaw" expect {PAJAW, pajaW} - case sensitive search doesn't give results, need to use sorting
|
||||
// 4. "NOTHING" expect {} - no results
|
||||
// 5. "nothing" expect {} - same as 4 but first search is case insensitive
|
||||
|
||||
// Check if the query contains any uppercase characters
|
||||
// This tells us if we're in case 1 or 5 vs all others
|
||||
bool haveUpper =
|
||||
std::any_of(query.begin(), query.end(), [](const QChar &c) {
|
||||
return c.isUpper();
|
||||
});
|
||||
|
||||
// First search, for case 1 it will be case insensitive,
|
||||
// for cases 2, 3 and 4 it will be case sensitive
|
||||
for (const auto &item : items)
|
||||
{
|
||||
if (matchingFunction(
|
||||
item, haveUpper ? Qt::CaseSensitive : Qt::CaseInsensitive))
|
||||
{
|
||||
output.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
// if case 3: then true; false otherwise
|
||||
bool prioritizeUpper = false;
|
||||
|
||||
// No results from search
|
||||
if (output.empty())
|
||||
{
|
||||
if (!haveUpper)
|
||||
{
|
||||
// Optimisation: First search was case insensitive, but we found nothing
|
||||
// There is nothing to be found: case 5.
|
||||
return;
|
||||
}
|
||||
// Case sensitive search from case 2 found nothing, therefore we can
|
||||
// only be in case 3 or 4.
|
||||
|
||||
prioritizeUpper = true;
|
||||
// Run the search again but this time without case sensitivity
|
||||
for (const auto &item : items)
|
||||
{
|
||||
if (matchingFunction(
|
||||
item, haveUpper ? Qt::CaseSensitive : Qt::CaseInsensitive))
|
||||
if (matchingFunction(item, Qt::CaseInsensitive))
|
||||
{
|
||||
output.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
// if case 3: then true; false otherwise
|
||||
bool prioritizeUpper = false;
|
||||
|
||||
// No results from search
|
||||
if (output.empty())
|
||||
{
|
||||
if (!haveUpper)
|
||||
{
|
||||
// Optimisation: First search was case insensitive, but we found nothing
|
||||
// There is nothing to be found: case 5.
|
||||
return;
|
||||
}
|
||||
// Case sensitive search from case 2 found nothing, therefore we can
|
||||
// only be in case 3 or 4.
|
||||
|
||||
prioritizeUpper = true;
|
||||
// Run the search again but this time without case sensitivity
|
||||
for (const auto &item : items)
|
||||
{
|
||||
if (matchingFunction(item, Qt::CaseInsensitive))
|
||||
{
|
||||
output.push_back(item);
|
||||
}
|
||||
}
|
||||
if (output.empty())
|
||||
{
|
||||
// The second search found nothing, so don't even try to sort: case 4
|
||||
return;
|
||||
}
|
||||
// The second search found nothing, so don't even try to sort: case 4
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(output.begin(), output.end(),
|
||||
[query, prioritizeUpper, ignoreColonForCost](
|
||||
const EmoteItem &a, const EmoteItem &b) -> bool {
|
||||
auto tempA = a.searchName;
|
||||
auto tempB = b.searchName;
|
||||
if (ignoreColonForCost && tempA.startsWith(":"))
|
||||
{
|
||||
tempA = tempA.mid(1);
|
||||
}
|
||||
if (ignoreColonForCost && tempB.startsWith(":"))
|
||||
{
|
||||
tempB = tempB.mid(1);
|
||||
}
|
||||
|
||||
auto costA = costOfEmote(query, tempA, prioritizeUpper);
|
||||
auto costB = costOfEmote(query, tempB, prioritizeUpper);
|
||||
if (costA == costB)
|
||||
{
|
||||
// Case difference and length came up tied for (a, b), break the tie
|
||||
return QString::compare(tempA, tempB,
|
||||
Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
|
||||
return costA < costB;
|
||||
});
|
||||
}
|
||||
|
||||
std::sort(output.begin(), output.end(),
|
||||
[query, prioritizeUpper, ignoreColonForCost](
|
||||
const EmoteItem &a, const EmoteItem &b) -> bool {
|
||||
auto tempA = a.searchName;
|
||||
auto tempB = b.searchName;
|
||||
if (ignoreColonForCost && tempA.startsWith(":"))
|
||||
{
|
||||
tempA = tempA.mid(1);
|
||||
}
|
||||
if (ignoreColonForCost && tempB.startsWith(":"))
|
||||
{
|
||||
tempB = tempB.mid(1);
|
||||
}
|
||||
|
||||
auto costA = costOfEmote(query, tempA, prioritizeUpper);
|
||||
auto costB = costOfEmote(query, tempB, prioritizeUpper);
|
||||
if (costA == costB)
|
||||
{
|
||||
// Case difference and length came up tied for (a, b), break the tie
|
||||
return QString::compare(tempA, tempB,
|
||||
Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
|
||||
return costA < costB;
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void SmartEmoteStrategy::apply(const std::vector<EmoteItem> &items,
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr QStringView REGEX_START_BOUNDARY(u"(?:\\b|\\s|^)");
|
||||
constexpr QStringView REGEX_END_BOUNDARY(u"(?:\\b|\\s|$)");
|
||||
constexpr QStringView REGEX_START_BOUNDARY(u"(?:\\b|\\s|^)");
|
||||
constexpr QStringView REGEX_END_BOUNDARY(u"(?:\\b|\\s|$)");
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -105,11 +105,11 @@ private:
|
||||
namespace pajlada {
|
||||
|
||||
namespace {
|
||||
chatterino::HighlightPhrase constructError()
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), false, false, false,
|
||||
false, false, QString(), QColor());
|
||||
}
|
||||
chatterino::HighlightPhrase constructError()
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), false, false, false, false,
|
||||
false, QString(), QColor());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
|
||||
@@ -37,7 +37,7 @@ struct ChannelPointReward;
|
||||
struct TwitchEmoteOccurrence;
|
||||
|
||||
namespace linkparser {
|
||||
struct Parsed;
|
||||
struct Parsed;
|
||||
} // namespace linkparser
|
||||
|
||||
struct SystemMessageTag {
|
||||
|
||||
@@ -26,21 +26,21 @@ using namespace literals;
|
||||
|
||||
namespace {
|
||||
|
||||
// Computes the bounding box for the given vector of images
|
||||
QSizeF getBoundingBoxSize(const std::vector<ImagePtr> &images)
|
||||
// Computes the bounding box for the given vector of images
|
||||
QSizeF getBoundingBoxSize(const std::vector<ImagePtr> &images)
|
||||
{
|
||||
qreal width = 0;
|
||||
qreal height = 0;
|
||||
for (const auto &img : images)
|
||||
{
|
||||
qreal width = 0;
|
||||
qreal height = 0;
|
||||
for (const auto &img : images)
|
||||
{
|
||||
QSizeF s = img->size();
|
||||
width = std::max(width, s.width());
|
||||
height = std::max(height, s.height());
|
||||
}
|
||||
|
||||
return {width, height};
|
||||
QSizeF s = img->size();
|
||||
width = std::max(width, s.width());
|
||||
height = std::max(height, s.height());
|
||||
}
|
||||
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MessageElement::MessageElement(MessageElementFlags flags)
|
||||
|
||||
@@ -23,15 +23,15 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
QColor blendColors(const QColor &base, const QColor &apply)
|
||||
{
|
||||
const qreal &alpha = apply.alphaF();
|
||||
QColor result;
|
||||
result.setRgbF(base.redF() * (1 - alpha) + apply.redF() * alpha,
|
||||
base.greenF() * (1 - alpha) + apply.greenF() * alpha,
|
||||
base.blueF() * (1 - alpha) + apply.blueF() * alpha);
|
||||
return result;
|
||||
}
|
||||
QColor blendColors(const QColor &base, const QColor &apply)
|
||||
{
|
||||
const qreal &alpha = apply.alphaF();
|
||||
QColor result;
|
||||
result.setRgbF(base.redF() * (1 - alpha) + apply.redF() * alpha,
|
||||
base.greenF() * (1 - alpha) + apply.greenF() * alpha,
|
||||
base.blueF() * (1 - alpha) + apply.blueF() * alpha);
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MessageLayout::MessageLayout(MessagePtr message)
|
||||
|
||||
@@ -24,8 +24,8 @@ struct BttvLiveUpdateEmoteRemoveMessage;
|
||||
|
||||
namespace bttv::detail {
|
||||
|
||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot,
|
||||
const QString &channelDisplayName);
|
||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot,
|
||||
const QString &channelDisplayName);
|
||||
|
||||
} // namespace bttv::detail
|
||||
|
||||
|
||||
@@ -27,12 +27,10 @@ using FfzChannelBadgeMap =
|
||||
|
||||
namespace ffz::detail {
|
||||
|
||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot);
|
||||
EmoteMap parseChannelEmotes(const QJsonObject &jsonRoot);
|
||||
|
||||
/**
|
||||
* Parse the `user_badge_ids` into a map of User IDs -> Badge IDs
|
||||
*/
|
||||
FfzChannelBadgeMap parseChannelBadges(const QJsonObject &badgeRoot);
|
||||
/// Parse the `user_badge_ids` into a map of User IDs -> Badge IDs
|
||||
FfzChannelBadgeMap parseChannelBadges(const QJsonObject &badgeRoot);
|
||||
|
||||
} // namespace ffz::detail
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
const auto payload = "chatterino/" + CHATTERINO_VERSION;
|
||||
const auto payload = "chatterino/" + CHATTERINO_VERSION;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ struct BasicPubSubConfig : public websocketpp::config::asio_tls_client {
|
||||
};
|
||||
|
||||
namespace liveupdates {
|
||||
using WebsocketClient = websocketpp::client<chatterino::BasicPubSubConfig>;
|
||||
using WebsocketHandle = websocketpp::connection_hdl;
|
||||
using WebsocketErrorCode = websocketpp::lib::error_code;
|
||||
using WebsocketClient = websocketpp::client<chatterino::BasicPubSubConfig>;
|
||||
using WebsocketHandle = websocketpp::connection_hdl;
|
||||
using WebsocketErrorCode = websocketpp::lib::error_code;
|
||||
} // namespace liveupdates
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -21,9 +21,9 @@ namespace chatterino {
|
||||
class ImageSet;
|
||||
class Channel;
|
||||
namespace seventv::eventapi {
|
||||
struct EmoteAddDispatch;
|
||||
struct EmoteUpdateDispatch;
|
||||
struct EmoteRemoveDispatch;
|
||||
struct EmoteAddDispatch;
|
||||
struct EmoteUpdateDispatch;
|
||||
struct EmoteRemoveDispatch;
|
||||
} // namespace seventv::eventapi
|
||||
|
||||
// https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L29-L36
|
||||
@@ -86,7 +86,7 @@ using SeventvEmoteSetFlags = FlagsEnum<SeventvEmoteSetFlag>;
|
||||
|
||||
namespace seventv::detail {
|
||||
|
||||
EmoteMap parseEmotes(const QJsonArray &emoteSetEmotes, bool isGlobal);
|
||||
EmoteMap parseEmotes(const QJsonArray &emoteSetEmotes, bool isGlobal);
|
||||
|
||||
} // namespace seventv::detail
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
namespace chatterino {
|
||||
|
||||
namespace seventv::eventapi {
|
||||
struct Dispatch;
|
||||
struct EmoteAddDispatch;
|
||||
struct EmoteUpdateDispatch;
|
||||
struct EmoteRemoveDispatch;
|
||||
struct UserConnectionUpdateDispatch;
|
||||
struct CosmeticCreateDispatch;
|
||||
struct EntitlementCreateDeleteDispatch;
|
||||
struct Dispatch;
|
||||
struct EmoteAddDispatch;
|
||||
struct EmoteUpdateDispatch;
|
||||
struct EmoteRemoveDispatch;
|
||||
struct UserConnectionUpdateDispatch;
|
||||
struct CosmeticCreateDispatch;
|
||||
struct EntitlementCreateDeleteDispatch;
|
||||
} // namespace seventv::eventapi
|
||||
|
||||
class SeventvBadges;
|
||||
|
||||
@@ -63,35 +63,34 @@ using namespace literals;
|
||||
|
||||
namespace {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0)
|
||||
const QString MAGIC_MESSAGE_SUFFIX = QString((const char *)u8" \U000E0000");
|
||||
const QString MAGIC_MESSAGE_SUFFIX = QString((const char *)u8" \U000E0000");
|
||||
#else
|
||||
const QString MAGIC_MESSAGE_SUFFIX = QString::fromUtf8(u8" \U000E0000");
|
||||
const QString MAGIC_MESSAGE_SUFFIX = QString::fromUtf8(u8" \U000E0000");
|
||||
#endif
|
||||
constexpr int CLIP_CREATION_COOLDOWN = 5000;
|
||||
const QString CLIPS_LINK("https://clips.twitch.tv/%1");
|
||||
const QString CLIPS_FAILURE_CLIPS_UNAVAILABLE_TEXT(
|
||||
"Failed to create a clip - clips are temporarily unavailable: %1");
|
||||
const QString CLIPS_FAILURE_CLIPS_DISABLED_TEXT(
|
||||
"Failed to create a clip - the streamer has clips disabled in their "
|
||||
"channel.");
|
||||
const QString CLIPS_FAILURE_CLIPS_RESTRICTED_TEXT(
|
||||
"Failed to create a clip - the streamer has restricted clip creation "
|
||||
"to subscribers, or followers of an unknown duration.");
|
||||
const QString CLIPS_FAILURE_CLIPS_RESTRICTED_CATEGORY_TEXT(
|
||||
"Failed to create a clip - the streamer has disabled clips while in "
|
||||
"this category.");
|
||||
const QString CLIPS_FAILURE_NOT_AUTHENTICATED_TEXT(
|
||||
"Failed to create a clip - you need to re-authenticate.");
|
||||
const QString CLIPS_FAILURE_UNKNOWN_ERROR_TEXT(
|
||||
"Failed to create a clip: %1");
|
||||
const QString LOGIN_PROMPT_TEXT("Click here to add your account again.");
|
||||
const Link ACCOUNTS_LINK(Link::OpenAccountsPage, QString());
|
||||
constexpr int CLIP_CREATION_COOLDOWN = 5000;
|
||||
const QString CLIPS_LINK("https://clips.twitch.tv/%1");
|
||||
const QString CLIPS_FAILURE_CLIPS_UNAVAILABLE_TEXT(
|
||||
"Failed to create a clip - clips are temporarily unavailable: %1");
|
||||
const QString CLIPS_FAILURE_CLIPS_DISABLED_TEXT(
|
||||
"Failed to create a clip - the streamer has clips disabled in their "
|
||||
"channel.");
|
||||
const QString CLIPS_FAILURE_CLIPS_RESTRICTED_TEXT(
|
||||
"Failed to create a clip - the streamer has restricted clip creation "
|
||||
"to subscribers, or followers of an unknown duration.");
|
||||
const QString CLIPS_FAILURE_CLIPS_RESTRICTED_CATEGORY_TEXT(
|
||||
"Failed to create a clip - the streamer has disabled clips while in "
|
||||
"this category.");
|
||||
const QString CLIPS_FAILURE_NOT_AUTHENTICATED_TEXT(
|
||||
"Failed to create a clip - you need to re-authenticate.");
|
||||
const QString CLIPS_FAILURE_UNKNOWN_ERROR_TEXT("Failed to create a clip: %1");
|
||||
const QString LOGIN_PROMPT_TEXT("Click here to add your account again.");
|
||||
const Link ACCOUNTS_LINK(Link::OpenAccountsPage, QString());
|
||||
|
||||
// Maximum number of chatters to fetch when refreshing chatters
|
||||
constexpr auto MAX_CHATTERS_TO_FETCH = 5000;
|
||||
// Maximum number of chatters to fetch when refreshing chatters
|
||||
constexpr auto MAX_CHATTERS_TO_FETCH = 5000;
|
||||
|
||||
// From Twitch docs - expected size for a badge (1x)
|
||||
constexpr QSize BASE_BADGE_SIZE(18, 18);
|
||||
// From Twitch docs - expected size for a badge (1x)
|
||||
constexpr QSize BASE_BADGE_SIZE(18, 18);
|
||||
} // namespace
|
||||
|
||||
TwitchChannel::TwitchChannel(const QString &name)
|
||||
|
||||
@@ -45,10 +45,10 @@ struct BttvLiveUpdateEmoteRemoveMessage;
|
||||
|
||||
class SeventvEmotes;
|
||||
namespace seventv::eventapi {
|
||||
struct EmoteAddDispatch;
|
||||
struct EmoteUpdateDispatch;
|
||||
struct EmoteRemoveDispatch;
|
||||
struct UserConnectionUpdateDispatch;
|
||||
struct EmoteAddDispatch;
|
||||
struct EmoteUpdateDispatch;
|
||||
struct EmoteRemoveDispatch;
|
||||
struct UserConnectionUpdateDispatch;
|
||||
} // namespace seventv::eventapi
|
||||
|
||||
struct ChannelPointReward;
|
||||
|
||||
@@ -200,22 +200,22 @@ std::string &getNmQueueName(const Paths &paths)
|
||||
|
||||
namespace nm::client {
|
||||
|
||||
void sendMessage(const QByteArray &array)
|
||||
{
|
||||
ipc::sendMessage("chatterino_gui", array);
|
||||
}
|
||||
void sendMessage(const QByteArray &array)
|
||||
{
|
||||
ipc::sendMessage("chatterino_gui", array);
|
||||
}
|
||||
|
||||
void writeToCout(const QByteArray &array)
|
||||
{
|
||||
const auto *data = array.data();
|
||||
auto size = uint32_t(array.size());
|
||||
void writeToCout(const QByteArray &array)
|
||||
{
|
||||
const auto *data = array.data();
|
||||
auto size = uint32_t(array.size());
|
||||
|
||||
// We're writing the raw bytes to cout.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
std::cout.write(reinterpret_cast<char *>(&size), 4);
|
||||
std::cout.write(data, size);
|
||||
std::cout.flush();
|
||||
}
|
||||
// We're writing the raw bytes to cout.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
std::cout.write(reinterpret_cast<char *>(&size), 4);
|
||||
std::cout.write(data, size);
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
} // namespace nm::client
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ Atomic<std::optional<QString>> &nmIpcError();
|
||||
|
||||
namespace nm::client {
|
||||
|
||||
void sendMessage(const QByteArray &array);
|
||||
void writeToCout(const QByteArray &array);
|
||||
void sendMessage(const QByteArray &array);
|
||||
void writeToCout(const QByteArray &array);
|
||||
|
||||
} // namespace nm::client
|
||||
|
||||
|
||||
@@ -7,15 +7,15 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
void appendDuration(int count, QChar &&suffix, QString &out)
|
||||
void appendDuration(int count, QChar &&suffix, QString &out)
|
||||
{
|
||||
if (!out.isEmpty())
|
||||
{
|
||||
if (!out.isEmpty())
|
||||
{
|
||||
out.append(' ');
|
||||
}
|
||||
out.append(QString::number(count));
|
||||
out.append(suffix);
|
||||
out.append(' ');
|
||||
}
|
||||
out.append(QString::number(count));
|
||||
out.append(suffix);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
+86
-86
@@ -33,105 +33,105 @@ namespace chatterino {
|
||||
|
||||
namespace helpers::detail {
|
||||
|
||||
SizeType skipSpace(QStringView view, SizeType startPos)
|
||||
SizeType skipSpace(QStringView view, SizeType startPos)
|
||||
{
|
||||
while (startPos < view.length() && view.at(startPos).isSpace())
|
||||
{
|
||||
while (startPos < view.length() && view.at(startPos).isSpace())
|
||||
{
|
||||
startPos++;
|
||||
}
|
||||
return startPos - 1;
|
||||
startPos++;
|
||||
}
|
||||
return startPos - 1;
|
||||
}
|
||||
|
||||
bool matchesIgnorePlural(QStringView word, const QString &expected)
|
||||
bool matchesIgnorePlural(QStringView word, const QString &expected)
|
||||
{
|
||||
if (!word.startsWith(expected))
|
||||
{
|
||||
if (!word.startsWith(expected))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (word.length() == expected.length())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return word.length() == expected.length() + 1 &&
|
||||
word.at(word.length() - 1).toLatin1() == 's';
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<uint64_t, bool> findUnitMultiplierToSec(QStringView view,
|
||||
SizeType &pos)
|
||||
if (word.length() == expected.length())
|
||||
{
|
||||
// Step 1. find end of unit
|
||||
auto startIdx = pos;
|
||||
auto endIdx = view.length();
|
||||
for (; pos < view.length(); pos++)
|
||||
{
|
||||
auto c = view.at(pos);
|
||||
if (c.isSpace() || c.isDigit())
|
||||
{
|
||||
endIdx = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
return true;
|
||||
}
|
||||
return word.length() == expected.length() + 1 &&
|
||||
word.at(word.length() - 1).toLatin1() == 's';
|
||||
}
|
||||
|
||||
// TODO(QT6): use sliced (more readable)
|
||||
auto unit = view.mid(startIdx, endIdx - startIdx);
|
||||
if (unit.isEmpty())
|
||||
std::pair<uint64_t, bool> findUnitMultiplierToSec(QStringView view,
|
||||
SizeType &pos)
|
||||
{
|
||||
// Step 1. find end of unit
|
||||
auto startIdx = pos;
|
||||
auto endIdx = view.length();
|
||||
for (; pos < view.length(); pos++)
|
||||
{
|
||||
auto c = view.at(pos);
|
||||
if (c.isSpace() || c.isDigit())
|
||||
{
|
||||
return std::make_pair(0, false);
|
||||
endIdx = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
|
||||
auto first = unit.at(0).toLatin1();
|
||||
switch (first)
|
||||
{
|
||||
case 's': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("second")))
|
||||
{
|
||||
return std::make_pair(1, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'm': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("minute")))
|
||||
{
|
||||
return std::make_pair(60, true);
|
||||
}
|
||||
if ((unit.length() == 2 && unit.at(1).toLatin1() == 'o') ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("month")))
|
||||
{
|
||||
return std::make_pair(60 * 60 * 24 * 30, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'h': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("hour")))
|
||||
{
|
||||
return std::make_pair(60 * 60, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'd': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("day")))
|
||||
{
|
||||
return std::make_pair(60 * 60 * 24, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'w': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("week")))
|
||||
{
|
||||
return std::make_pair(60 * 60 * 24 * 7, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// TODO(QT6): use sliced (more readable)
|
||||
auto unit = view.mid(startIdx, endIdx - startIdx);
|
||||
if (unit.isEmpty())
|
||||
{
|
||||
return std::make_pair(0, false);
|
||||
}
|
||||
|
||||
auto first = unit.at(0).toLatin1();
|
||||
switch (first)
|
||||
{
|
||||
case 's': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("second")))
|
||||
{
|
||||
return std::make_pair(1, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'm': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("minute")))
|
||||
{
|
||||
return std::make_pair(60, true);
|
||||
}
|
||||
if ((unit.length() == 2 && unit.at(1).toLatin1() == 'o') ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("month")))
|
||||
{
|
||||
return std::make_pair(60 * 60 * 24 * 30, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'h': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("hour")))
|
||||
{
|
||||
return std::make_pair(60 * 60, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'd': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("day")))
|
||||
{
|
||||
return std::make_pair(60 * 60 * 24, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'w': {
|
||||
if (unit.length() == 1 ||
|
||||
matchesIgnorePlural(unit, QStringLiteral("week")))
|
||||
{
|
||||
return std::make_pair(60 * 60 * 24 * 7, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return std::make_pair(0, false);
|
||||
}
|
||||
|
||||
} // namespace helpers::detail
|
||||
using namespace helpers::detail;
|
||||
|
||||
|
||||
+35
-35
@@ -18,44 +18,44 @@ namespace chatterino {
|
||||
// only qualified for tests
|
||||
namespace helpers::detail {
|
||||
|
||||
using SizeType = QStringView::size_type;
|
||||
using SizeType = QStringView::size_type;
|
||||
|
||||
/**
|
||||
* Skips all spaces.
|
||||
* The caller must guarantee view.at(startPos).isSpace().
|
||||
*
|
||||
* @param view The string to skip spaces in.
|
||||
* @param startPos The starting position (there must be a space in the view).
|
||||
* @return The position of the last space.
|
||||
*/
|
||||
SizeType skipSpace(QStringView view, SizeType startPos);
|
||||
/**
|
||||
* Skips all spaces.
|
||||
* The caller must guarantee view.at(startPos).isSpace().
|
||||
*
|
||||
* @param view The string to skip spaces in.
|
||||
* @param startPos The starting position (there must be a space in the view).
|
||||
* @return The position of the last space.
|
||||
*/
|
||||
SizeType skipSpace(QStringView view, SizeType startPos);
|
||||
|
||||
/**
|
||||
* Checks if `word` equals `expected` (singular) or `expected` + 's' (plural).
|
||||
*
|
||||
* @param word Word to test. Must not be empty.
|
||||
* @param expected Singular of the expected word.
|
||||
* @return true if `word` is singular or plural of `expected`.
|
||||
*/
|
||||
bool matchesIgnorePlural(QStringView word, const QString &expected);
|
||||
/**
|
||||
* Checks if `word` equals `expected` (singular) or `expected` + 's' (plural).
|
||||
*
|
||||
* @param word Word to test. Must not be empty.
|
||||
* @param expected Singular of the expected word.
|
||||
* @return true if `word` is singular or plural of `expected`.
|
||||
*/
|
||||
bool matchesIgnorePlural(QStringView word, const QString &expected);
|
||||
|
||||
/**
|
||||
* Tries to find the unit starting at `pos` and returns its multiplier so
|
||||
* `valueInUnit * multiplier = valueInSeconds` (e.g. 60 for minutes).
|
||||
*
|
||||
* Supported units are
|
||||
* 'w[eek(s)]', 'd[ay(s)]',
|
||||
* 'h[our(s)]', 'm[inute(s)]', 's[econd(s)]'.
|
||||
* The unit must be in lowercase.
|
||||
*
|
||||
* @param view A view into a string
|
||||
* @param pos The starting position.
|
||||
* This is set to the last position of the unit
|
||||
* if it's a valid unit, undefined otherwise.
|
||||
* @return (multiplier, ok)
|
||||
*/
|
||||
std::pair<uint64_t, bool> findUnitMultiplierToSec(QStringView view,
|
||||
SizeType &pos);
|
||||
/**
|
||||
* Tries to find the unit starting at `pos` and returns its multiplier so
|
||||
* `valueInUnit * multiplier = valueInSeconds` (e.g. 60 for minutes).
|
||||
*
|
||||
* Supported units are
|
||||
* 'w[eek(s)]', 'd[ay(s)]',
|
||||
* 'h[our(s)]', 'm[inute(s)]', 's[econd(s)]'.
|
||||
* The unit must be in lowercase.
|
||||
*
|
||||
* @param view A view into a string
|
||||
* @param pos The starting position.
|
||||
* This is set to the last position of the unit
|
||||
* if it's a valid unit, undefined otherwise.
|
||||
* @return (multiplier, ok)
|
||||
*/
|
||||
std::pair<uint64_t, bool> findUnitMultiplierToSec(QStringView view,
|
||||
SizeType &pos);
|
||||
|
||||
} // namespace helpers::detail
|
||||
|
||||
|
||||
@@ -5,45 +5,43 @@
|
||||
namespace chatterino {
|
||||
namespace rj {
|
||||
|
||||
void addMember(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &&value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &&value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
obj.AddMember(rapidjson::Value(key, a).Move(), value, a);
|
||||
}
|
||||
|
||||
void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
obj.AddMember(rapidjson::Value(key, a).Move(), value.Move(), a);
|
||||
}
|
||||
|
||||
QString stringify(const rapidjson::Value &value)
|
||||
{
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
value.Accept(writer);
|
||||
|
||||
return buffer.GetString();
|
||||
}
|
||||
|
||||
bool getSafeObject(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &out)
|
||||
{
|
||||
if (!checkJsonValue(obj, key))
|
||||
{
|
||||
obj.AddMember(rapidjson::Value(key, a).Move(), value, a);
|
||||
return false;
|
||||
}
|
||||
|
||||
void addMember(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
obj.AddMember(rapidjson::Value(key, a).Move(), value.Move(), a);
|
||||
}
|
||||
out = obj[key].Move();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString stringify(const rapidjson::Value &value)
|
||||
{
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
value.Accept(writer);
|
||||
|
||||
return buffer.GetString();
|
||||
}
|
||||
|
||||
bool getSafeObject(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &out)
|
||||
{
|
||||
if (!checkJsonValue(obj, key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
out = obj[key].Move();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool checkJsonValue(const rapidjson::Value &obj, const char *key)
|
||||
{
|
||||
return obj.IsObject() && !obj.IsNull() && obj.HasMember(key);
|
||||
}
|
||||
bool checkJsonValue(const rapidjson::Value &obj, const char *key)
|
||||
{
|
||||
return obj.IsObject() && !obj.IsNull() && obj.HasMember(key);
|
||||
}
|
||||
|
||||
} // namespace rj
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -11,91 +11,89 @@
|
||||
namespace chatterino {
|
||||
namespace rj {
|
||||
|
||||
void addMember(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &&value,
|
||||
rapidjson::Document::AllocatorType &a);
|
||||
void addMember(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &value,
|
||||
rapidjson::Document::AllocatorType &a);
|
||||
void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &&value,
|
||||
rapidjson::Document::AllocatorType &a);
|
||||
void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &value,
|
||||
rapidjson::Document::AllocatorType &a);
|
||||
|
||||
template <typename Type>
|
||||
void set(rapidjson::Value &obj, const char *key, const Type &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
template <typename Type>
|
||||
void set(rapidjson::Value &obj, const char *key, const Type &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
|
||||
addMember(obj, key, pajlada::Serialize<Type>::get(value, a), a);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void set(rapidjson::Value &obj, const char *key,
|
||||
const rapidjson::Value &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
|
||||
addMember(obj, key, const_cast<rapidjson::Value &>(value), a);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void set(rapidjson::Document &obj, const char *key, const Type &value)
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
|
||||
auto &a = obj.GetAllocator();
|
||||
|
||||
addMember(obj, key, pajlada::Serialize<Type>::get(value, a), a);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void set(rapidjson::Document &obj, const char *key,
|
||||
const rapidjson::Value &value)
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
|
||||
auto &a = obj.GetAllocator();
|
||||
|
||||
addMember(obj, key, const_cast<rapidjson::Value &>(value), a);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void add(rapidjson::Value &arr, const Type &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
assert(arr.IsArray());
|
||||
|
||||
arr.PushBack(pajlada::Serialize<Type>::get(value, a), a);
|
||||
}
|
||||
|
||||
bool checkJsonValue(const rapidjson::Value &obj, const char *key);
|
||||
|
||||
template <typename Type>
|
||||
bool getSafe(const rapidjson::Value &obj, const char *key, Type &out)
|
||||
{
|
||||
if (!checkJsonValue(obj, key))
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
|
||||
addMember(obj, key, pajlada::Serialize<Type>::get(value, a), a);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void set(rapidjson::Value &obj, const char *key,
|
||||
const rapidjson::Value &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
bool error = false;
|
||||
out = pajlada::Deserialize<Type>::get(obj[key], &error);
|
||||
|
||||
addMember(obj, key, const_cast<rapidjson::Value &>(value), a);
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void set(rapidjson::Document &obj, const char *key, const Type &value)
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
template <typename Type>
|
||||
bool getSafe(const rapidjson::Value &value, Type &out)
|
||||
{
|
||||
bool error = false;
|
||||
out = pajlada::Deserialize<Type>::get(value, &error);
|
||||
|
||||
auto &a = obj.GetAllocator();
|
||||
return !error;
|
||||
}
|
||||
|
||||
addMember(obj, key, pajlada::Serialize<Type>::get(value, a), a);
|
||||
}
|
||||
bool getSafeObject(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &out);
|
||||
|
||||
template <>
|
||||
inline void set(rapidjson::Document &obj, const char *key,
|
||||
const rapidjson::Value &value)
|
||||
{
|
||||
assert(obj.IsObject());
|
||||
|
||||
auto &a = obj.GetAllocator();
|
||||
|
||||
addMember(obj, key, const_cast<rapidjson::Value &>(value), a);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void add(rapidjson::Value &arr, const Type &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
assert(arr.IsArray());
|
||||
|
||||
arr.PushBack(pajlada::Serialize<Type>::get(value, a), a);
|
||||
}
|
||||
|
||||
bool checkJsonValue(const rapidjson::Value &obj, const char *key);
|
||||
|
||||
template <typename Type>
|
||||
bool getSafe(const rapidjson::Value &obj, const char *key, Type &out)
|
||||
{
|
||||
if (!checkJsonValue(obj, key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
out = pajlada::Deserialize<Type>::get(obj[key], &error);
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
bool getSafe(const rapidjson::Value &value, Type &out)
|
||||
{
|
||||
bool error = false;
|
||||
out = pajlada::Deserialize<Type>::get(value, &error);
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool getSafeObject(rapidjson::Value &obj, const char *key,
|
||||
rapidjson::Value &out);
|
||||
|
||||
QString stringify(const rapidjson::Value &value);
|
||||
QString stringify(const rapidjson::Value &value);
|
||||
|
||||
} // namespace rj
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace chatterino {
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
namespace windows::detail {
|
||||
void renameThread(void *hThread, const QString &name);
|
||||
void renameThread(void *hThread, const QString &name);
|
||||
} // namespace windows::detail
|
||||
#endif
|
||||
|
||||
|
||||
+9
-9
@@ -11,16 +11,16 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
const auto TWITCH_USER_LOGIN_PATTERN = R"(^[a-z0-9]\w{0,24}$)";
|
||||
const auto TWITCH_USER_LOGIN_PATTERN = R"(^[a-z0-9]\w{0,24}$)";
|
||||
|
||||
// Remember to keep VALID_HELIX_COLORS up-to-date if a new color is implemented to keep naming for users consistent
|
||||
const std::unordered_map<QString, QString> HELIX_COLOR_REPLACEMENTS{
|
||||
{"blueviolet", "blue_violet"}, {"cadetblue", "cadet_blue"},
|
||||
{"dodgerblue", "dodger_blue"}, {"goldenrod", "golden_rod"},
|
||||
{"hotpink", "hot_pink"}, {"orangered", "orange_red"},
|
||||
{"seagreen", "sea_green"}, {"springgreen", "spring_green"},
|
||||
{"yellowgreen", "yellow_green"},
|
||||
};
|
||||
// Remember to keep VALID_HELIX_COLORS up-to-date if a new color is implemented to keep naming for users consistent
|
||||
const std::unordered_map<QString, QString> HELIX_COLOR_REPLACEMENTS{
|
||||
{"blueviolet", "blue_violet"}, {"cadetblue", "cadet_blue"},
|
||||
{"dodgerblue", "dodger_blue"}, {"goldenrod", "golden_rod"},
|
||||
{"hotpink", "hot_pink"}, {"orangered", "orange_red"},
|
||||
{"seagreen", "sea_green"}, {"springgreen", "spring_green"},
|
||||
{"yellowgreen", "yellow_green"},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -13,23 +13,23 @@ namespace chatterino {
|
||||
namespace {
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
FlagsEnum<BaseWindow::Flags> popupFlags{
|
||||
BaseWindow::Dialog,
|
||||
BaseWindow::EnableCustomFrame,
|
||||
};
|
||||
FlagsEnum<BaseWindow::Flags> popupFlagsCloseAutomatically{
|
||||
BaseWindow::Dialog,
|
||||
BaseWindow::EnableCustomFrame,
|
||||
};
|
||||
FlagsEnum<BaseWindow::Flags> popupFlags{
|
||||
BaseWindow::Dialog,
|
||||
BaseWindow::EnableCustomFrame,
|
||||
};
|
||||
FlagsEnum<BaseWindow::Flags> popupFlagsCloseAutomatically{
|
||||
BaseWindow::Dialog,
|
||||
BaseWindow::EnableCustomFrame,
|
||||
};
|
||||
#else
|
||||
FlagsEnum<BaseWindow::Flags> popupFlags{
|
||||
BaseWindow::EnableCustomFrame,
|
||||
};
|
||||
FlagsEnum<BaseWindow::Flags> popupFlagsCloseAutomatically{
|
||||
BaseWindow::EnableCustomFrame,
|
||||
BaseWindow::Frameless,
|
||||
BaseWindow::FramelessDraggable,
|
||||
};
|
||||
FlagsEnum<BaseWindow::Flags> popupFlags{
|
||||
BaseWindow::EnableCustomFrame,
|
||||
};
|
||||
FlagsEnum<BaseWindow::Flags> popupFlagsCloseAutomatically{
|
||||
BaseWindow::EnableCustomFrame,
|
||||
BaseWindow::Frameless,
|
||||
BaseWindow::FramelessDraggable,
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -8,14 +8,13 @@
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
const QStringList friendlyBinaryOps = {
|
||||
"and", "or", "+", "-", "*", "/",
|
||||
"%", "equals", "not equals", "<", ">", "<=",
|
||||
">=", "contains", "starts with", "ends with", "(nothing)"};
|
||||
const QStringList realBinaryOps = {
|
||||
"&&", "||", "+", "-", "*", "/",
|
||||
"%", "==", "!=", "<", ">", "<=",
|
||||
">=", "contains", "startswith", "endswith", ""};
|
||||
const QStringList friendlyBinaryOps = {
|
||||
"and", "or", "+", "-", "*", "/",
|
||||
"%", "equals", "not equals", "<", ">", "<=",
|
||||
">=", "contains", "starts with", "ends with", "(nothing)"};
|
||||
const QStringList realBinaryOps = {
|
||||
"&&", "||", "+", "-", "*", "/", "%", "==", "!=",
|
||||
"<", ">", "<=", ">=", "contains", "startswith", "endswith", ""};
|
||||
} // namespace
|
||||
|
||||
ChannelFilterEditorDialog::ChannelFilterEditorDialog(QWidget *parent)
|
||||
|
||||
@@ -25,53 +25,51 @@ namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
bool logInWithCredentials(QWidget *parent, const QString &userID,
|
||||
const QString &username, const QString &clientID,
|
||||
const QString &oauthToken)
|
||||
bool logInWithCredentials(QWidget *parent, const QString &userID,
|
||||
const QString &username, const QString &clientID,
|
||||
const QString &oauthToken)
|
||||
{
|
||||
QStringList errors;
|
||||
|
||||
if (userID.isEmpty())
|
||||
{
|
||||
QStringList errors;
|
||||
|
||||
if (userID.isEmpty())
|
||||
{
|
||||
errors.append("Missing user ID");
|
||||
}
|
||||
if (username.isEmpty())
|
||||
{
|
||||
errors.append("Missing username");
|
||||
}
|
||||
if (clientID.isEmpty())
|
||||
{
|
||||
errors.append("Missing Client ID");
|
||||
}
|
||||
if (oauthToken.isEmpty())
|
||||
{
|
||||
errors.append("Missing OAuth Token");
|
||||
}
|
||||
|
||||
if (errors.length() > 0)
|
||||
{
|
||||
QMessageBox messageBox(parent);
|
||||
messageBox.setWindowTitle("Invalid account credentials");
|
||||
messageBox.setIcon(QMessageBox::Critical);
|
||||
messageBox.setText(errors.join("<br>"));
|
||||
messageBox.exec();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string basePath = "/accounts/uid" + userID.toStdString();
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/username",
|
||||
username);
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/userID", userID);
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/clientID",
|
||||
clientID);
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/oauthToken",
|
||||
oauthToken);
|
||||
|
||||
getApp()->getAccounts()->twitch.reloadUsers();
|
||||
getApp()->getAccounts()->twitch.currentUsername = username;
|
||||
getSettings()->requestSave();
|
||||
return true;
|
||||
errors.append("Missing user ID");
|
||||
}
|
||||
if (username.isEmpty())
|
||||
{
|
||||
errors.append("Missing username");
|
||||
}
|
||||
if (clientID.isEmpty())
|
||||
{
|
||||
errors.append("Missing Client ID");
|
||||
}
|
||||
if (oauthToken.isEmpty())
|
||||
{
|
||||
errors.append("Missing OAuth Token");
|
||||
}
|
||||
|
||||
if (errors.length() > 0)
|
||||
{
|
||||
QMessageBox messageBox(parent);
|
||||
messageBox.setWindowTitle("Invalid account credentials");
|
||||
messageBox.setIcon(QMessageBox::Critical);
|
||||
messageBox.setText(errors.join("<br>"));
|
||||
messageBox.exec();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string basePath = "/accounts/uid" + userID.toStdString();
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/username", username);
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/userID", userID);
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/clientID", clientID);
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/oauthToken",
|
||||
oauthToken);
|
||||
|
||||
getApp()->getAccounts()->twitch.reloadUsers();
|
||||
getApp()->getAccounts()->twitch.currentUsername = username;
|
||||
getSettings()->requestSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -33,52 +33,52 @@
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
// Translates the given rectangle by an amount in the direction to appear like the tab is selected.
|
||||
// For example, if location is Top, the rectangle will be translated in the negative Y direction,
|
||||
// or "up" on the screen, by amount.
|
||||
void translateRectForLocation(QRect &rect, NotebookTabLocation location,
|
||||
int amount)
|
||||
// Translates the given rectangle by an amount in the direction to appear like the tab is selected.
|
||||
// For example, if location is Top, the rectangle will be translated in the negative Y direction,
|
||||
// or "up" on the screen, by amount.
|
||||
void translateRectForLocation(QRect &rect, NotebookTabLocation location,
|
||||
int amount)
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
case NotebookTabLocation::Top:
|
||||
rect.translate(0, -amount);
|
||||
break;
|
||||
case NotebookTabLocation::Left:
|
||||
rect.translate(-amount, 0);
|
||||
break;
|
||||
case NotebookTabLocation::Right:
|
||||
rect.translate(amount, 0);
|
||||
break;
|
||||
case NotebookTabLocation::Bottom:
|
||||
rect.translate(0, amount);
|
||||
break;
|
||||
}
|
||||
case NotebookTabLocation::Top:
|
||||
rect.translate(0, -amount);
|
||||
break;
|
||||
case NotebookTabLocation::Left:
|
||||
rect.translate(-amount, 0);
|
||||
break;
|
||||
case NotebookTabLocation::Right:
|
||||
rect.translate(amount, 0);
|
||||
break;
|
||||
case NotebookTabLocation::Bottom:
|
||||
rect.translate(0, amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float getCompactDivider(TabStyle tabStyle)
|
||||
float getCompactDivider(TabStyle tabStyle)
|
||||
{
|
||||
switch (tabStyle)
|
||||
{
|
||||
switch (tabStyle)
|
||||
{
|
||||
case TabStyle::Compact:
|
||||
return 1.5;
|
||||
case TabStyle::Normal:
|
||||
default:
|
||||
return 1.0;
|
||||
}
|
||||
case TabStyle::Compact:
|
||||
return 1.5;
|
||||
case TabStyle::Normal:
|
||||
default:
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
float getCompactReducer(TabStyle tabStyle)
|
||||
float getCompactReducer(TabStyle tabStyle)
|
||||
{
|
||||
switch (tabStyle)
|
||||
{
|
||||
switch (tabStyle)
|
||||
{
|
||||
case TabStyle::Compact:
|
||||
return 4.0;
|
||||
case TabStyle::Normal:
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
case TabStyle::Compact:
|
||||
return 4.0;
|
||||
case TabStyle::Normal:
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NotebookTab::NotebookTab(Notebook *notebook)
|
||||
|
||||
@@ -27,19 +27,19 @@
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
// Add additional badges for highlights here
|
||||
QList<DisplayBadge> availableBadges = {
|
||||
{"Broadcaster", "broadcaster"},
|
||||
{"Admin", "admin"},
|
||||
{"Staff", "staff"},
|
||||
{"Moderator", "moderator"},
|
||||
{"Verified", "partner"},
|
||||
{"VIP", "vip"},
|
||||
{"Founder", "founder"},
|
||||
{"Subscriber", "subscriber"},
|
||||
{"Predicted Blue", "predictions/blue-1,predictions/blue-2"},
|
||||
{"Predicted Pink", "predictions/pink-2,predictions/pink-1"},
|
||||
};
|
||||
// Add additional badges for highlights here
|
||||
QList<DisplayBadge> availableBadges = {
|
||||
{"Broadcaster", "broadcaster"},
|
||||
{"Admin", "admin"},
|
||||
{"Staff", "staff"},
|
||||
{"Moderator", "moderator"},
|
||||
{"Verified", "partner"},
|
||||
{"VIP", "vip"},
|
||||
{"Founder", "founder"},
|
||||
{"Subscriber", "subscriber"},
|
||||
{"Predicted Blue", "predictions/blue-1,predictions/blue-2"},
|
||||
{"Predicted Pink", "predictions/pink-2,predictions/pink-1"},
|
||||
};
|
||||
} // namespace
|
||||
|
||||
HighlightingPage::HighlightingPage()
|
||||
|
||||
@@ -183,28 +183,28 @@ QString formatChattersError(HelixGetChattersError error, const QString &message)
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
void showTutorialVideo(QWidget *parent, const QString &source,
|
||||
const QString &title, const QString &description)
|
||||
{
|
||||
auto *window = new BasePopup(
|
||||
{
|
||||
BaseWindow::EnableCustomFrame,
|
||||
BaseWindow::BoundsCheckOnShow,
|
||||
},
|
||||
parent);
|
||||
window->setWindowTitle("Chatterino - " + title);
|
||||
window->setAttribute(Qt::WA_DeleteOnClose);
|
||||
auto *layout = new QVBoxLayout();
|
||||
layout->addWidget(new QLabel(description));
|
||||
auto *label = new QLabel(window);
|
||||
layout->addWidget(label);
|
||||
auto *movie = new QMovie(label);
|
||||
movie->setFileName(source);
|
||||
label->setMovie(movie);
|
||||
movie->start();
|
||||
window->getLayoutContainer()->setLayout(layout);
|
||||
window->show();
|
||||
}
|
||||
void showTutorialVideo(QWidget *parent, const QString &source,
|
||||
const QString &title, const QString &description)
|
||||
{
|
||||
auto *window = new BasePopup(
|
||||
{
|
||||
BaseWindow::EnableCustomFrame,
|
||||
BaseWindow::BoundsCheckOnShow,
|
||||
},
|
||||
parent);
|
||||
window->setWindowTitle("Chatterino - " + title);
|
||||
window->setAttribute(Qt::WA_DeleteOnClose);
|
||||
auto *layout = new QVBoxLayout();
|
||||
layout->addWidget(new QLabel(description));
|
||||
auto *label = new QLabel(window);
|
||||
layout->addWidget(label);
|
||||
auto *movie = new QMovie(label);
|
||||
movie->setFileName(source);
|
||||
label->setMovie(movie);
|
||||
movie->start();
|
||||
window->getLayoutContainer()->setLayout(layout);
|
||||
window->show();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
pajlada::Signals::Signal<Qt::KeyboardModifiers> Split::modifierStatusChanged;
|
||||
|
||||
Reference in New Issue
Block a user