fix: don't indent inner namespaces (#6235)

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