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
- 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+71 -71
View File
@@ -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 <>
+1 -1
View File
@@ -37,7 +37,7 @@ struct ChannelPointReward;
struct TwitchEmoteOccurrence;
namespace linkparser {
struct Parsed;
struct Parsed;
} // namespace linkparser
struct SystemMessageTag {
+12 -12
View File
@@ -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)
+9 -9
View File
@@ -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)
+2 -2
View File
@@ -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
+3 -5
View File
@@ -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
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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
+7 -7
View File
@@ -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;
+24 -25
View File
@@ -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)
+4 -4
View File
@@ -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;
+14 -14
View File
@@ -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
+2 -2
View File
@@ -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 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
+33 -35
View File
@@ -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
+74 -76
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
+16 -16
View File
@@ -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)
+43 -45
View File
@@ -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
+38 -38
View File
@@ -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)
+13 -13
View File
@@ -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()
+22 -22
View File
@@ -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;