changed .clang-format

This commit is contained in:
fourtf 2018-08-15 22:46:20 +02:00
parent 44c16f1b3a
commit af7b742a23
46 changed files with 1636 additions and 1611 deletions

View file

@ -25,6 +25,9 @@ DerivePointerBinding: false
FixNamespaceComments: true FixNamespaceComments: true
IndentCaseLabels: true IndentCaseLabels: true
IndentWidth: 4 IndentWidth: 4
IndentWrappedFunctionNames: true
IndentPPDirectives: AfterHash
NamespaceIndentation: Inner
PointerBindsToType: false PointerBindsToType: false
SpacesBeforeTrailingComments: 2 SpacesBeforeTrailingComments: 2
Standard: Auto Standard: Auto

View file

@ -99,15 +99,15 @@ void Application::save()
void Application::initNm() void Application::initNm()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#ifdef QT_DEBUG # ifdef QT_DEBUG
#ifdef C_DEBUG_NM # ifdef C_DEBUG_NM
this->nativeMessaging->registerHost(); this->nativeMessaging->registerHost();
this->nativeMessaging->openGuiMessageQueue(); this->nativeMessaging->openGuiMessageQueue();
#endif # endif
#else # else
this->nativeMessaging->registerHost(); this->nativeMessaging->registerHost();
this->nativeMessaging->openGuiMessageQueue(); this->nativeMessaging->openGuiMessageQueue();
#endif # endif
#endif #endif
} }

View file

@ -9,31 +9,31 @@
#include <memory> #include <memory>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <fcntl.h> # include <fcntl.h>
#include <io.h> # include <io.h>
#include <stdio.h> # include <stdio.h>
#endif #endif
namespace chatterino { namespace chatterino {
namespace { namespace {
void initFileMode() void initFileMode()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY);
#endif #endif
} }
void runLoop(NativeMessagingClient &client) void runLoop(NativeMessagingClient &client)
{ {
while (true) { while (true) {
char size_c[4]; char size_c[4];
std::cin.read(size_c, 4); std::cin.read(size_c, 4);
if (std::cin.eof()) break; if (std::cin.eof()) break;
auto size = *reinterpret_cast<uint32_t *>(size_c); auto size = *reinterpret_cast<uint32_t *>(size_c);
#if 0 #if 0
bool bigEndian = isBigEndian(); bool bigEndian = isBigEndian();
@ -48,14 +48,14 @@ void runLoop(NativeMessagingClient &client)
} }
#endif #endif
std::unique_ptr<char[]> buffer(new char[size + 1]); std::unique_ptr<char[]> buffer(new char[size + 1]);
std::cin.read(buffer.get(), size); std::cin.read(buffer.get(), size);
*(buffer.get() + size) = '\0'; *(buffer.get() + size) = '\0';
client.sendMessage( client.sendMessage(QByteArray::fromRawData(
QByteArray::fromRawData(buffer.get(), static_cast<int32_t>(size))); buffer.get(), static_cast<int32_t>(size)));
}
} }
}
} // namespace } // namespace
bool shouldRunBrowserExtensionHost(const QStringList &args) bool shouldRunBrowserExtensionHost(const QStringList &args)

View file

@ -1,168 +1,168 @@
#ifdef __cplusplus #ifdef __cplusplus
#include <fmt/format.h> # include <fmt/format.h>
#include <irccommand.h> # include <irccommand.h>
#include <ircconnection.h> # include <ircconnection.h>
#include <rapidjson/document.h> # include <rapidjson/document.h>
#include <rapidjson/error/en.h> # include <rapidjson/error/en.h>
#include <rapidjson/error/error.h> # include <rapidjson/error/error.h>
#include <IrcMessage> # include <IrcMessage>
#include <QAbstractListModel> # include <QAbstractListModel>
#include <QAbstractNativeEventFilter> # include <QAbstractNativeEventFilter>
#include <QAction> # include <QAction>
#include <QApplication> # include <QApplication>
#include <QBrush> # include <QBrush>
#include <QBuffer> # include <QBuffer>
#include <QButtonGroup> # include <QButtonGroup>
#include <QByteArray> # include <QByteArray>
#include <QCheckBox> # include <QCheckBox>
#include <QClipboard> # include <QClipboard>
#include <QColor> # include <QColor>
#include <QComboBox> # include <QComboBox>
#include <QCompleter> # include <QCompleter>
#include <QCoreApplication> # include <QCoreApplication>
#include <QDateTime> # include <QDateTime>
#include <QDebug> # include <QDebug>
#include <QDesktopServices> # include <QDesktopServices>
#include <QDialog> # include <QDialog>
#include <QDialogButtonBox> # include <QDialogButtonBox>
#include <QDir> # include <QDir>
#include <QDockWidget> # include <QDockWidget>
#include <QDrag> # include <QDrag>
#include <QDragEnterEvent> # include <QDragEnterEvent>
#include <QElapsedTimer> # include <QElapsedTimer>
#include <QEventLoop> # include <QEventLoop>
#include <QFile> # include <QFile>
#include <QFileDialog> # include <QFileDialog>
#include <QFileInfo> # include <QFileInfo>
#include <QFlags> # include <QFlags>
#include <QFont> # include <QFont>
#include <QFontDatabase> # include <QFontDatabase>
#include <QFontDialog> # include <QFontDialog>
#include <QFontMetrics> # include <QFontMetrics>
#include <QFormLayout> # include <QFormLayout>
#include <QGraphicsBlurEffect> # include <QGraphicsBlurEffect>
#include <QGroupBox> # include <QGroupBox>
#include <QHBoxLayout> # include <QHBoxLayout>
#include <QHeaderView> # include <QHeaderView>
#include <QIcon> # include <QIcon>
#include <QImageReader> # include <QImageReader>
#include <QJsonArray> # include <QJsonArray>
#include <QJsonDocument> # include <QJsonDocument>
#include <QJsonObject> # include <QJsonObject>
#include <QJsonValue> # include <QJsonValue>
#include <QKeyEvent> # include <QKeyEvent>
#include <QLabel> # include <QLabel>
#include <QLayout> # include <QLayout>
#include <QLibrary> # include <QLibrary>
#include <QLineEdit> # include <QLineEdit>
#include <QList> # include <QList>
#include <QListView> # include <QListView>
#include <QListWidget> # include <QListWidget>
#include <QMap> # include <QMap>
#include <QMediaPlayer> # include <QMediaPlayer>
#include <QMenu> # include <QMenu>
#include <QMessageBox> # include <QMessageBox>
#include <QMimeData> # include <QMimeData>
#include <QMouseEvent> # include <QMouseEvent>
#include <QMutex> # include <QMutex>
#include <QMutexLocker> # include <QMutexLocker>
#include <QNetworkAccessManager> # include <QNetworkAccessManager>
#include <QNetworkReply> # include <QNetworkReply>
#include <QNetworkRequest> # include <QNetworkRequest>
#include <QObject> # include <QObject>
#include <QPaintEvent> # include <QPaintEvent>
#include <QPainter> # include <QPainter>
#include <QPainterPath> # include <QPainterPath>
#include <QPalette> # include <QPalette>
#include <QPixmap> # include <QPixmap>
#include <QPoint> # include <QPoint>
#include <QProcess> # include <QProcess>
#include <QPropertyAnimation> # include <QPropertyAnimation>
#include <QPushButton> # include <QPushButton>
#include <QRadialGradient> # include <QRadialGradient>
#include <QRect> # include <QRect>
#include <QRegularExpression> # include <QRegularExpression>
#include <QRunnable> # include <QRunnable>
#include <QScroller> # include <QScroller>
#include <QShortcut> # include <QShortcut>
#include <QSizePolicy> # include <QSizePolicy>
#include <QSlider> # include <QSlider>
#include <QStackedLayout> # include <QStackedLayout>
#include <QStandardPaths> # include <QStandardPaths>
#include <QString> # include <QString>
#include <QStyle> # include <QStyle>
#include <QStyleOption> # include <QStyleOption>
#include <QTabWidget> # include <QTabWidget>
#include <QTextEdit> # include <QTextEdit>
#include <QThread> # include <QThread>
#include <QThreadPool> # include <QThreadPool>
#include <QTime> # include <QTime>
#include <QTimer> # include <QTimer>
#include <QUrl> # include <QUrl>
#include <QUuid> # include <QUuid>
#include <QVBoxLayout> # include <QVBoxLayout>
#include <QVariant> # include <QVariant>
#include <QVector> # include <QVector>
#include <QWheelEvent> # include <QWheelEvent>
#include <QWidget> # include <QWidget>
#include <QtCore/QVariant> # include <QtCore/QVariant>
#include <QtGlobal> # include <QtGlobal>
#include <QtWidgets/QAction> # include <QtWidgets/QAction>
#include <QtWidgets/QApplication> # include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup> # include <QtWidgets/QButtonGroup>
#include <QtWidgets/QDialog> # include <QtWidgets/QDialog>
#include <QtWidgets/QDialogButtonBox> # include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QFormLayout> # include <QtWidgets/QFormLayout>
#include <QtWidgets/QHBoxLayout> # include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView> # include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel> # include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit> # include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton> # include <QtWidgets/QPushButton>
#include <QtWidgets/QTabWidget> # include <QtWidgets/QTabWidget>
#include <QtWidgets/QVBoxLayout> # include <QtWidgets/QVBoxLayout>
#include <algorithm> # include <algorithm>
#include <boost/current_function.hpp> # include <boost/current_function.hpp>
#include <boost/foreach.hpp> # include <boost/foreach.hpp>
#include <boost/noncopyable.hpp> # include <boost/noncopyable.hpp>
#include <boost/optional.hpp> # include <boost/optional.hpp>
#include <cassert> # include <cassert>
#include <chrono> # include <chrono>
#include <cinttypes> # include <cinttypes>
#include <climits> # include <climits>
#include <cmath> # include <cmath>
#include <cstdint> # include <cstdint>
#include <ctime> # include <ctime>
#include <functional> # include <functional>
#include <future> # include <future>
#include <list> # include <list>
#include <map> # include <map>
#include <memory> # include <memory>
#include <mutex> # include <mutex>
#include <pajlada/settings/serialize.hpp> # include <pajlada/settings/serialize.hpp>
#include <pajlada/settings/setting.hpp> # include <pajlada/settings/setting.hpp>
#include <pajlada/settings/settinglistener.hpp> # include <pajlada/settings/settinglistener.hpp>
#include <pajlada/signals/connection.hpp> # include <pajlada/signals/connection.hpp>
#include <pajlada/signals/signal.hpp> # include <pajlada/signals/signal.hpp>
#include <random> # include <random>
#include <set> # include <set>
#include <string> # include <string>
#include <thread> # include <thread>
#include <tuple> # include <tuple>
#include <type_traits> # include <type_traits>
#include <unordered_map> # include <unordered_map>
#include <unordered_set> # include <unordered_set>
#include <vector> # include <vector>
#ifndef UNUSED # ifndef UNUSED
#define UNUSED(x) (void)(x) # define UNUSED(x) (void)(x)
#endif # endif
#ifndef ATTR_UNUSED # ifndef ATTR_UNUSED
#ifdef Q_OS_WIN # ifdef Q_OS_WIN
#define ATTR_UNUSED # define ATTR_UNUSED
#else # else
#define ATTR_UNUSED __attribute__((unused)) # define ATTR_UNUSED __attribute__((unused))
#endif # endif
#endif # endif
#endif #endif

View file

@ -12,7 +12,7 @@
#include "widgets/dialogs/LastRunCrashDialog.hpp" #include "widgets/dialogs/LastRunCrashDialog.hpp"
#ifdef C_USE_BREAKPAD #ifdef C_USE_BREAKPAD
#include <QBreakpadHandler.h> # include <QBreakpadHandler.h>
#endif #endif
// void initQt(); // void initQt();
@ -23,80 +23,82 @@
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 = qApp->palette(); auto dark = qApp->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::Disabled, QPalette::WindowText, dark.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127)); QColor(127, 127, 127));
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::white); dark.setColor(QPalette::ToolTipText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); dark.setColor(QPalette::Disabled, QPalette::Text,
dark.setColor(QPalette::Dark, QColor(35, 35, 35)); QColor(127, 127, 127));
dark.setColor(QPalette::Shadow, QColor(20, 20, 20)); dark.setColor(QPalette::Dark, QColor(35, 35, 35));
dark.setColor(QPalette::Button, QColor(70, 70, 70)); dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
dark.setColor(QPalette::ButtonText, Qt::white); dark.setColor(QPalette::Button, QColor(70, 70, 70));
dark.setColor(QPalette::Disabled, QPalette::ButtonText, dark.setColor(QPalette::ButtonText, Qt::white);
QColor(127, 127, 127)); dark.setColor(QPalette::Disabled, QPalette::ButtonText,
dark.setColor(QPalette::BrightText, Qt::red); QColor(127, 127, 127));
dark.setColor(QPalette::Link, QColor(42, 130, 218)); dark.setColor(QPalette::BrightText, Qt::red);
dark.setColor(QPalette::Highlight, QColor(42, 130, 218)); dark.setColor(QPalette::Link, QColor(42, 130, 218));
dark.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80)); dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
dark.setColor(QPalette::HighlightedText, Qt::white); dark.setColor(QPalette::Disabled, QPalette::Highlight,
dark.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(80, 80, 80));
QColor(127, 127, 127)); dark.setColor(QPalette::HighlightedText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
qApp->setPalette(dark); qApp->setPalette(dark);
}
void initQt()
{
// set up the QApplication flags
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
#ifdef Q_OS_WIN32
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif
QApplication::setStyle(QStyleFactory::create("Fusion"));
installCustomPalette();
}
void showLastCrashDialog()
{
#ifndef C_DISABLE_CRASH_DIALOG
LastRunCrashDialog dialog;
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
} }
void initQt()
{
// set up the QApplication flags
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
#ifdef Q_OS_WIN32
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif #endif
}
void createRunningFile(const QString &path) QApplication::setStyle(QStyleFactory::create("Fusion"));
{
QFile runningFile(path);
runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate); installCustomPalette();
runningFile.flush(); }
runningFile.close();
}
void removeRunningFile(const QString &path) void showLastCrashDialog()
{ {
QFile::remove(path); #ifndef C_DISABLE_CRASH_DIALOG
} LastRunCrashDialog dialog;
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
}
#endif
}
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);
}
} // namespace } // namespace
void runGui(QApplication &a, Paths &paths, Settings &settings) void runGui(QApplication &a, Paths &paths, Settings &settings)

View file

@ -5,9 +5,9 @@
#define CHATTERINO_VERSION "2.0.4" #define CHATTERINO_VERSION "2.0.4"
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
#define CHATTERINO_OS "win" # define CHATTERINO_OS "win"
#elif defined(Q_OS_MACOS) #elif defined(Q_OS_MACOS)
#define CHATTERINO_OS "macos" # define CHATTERINO_OS "macos"
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
#define CHATTERINO_OS "linux" # define CHATTERINO_OS "linux"
#endif #endif

View file

@ -68,37 +68,39 @@ private:
namespace pajlada { namespace pajlada {
namespace Settings { namespace Settings {
template <> template <>
struct Serialize<chatterino::HighlightBlacklistUser> { struct Serialize<chatterino::HighlightBlacklistUser> {
static rapidjson::Value get(const chatterino::HighlightBlacklistUser &value, static rapidjson::Value get(
rapidjson::Document::AllocatorType &a) const chatterino::HighlightBlacklistUser &value,
{ rapidjson::Document::AllocatorType &a)
rapidjson::Value ret(rapidjson::kObjectType); {
rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getPattern(), a); AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a); AddMember(ret, "regex", value.isRegex(), a);
return ret; return ret;
} }
}; };
template <> template <>
struct Deserialize<chatterino::HighlightBlacklistUser> { struct Deserialize<chatterino::HighlightBlacklistUser> {
static chatterino::HighlightBlacklistUser get(const rapidjson::Value &value) static chatterino::HighlightBlacklistUser get(
{ const rapidjson::Value &value)
QString pattern; {
bool isRegex = false; QString pattern;
bool isRegex = false;
if (!value.IsObject()) {
return chatterino::HighlightBlacklistUser(pattern, isRegex);
}
chatterino::rj::getSafe(value, "pattern", pattern);
chatterino::rj::getSafe(value, "regex", isRegex);
if (!value.IsObject()) {
return chatterino::HighlightBlacklistUser(pattern, isRegex); return chatterino::HighlightBlacklistUser(pattern, isRegex);
} }
};
chatterino::rj::getSafe(value, "pattern", pattern);
chatterino::rj::getSafe(value, "regex", isRegex);
return chatterino::HighlightBlacklistUser(pattern, isRegex);
}
};
} // namespace Settings } // namespace Settings
} // namespace pajlada } // namespace pajlada

View file

@ -72,43 +72,45 @@ private:
namespace pajlada { namespace pajlada {
namespace Settings { namespace Settings {
template <> template <>
struct Serialize<chatterino::HighlightPhrase> { struct Serialize<chatterino::HighlightPhrase> {
static rapidjson::Value get(const chatterino::HighlightPhrase &value, static rapidjson::Value get(const chatterino::HighlightPhrase &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
rapidjson::Value ret(rapidjson::kObjectType); rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getPattern(), a); AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "alert", value.getAlert(), a); AddMember(ret, "alert", value.getAlert(), a);
AddMember(ret, "sound", value.getSound(), a); AddMember(ret, "sound", value.getSound(), a);
AddMember(ret, "regex", value.isRegex(), a); AddMember(ret, "regex", value.isRegex(), a);
return ret; return ret;
}
};
template <>
struct Deserialize<chatterino::HighlightPhrase> {
static chatterino::HighlightPhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::HighlightPhrase(QString(), true, false, false);
} }
};
QString _pattern; template <>
bool _alert = true; struct Deserialize<chatterino::HighlightPhrase> {
bool _sound = false; static chatterino::HighlightPhrase get(const rapidjson::Value &value)
bool _isRegex = false; {
if (!value.IsObject()) {
return chatterino::HighlightPhrase(QString(), true, false,
false);
}
chatterino::rj::getSafe(value, "pattern", _pattern); QString _pattern;
chatterino::rj::getSafe(value, "alert", _alert); bool _alert = true;
chatterino::rj::getSafe(value, "sound", _sound); bool _sound = false;
chatterino::rj::getSafe(value, "regex", _isRegex); bool _isRegex = false;
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex); chatterino::rj::getSafe(value, "pattern", _pattern);
} chatterino::rj::getSafe(value, "alert", _alert);
}; chatterino::rj::getSafe(value, "sound", _sound);
chatterino::rj::getSafe(value, "regex", _isRegex);
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
_isRegex);
}
};
} // namespace Settings } // namespace Settings
} // namespace pajlada } // namespace pajlada

View file

@ -59,37 +59,37 @@ private:
namespace pajlada { namespace pajlada {
namespace Settings { namespace Settings {
template <> template <>
struct Serialize<chatterino::IgnorePhrase> { struct Serialize<chatterino::IgnorePhrase> {
static rapidjson::Value get(const chatterino::IgnorePhrase &value, static rapidjson::Value get(const chatterino::IgnorePhrase &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
rapidjson::Value ret(rapidjson::kObjectType); rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getPattern(), a); AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a); AddMember(ret, "regex", value.isRegex(), a);
return ret; return ret;
}
};
template <>
struct Deserialize<chatterino::IgnorePhrase> {
static chatterino::IgnorePhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::IgnorePhrase(QString(), false);
} }
};
QString _pattern; template <>
bool _isRegex = false; struct Deserialize<chatterino::IgnorePhrase> {
static chatterino::IgnorePhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::IgnorePhrase(QString(), false);
}
chatterino::rj::getSafe(value, "pattern", _pattern); QString _pattern;
chatterino::rj::getSafe(value, "regex", _isRegex); bool _isRegex = false;
return chatterino::IgnorePhrase(_pattern, _isRegex); chatterino::rj::getSafe(value, "pattern", _pattern);
} chatterino::rj::getSafe(value, "regex", _isRegex);
};
return chatterino::IgnorePhrase(_pattern, _isRegex);
}
};
} // namespace Settings } // namespace Settings
} // namespace pajlada } // namespace pajlada

View file

@ -36,34 +36,34 @@ private:
namespace pajlada { namespace pajlada {
namespace Settings { namespace Settings {
template <> template <>
struct Serialize<chatterino::ModerationAction> { struct Serialize<chatterino::ModerationAction> {
static rapidjson::Value get(const chatterino::ModerationAction &value, static rapidjson::Value get(const chatterino::ModerationAction &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
rapidjson::Value ret(rapidjson::kObjectType); rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getAction(), a); AddMember(ret, "pattern", value.getAction(), a);
return ret; return ret;
}
};
template <>
struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::ModerationAction(QString());
} }
};
QString pattern; template <>
struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::ModerationAction(QString());
}
chatterino::rj::getSafe(value, "pattern", pattern); QString pattern;
return chatterino::ModerationAction(pattern); chatterino::rj::getSafe(value, "pattern", pattern);
}
}; return chatterino::ModerationAction(pattern);
}
};
} // namespace Settings } // namespace Settings
} // namespace pajlada } // namespace pajlada

View file

@ -22,157 +22,163 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
// Frames // Frames
Frames::Frames() Frames::Frames()
{ {
DebugCount::increase("images"); DebugCount::increase("images");
}
Frames::Frames(const QVector<Frame<QPixmap>> &frames)
: items_(frames)
{
assertInGuiThread();
DebugCount::increase("images");
if (this->animated()) {
DebugCount::increase("animated images");
this->gifTimerConnection_ = getApp()->emotes->gifTimer.signal.connect(
[this] { this->advance(); });
}
}
Frames::~Frames()
{
assertInGuiThread();
DebugCount::decrease("images");
if (this->animated()) {
DebugCount::decrease("animated images");
} }
this->gifTimerConnection_.disconnect(); Frames::Frames(const QVector<Frame<QPixmap>> &frames)
} : items_(frames)
{
assertInGuiThread();
DebugCount::increase("images");
void Frames::advance() if (this->animated()) {
{ DebugCount::increase("animated images");
this->durationOffset_ += GIF_FRAME_LENGTH;
while (true) { this->gifTimerConnection_ =
this->index_ %= this->items_.size(); getApp()->emotes->gifTimer.signal.connect(
[this] { this->advance(); });
if (this->index_ >= this->items_.size()) {
this->index_ = this->index_;
}
if (this->durationOffset_ > this->items_[this->index_].duration) {
this->durationOffset_ -= this->items_[this->index_].duration;
this->index_ = (this->index_ + 1) % this->items_.size();
} else {
break;
} }
} }
}
bool Frames::animated() const Frames::~Frames()
{ {
return this->items_.size() > 1; assertInGuiThread();
} DebugCount::decrease("images");
boost::optional<QPixmap> Frames::current() const if (this->animated()) {
{ DebugCount::decrease("animated images");
if (this->items_.size() == 0) return boost::none; }
return this->items_[this->index_].image;
}
boost::optional<QPixmap> Frames::first() const this->gifTimerConnection_.disconnect();
{ }
if (this->items_.size() == 0) return boost::none;
return this->items_.front().image;
}
// functions void Frames::advance()
QVector<Frame<QImage>> readFrames(QImageReader &reader, const Url &url) {
{ this->durationOffset_ += GIF_FRAME_LENGTH;
QVector<Frame<QImage>> frames;
while (true) {
this->index_ %= this->items_.size();
if (this->index_ >= this->items_.size()) {
this->index_ = this->index_;
}
if (this->durationOffset_ > this->items_[this->index_].duration) {
this->durationOffset_ -= this->items_[this->index_].duration;
this->index_ = (this->index_ + 1) % this->items_.size();
} else {
break;
}
}
}
bool Frames::animated() const
{
return this->items_.size() > 1;
}
boost::optional<QPixmap> Frames::current() const
{
if (this->items_.size() == 0) return boost::none;
return this->items_[this->index_].image;
}
boost::optional<QPixmap> Frames::first() const
{
if (this->items_.size() == 0) return boost::none;
return this->items_.front().image;
}
// functions
QVector<Frame<QImage>> readFrames(QImageReader &reader, const Url &url)
{
QVector<Frame<QImage>> frames;
if (reader.imageCount() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
return frames;
}
QImage image;
for (int index = 0; index < reader.imageCount(); ++index) {
if (reader.read(&image)) {
QPixmap::fromImage(image);
int duration = std::max(20, reader.nextImageDelay());
frames.push_back(Frame<QImage>{image, duration});
}
}
if (frames.size() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
}
if (reader.imageCount() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
return frames; return frames;
} }
QImage image; // parsed
for (int index = 0; index < reader.imageCount(); ++index) { template <typename Assign>
if (reader.read(&image)) { void assignDelayed(
QPixmap::fromImage(image); std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> &queued,
std::mutex &mutex, std::atomic_bool &loadedEventQueued)
int duration = std::max(20, reader.nextImageDelay()); {
frames.push_back(Frame<QImage>{image, duration});
}
}
if (frames.size() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
}
return frames;
}
// parsed
template <typename Assign>
void assignDelayed(
std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> &queued,
std::mutex &mutex, std::atomic_bool &loadedEventQueued)
{
std::lock_guard<std::mutex> lock(mutex);
int i = 0;
while (!queued.empty()) {
queued.front().first(queued.front().second);
queued.pop();
if (++i > 50) {
QTimer::singleShot(
3, [&] { assignDelayed(queued, mutex, loadedEventQueued); });
return;
}
}
getApp()->windows->forceLayoutChannelViews();
loadedEventQueued = false;
}
template <typename Assign>
auto makeConvertCallback(const QVector<Frame<QImage>> &parsed, Assign assign)
{
return [parsed, assign] {
// convert to pixmap
auto frames = QVector<Frame<QPixmap>>();
std::transform(parsed.begin(), parsed.end(), std::back_inserter(frames),
[](auto &frame) {
return Frame<QPixmap>{
QPixmap::fromImage(frame.image), frame.duration};
});
// put into stack
static std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> queued;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
queued.emplace(assign, frames); int i = 0;
static std::atomic_bool loadedEventQueued{false}; while (!queued.empty()) {
queued.front().first(queued.front().second);
queued.pop();
if (!loadedEventQueued) { if (++i > 50) {
loadedEventQueued = true; QTimer::singleShot(3, [&] {
assignDelayed(queued, mutex, loadedEventQueued);
QTimer::singleShot( });
100, [=] { assignDelayed(queued, mutex, loadedEventQueued); }); return;
}
} }
};
} getApp()->windows->forceLayoutChannelViews();
loadedEventQueued = false;
}
template <typename Assign>
auto makeConvertCallback(const QVector<Frame<QImage>> &parsed,
Assign assign)
{
return [parsed, assign] {
// convert to pixmap
auto frames = QVector<Frame<QPixmap>>();
std::transform(parsed.begin(), parsed.end(),
std::back_inserter(frames), [](auto &frame) {
return Frame<QPixmap>{
QPixmap::fromImage(frame.image),
frame.duration};
});
// put into stack
static std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>>
queued;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
queued.emplace(assign, frames);
static std::atomic_bool loadedEventQueued{false};
if (!loadedEventQueued) {
loadedEventQueued = true;
QTimer::singleShot(100, [=] {
assignDelayed(queued, mutex, loadedEventQueued);
});
}
};
}
} // namespace } // namespace
// IMAGE2 // IMAGE2

View file

@ -17,29 +17,29 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
template <typename Image> template <typename Image>
struct Frame { struct Frame {
Image image; Image image;
int duration; int duration;
}; };
class Frames : boost::noncopyable class Frames : boost::noncopyable
{ {
public: public:
Frames(); Frames();
Frames(const QVector<Frame<QPixmap>> &frames); Frames(const QVector<Frame<QPixmap>> &frames);
~Frames(); ~Frames();
bool animated() const; bool animated() const;
void advance(); void advance();
boost::optional<QPixmap> current() const; boost::optional<QPixmap> current() const;
boost::optional<QPixmap> first() const; boost::optional<QPixmap> first() const;
private: private:
QVector<Frame<QPixmap>> items_; QVector<Frame<QPixmap>> items_;
int index_{0}; int index_{0};
int durationOffset_{0}; int durationOffset_{0};
pajlada::Signals::Connection gifTimerConnection_; pajlada::Signals::Connection gifTimerConnection_;
}; };
} // namespace } // namespace
class Image; class Image;

View file

@ -2,5 +2,4 @@
namespace chatterino { namespace chatterino {
} // namespace chatterino } // namespace chatterino

View file

@ -13,73 +13,76 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
Url getEmoteLink(QString urlTemplate, const EmoteId &id, Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale) const QString &emoteScale)
{ {
urlTemplate.detach(); urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string) return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)}; .replace("{{image}}", emoteScale)};
}
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
const EmoteMap &currentEmotes)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate = qS("https:") + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote : jsonEmotes) {
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
emotes[name] = cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
} }
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
qS("https:") + jsonRoot.value("urlTemplate").toString();
return {Success, std::move(emotes)}; for (auto jsonEmote : jsonEmotes) {
} auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id) auto name =
{ EmoteName{jsonEmote.toObject().value("code").toString()};
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id); auto emote = Emote(
} {name,
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot) ImageSet{
{ Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
auto emotes = EmoteMap(); Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
auto jsonEmotes = jsonRoot.value("emotes").toArray(); Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString(); Tooltip{name.string + "<br />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
for (auto jsonEmote_ : jsonEmotes) { emotes[name] =
auto jsonEmote = jsonEmote_.toObject(); cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
auto id = EmoteId{jsonEmote.value("id").toString()}; return {Success, std::move(emotes)};
auto name = EmoteName{jsonEmote.value("code").toString()};
// emoteObject.value("imageType").toString();
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Channel Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
emotes[name] = cachedOrMake(std::move(emote), id);
} }
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
{
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
return {Success, std::move(emotes)}; return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
} }
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote_ : jsonEmotes) {
auto jsonEmote = jsonEmote_.toObject();
auto id = EmoteId{jsonEmote.value("id").toString()};
auto name = EmoteName{jsonEmote.value("code").toString()};
// emoteObject.value("imageType").toString();
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Channel Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
emotes[name] = cachedOrMake(std::move(emote), id);
}
return {Success, std::move(emotes)};
}
} // namespace } // namespace
// //

View file

@ -13,82 +13,81 @@
#include <memory> #include <memory>
namespace chatterino { namespace chatterino {
namespace { namespace {
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
const rapidjson::Value &unparsedEmoji,
QString shortCode = QString())
{
static uint unicodeBytes[4];
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData, struct {
const rapidjson::Value &unparsedEmoji, bool apple;
QString shortCode = QString()) bool google;
{ bool twitter;
static uint unicodeBytes[4]; bool emojione;
bool facebook;
bool messenger;
} capabilities;
struct { if (!shortCode.isEmpty()) {
bool apple; emojiData->shortCodes.push_back(shortCode);
bool google; } else {
bool twitter; const auto &shortCodes = unparsedEmoji["short_names"];
bool emojione; for (const auto &shortCode : shortCodes.GetArray()) {
bool facebook; emojiData->shortCodes.emplace_back(shortCode.GetString());
bool messenger; }
} capabilities;
if (!shortCode.isEmpty()) {
emojiData->shortCodes.push_back(shortCode);
} else {
const auto &shortCodes = unparsedEmoji["short_names"];
for (const auto &shortCode : shortCodes.GetArray()) {
emojiData->shortCodes.emplace_back(shortCode.GetString());
} }
}
rj::getSafe(unparsedEmoji, "non_qualified", emojiData->nonQualifiedCode); rj::getSafe(unparsedEmoji, "non_qualified",
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode); emojiData->nonQualifiedCode);
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode);
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple); rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google); rj::getSafe(unparsedEmoji, "has_img_google", capabilities.google);
rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter); rj::getSafe(unparsedEmoji, "has_img_twitter", capabilities.twitter);
rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione); rj::getSafe(unparsedEmoji, "has_img_emojione", capabilities.emojione);
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook); rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger); rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
if (capabilities.apple) { if (capabilities.apple) {
emojiData->capabilities.insert("Apple"); emojiData->capabilities.insert("Apple");
} }
if (capabilities.google) { if (capabilities.google) {
emojiData->capabilities.insert("Google"); emojiData->capabilities.insert("Google");
} }
if (capabilities.twitter) { if (capabilities.twitter) {
emojiData->capabilities.insert("Twitter"); emojiData->capabilities.insert("Twitter");
} }
if (capabilities.emojione) { if (capabilities.emojione) {
emojiData->capabilities.insert("EmojiOne 3"); emojiData->capabilities.insert("EmojiOne 3");
} }
if (capabilities.facebook) { if (capabilities.facebook) {
emojiData->capabilities.insert("Facebook"); emojiData->capabilities.insert("Facebook");
} }
if (capabilities.messenger) { if (capabilities.messenger) {
emojiData->capabilities.insert("Messenger"); emojiData->capabilities.insert("Messenger");
} }
QStringList unicodeCharacters; QStringList unicodeCharacters;
if (!emojiData->nonQualifiedCode.isEmpty()) { if (!emojiData->nonQualifiedCode.isEmpty()) {
unicodeCharacters = emojiData->nonQualifiedCode.toLower().split('-'); unicodeCharacters =
} else { emojiData->nonQualifiedCode.toLower().split('-');
unicodeCharacters = emojiData->unifiedCode.toLower().split('-'); } else {
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
}
if (unicodeCharacters.length() < 1) {
return;
}
int numUnicodeBytes = 0;
for (const QString &unicodeCharacter : unicodeCharacters) {
unicodeBytes[numUnicodeBytes++] =
QString(unicodeCharacter).toUInt(nullptr, 16);
}
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
} }
if (unicodeCharacters.length() < 1) {
return;
}
int numUnicodeBytes = 0;
for (const QString &unicodeCharacter : unicodeCharacters) {
unicodeBytes[numUnicodeBytes++] =
QString(unicodeCharacter).toUInt(nullptr, 16);
}
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
}
} // namespace } // namespace
void Emojis::load() void Emojis::load()

View file

@ -10,99 +10,100 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale) Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
{ {
auto emote = urls.value(emoteScale); auto emote = urls.value(emoteScale);
if (emote.isUndefined()) { if (emote.isUndefined()) {
return {""}; return {""};
}
assert(emote.isString());
return {"https:" + emote.toString()};
}
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
const QString &tooltip, Emote &emoteData)
{
auto url1x = getEmoteLink(urls, "1");
auto url2x = getEmoteLink(urls, "2");
auto url3x = getEmoteLink(urls, "4");
//, code, tooltip
emoteData.name = name;
emoteData.images =
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5),
Image::fromUrl(url3x, 0.25)};
emoteData.tooltip = {tooltip};
}
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
{
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
}
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
const EmoteMap &currentEmotes)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
for (auto jsonEmoteValue : jsonEmotes) {
auto jsonEmote = jsonEmoteValue.toObject();
auto name = EmoteName{jsonEmote.value("name").toString()};
auto id = EmoteId{jsonEmote.value("id").toString()};
auto urls = jsonEmote.value("urls").toObject();
auto emote = Emote();
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote",
emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
} }
assert(emote.isString());
return {"https:" + emote.toString()};
} }
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
const QString &tooltip, Emote &emoteData)
{
auto url1x = getEmoteLink(urls, "1");
auto url2x = getEmoteLink(urls, "2");
auto url3x = getEmoteLink(urls, "4");
return {Success, std::move(emotes)}; //, code, tooltip
} emoteData.name = name;
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot) emoteData.images =
{ ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5),
auto jsonSets = jsonRoot.value("sets").toObject(); Image::fromUrl(url3x, 0.25)};
auto emotes = EmoteMap(); emoteData.tooltip = {tooltip};
}
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
{
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
for (auto jsonSet : jsonSets) { return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); }
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
for (auto _jsonEmote : jsonEmotes) { for (auto jsonSet : jsonSets) {
auto jsonEmote = _jsonEmote.toObject(); auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
// margins for (auto jsonEmoteValue : jsonEmotes) {
auto id = EmoteId{QString::number(jsonEmote.value("id").toInt())}; auto jsonEmote = jsonEmoteValue.toObject();
auto name = EmoteName{jsonEmote.value("name").toString()};
auto urls = jsonEmote.value("urls").toObject();
Emote emote; auto name = EmoteName{jsonEmote.value("name").toString()};
fillInEmoteData(urls, name, name.string + "<br/>Channel FFZ Emote", auto id = EmoteId{jsonEmote.value("id").toString()};
emote); auto urls = jsonEmote.value("urls").toObject();
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] = cachedOrMake(std::move(emote), id); auto emote = Emote();
fillInEmoteData(urls, name,
name.string + "<br/>Global FFZ Emote", emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
} }
}
return {Success, std::move(emotes)}; return {Success, std::move(emotes)};
} }
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
for (auto _jsonEmote : jsonEmotes) {
auto jsonEmote = _jsonEmote.toObject();
// margins
auto id =
EmoteId{QString::number(jsonEmote.value("id").toInt())};
auto name = EmoteName{jsonEmote.value("name").toString()};
auto urls = jsonEmote.value("urls").toObject();
Emote emote;
fillInEmoteData(urls, name,
name.string + "<br/>Channel FFZ Emote", emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] = cachedOrMake(std::move(emote), id);
}
}
return {Success, std::move(emotes)};
}
} // namespace } // namespace
FfzEmotes::FfzEmotes() FfzEmotes::FfzEmotes()

View file

@ -24,157 +24,159 @@ static std::map<QString, std::string> sentMessages;
namespace detail { namespace detail {
PubSubClient::PubSubClient(WebsocketClient &websocketClient, PubSubClient::PubSubClient(WebsocketClient &websocketClient,
WebsocketHandle handle) WebsocketHandle handle)
: websocketClient_(websocketClient) : websocketClient_(websocketClient)
, handle_(handle) , handle_(handle)
{ {
}
void PubSubClient::start()
{
assert(!this->started_);
this->started_ = true;
this->ping();
}
void PubSubClient::stop()
{
assert(this->started_);
this->started_ = false;
}
bool PubSubClient::listen(rapidjson::Document &message)
{
int numRequestedListens = message["data"]["topics"].Size();
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) {
// This PubSubClient is already at its peak listens
return false;
} }
this->numListens_ += numRequestedListens; void PubSubClient::start()
{
assert(!this->started_);
for (const auto &topic : message["data"]["topics"].GetArray()) { this->started_ = true;
this->listeners_.emplace_back(
Listener{topic.GetString(), false, false, false}); this->ping();
} }
auto uuid = CreateUUID(); void PubSubClient::stop()
{
assert(this->started_);
rj::set(message, "nonce", uuid); this->started_ = false;
}
std::string payload = rj::stringify(message); bool PubSubClient::listen(rapidjson::Document &message)
sentMessages[uuid] = payload; {
int numRequestedListens = message["data"]["topics"].Size();
this->send(payload.c_str()); if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) {
// This PubSubClient is already at its peak listens
return true; return false;
}
void PubSubClient::unlistenPrefix(const std::string &prefix)
{
std::vector<std::string> topics;
for (auto it = this->listeners_.begin(); it != this->listeners_.end();) {
const auto &listener = *it;
if (listener.topic.find(prefix) == 0) {
topics.push_back(listener.topic);
it = this->listeners_.erase(it);
} else {
++it;
} }
}
if (topics.empty()) { this->numListens_ += numRequestedListens;
return;
}
auto message = createUnlistenMessage(topics); for (const auto &topic : message["data"]["topics"].GetArray()) {
this->listeners_.emplace_back(
auto uuid = CreateUUID(); Listener{topic.GetString(), false, false, false});
rj::set(message, "nonce", CreateUUID());
std::string payload = rj::stringify(message);
sentMessages[uuid] = payload;
this->send(payload.c_str());
}
void PubSubClient::handlePong()
{
assert(this->awaitingPong_);
log("Got pong!");
this->awaitingPong_ = false;
}
bool PubSubClient::isListeningToTopic(const std::string &payload)
{
for (const auto &listener : this->listeners_) {
if (listener.topic == payload) {
return true;
} }
auto uuid = CreateUUID();
rj::set(message, "nonce", uuid);
std::string payload = rj::stringify(message);
sentMessages[uuid] = payload;
this->send(payload.c_str());
return true;
} }
return false; void PubSubClient::unlistenPrefix(const std::string &prefix)
} {
std::vector<std::string> topics;
void PubSubClient::ping() for (auto it = this->listeners_.begin();
{ it != this->listeners_.end();) {
assert(this->started_); const auto &listener = *it;
if (listener.topic.find(prefix) == 0) {
topics.push_back(listener.topic);
it = this->listeners_.erase(it);
} else {
++it;
}
}
if (!this->send(pingPayload)) { if (topics.empty()) {
return; return;
}
auto message = createUnlistenMessage(topics);
auto uuid = CreateUUID();
rj::set(message, "nonce", CreateUUID());
std::string payload = rj::stringify(message);
sentMessages[uuid] = payload;
this->send(payload.c_str());
} }
this->awaitingPong_ = true; void PubSubClient::handlePong()
{
assert(this->awaitingPong_);
auto self = this->shared_from_this(); log("Got pong!");
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15), this->awaitingPong_ = false;
[self](auto timer) { }
if (!self->started_) {
return;
}
if (self->awaitingPong_) { bool PubSubClient::isListeningToTopic(const std::string &payload)
log("No pong respnose, disconnect!"); {
// TODO(pajlada): Label this connection as "disconnect me" for (const auto &listener : this->listeners_) {
} if (listener.topic == payload) {
}); return true;
}
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5), }
[self](auto timer) {
if (!self->started_) {
return;
}
self->ping(); //
});
}
bool PubSubClient::send(const char *payload)
{
WebsocketErrorCode ec;
this->websocketClient_.send(this->handle_, payload,
websocketpp::frame::opcode::text, ec);
if (ec) {
log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe gracefully
// handle it
return false; return false;
} }
return true; void PubSubClient::ping()
} {
assert(this->started_);
if (!this->send(pingPayload)) {
return;
}
this->awaitingPong_ = true;
auto self = this->shared_from_this();
runAfter(this->websocketClient_.get_io_service(),
std::chrono::seconds(15), [self](auto timer) {
if (!self->started_) {
return;
}
if (self->awaitingPong_) {
log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect
// me"
}
});
runAfter(this->websocketClient_.get_io_service(),
std::chrono::minutes(5), [self](auto timer) {
if (!self->started_) {
return;
}
self->ping(); //
});
}
bool PubSubClient::send(const char *payload)
{
WebsocketErrorCode ec;
this->websocketClient_.send(this->handle_, payload,
websocketpp::frame::opcode::text, ec);
if (ec) {
log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe
// gracefully handle it
return false;
}
return true;
}
} // namespace detail } // namespace detail

View file

@ -32,41 +32,42 @@ using WebsocketErrorCode = websocketpp::lib::error_code;
namespace detail { namespace detail {
struct Listener { struct Listener {
std::string topic; std::string topic;
bool authed; bool authed;
bool persistent; bool persistent;
bool confirmed = false; bool confirmed = false;
}; };
class PubSubClient : public std::enable_shared_from_this<PubSubClient> class PubSubClient : public std::enable_shared_from_this<PubSubClient>
{ {
public: public:
PubSubClient(WebsocketClient &_websocketClient, WebsocketHandle _handle); PubSubClient(WebsocketClient &_websocketClient,
WebsocketHandle _handle);
void start(); void start();
void stop(); void stop();
bool listen(rapidjson::Document &message); bool listen(rapidjson::Document &message);
void unlistenPrefix(const std::string &prefix); void unlistenPrefix(const std::string &prefix);
void handlePong(); void handlePong();
bool isListeningToTopic(const std::string &topic); bool isListeningToTopic(const std::string &topic);
private: private:
void ping(); void ping();
bool send(const char *payload); bool send(const char *payload);
WebsocketClient &websocketClient_; WebsocketClient &websocketClient_;
WebsocketHandle handle_; WebsocketHandle handle_;
uint16_t numListens_ = 0; uint16_t numListens_ = 0;
std::vector<Listener> listeners_; std::vector<Listener> listeners_;
std::atomic<bool> awaitingPong_{false}; std::atomic<bool> awaitingPong_{false};
std::atomic<bool> started_{false}; std::atomic<bool> started_{false};
}; };
} // namespace detail } // namespace detail

View file

@ -1,11 +1,10 @@
#pragma once #pragma once
#include "debug/Log.hpp"
#include "util/RapidjsonHelpers.hpp"
#include "debug/Log.hpp"
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <memory> #include <memory>
#include "debug/Log.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino { namespace chatterino {

View file

@ -15,32 +15,32 @@ namespace chatterino {
namespace { namespace {
EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode) EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
{ {
auto cleanCode = dirtyEmoteCode.string; auto cleanCode = dirtyEmoteCode.string;
cleanCode.detach(); cleanCode.detach();
static QMap<QString, QString> emoteNameReplacements{ static QMap<QString, QString> emoteNameReplacements{
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("}, {"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("},
{"\\&lt\\;3", "&lt;3"}, {"\\:-?(o|O)", ":O"}, {"\\&lt\\;3", "&lt;3"}, {"\\:-?(o|O)", ":O"},
{"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("},
{"\\:-?\\)", ":)"}, {"\\:-?D", ":D"}, {"\\:-?\\)", ":)"}, {"\\:-?D", ":D"},
{"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"R-?\\)", "R)"}, {"B-?\\)", "B)"}, {"R-?\\)", "R)"}, {"B-?\\)", "B)"},
}; };
auto it = emoteNameReplacements.find(dirtyEmoteCode.string); auto it = emoteNameReplacements.find(dirtyEmoteCode.string);
if (it != emoteNameReplacements.end()) { if (it != emoteNameReplacements.end()) {
cleanCode = it.value(); cleanCode = it.value();
}
cleanCode.replace("&lt;", "<");
cleanCode.replace("&gt;", ">");
return {cleanCode};
} }
cleanCode.replace("&lt;", "<");
cleanCode.replace("&gt;", ">");
return {cleanCode};
}
} // namespace } // namespace
TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken, TwitchAccount::TwitchAccount(const QString &username, const QString &oauthToken,
@ -417,7 +417,7 @@ void TwitchAccount::loadEmotes()
} }
AccessGuard<const TwitchAccount::TwitchAccountEmoteData> AccessGuard<const TwitchAccount::TwitchAccountEmoteData>
TwitchAccount::accessEmotes() const TwitchAccount::accessEmotes() const
{ {
return this->emotes_.accessConst(); return this->emotes_.accessConst();
} }

View file

@ -25,48 +25,49 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
auto parseRecentMessages(const QJsonObject &jsonRoot, TwitchChannel &channel) auto parseRecentMessages(const QJsonObject &jsonRoot,
{ TwitchChannel &channel)
QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); {
std::vector<MessagePtr> messages; QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
std::vector<MessagePtr> messages;
if (jsonMessages.empty()) return messages; if (jsonMessages.empty()) return messages;
for (const auto jsonMessage : jsonMessages) { for (const auto jsonMessage : jsonMessages) {
auto content = jsonMessage.toString().toUtf8(); auto content = jsonMessage.toString().toUtf8();
// passing nullptr as the channel makes the message invalid but we don't // passing nullptr as the channel makes the message invalid but we
// check for that anyways // don't check for that anyways
auto message = Communi::IrcMessage::fromData(content, nullptr); auto message = Communi::IrcMessage::fromData(content, nullptr);
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message); auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
assert(privMsg); assert(privMsg);
MessageParseArgs args; MessageParseArgs args;
TwitchMessageBuilder builder(&channel, privMsg, args); TwitchMessageBuilder builder(&channel, privMsg, args);
if (!builder.isIgnored()) { if (!builder.isIgnored()) {
messages.push_back(builder.build()); messages.push_back(builder.build());
}
} }
return messages;
} }
std::pair<Outcome, UsernameSet> parseChatters(const QJsonObject &jsonRoot)
{
static QStringList categories = {"moderators", "staff", "admins",
"global_mods", "viewers"};
return messages; auto usernames = UsernameSet();
}
std::pair<Outcome, UsernameSet> parseChatters(const QJsonObject &jsonRoot)
{
static QStringList categories = {"moderators", "staff", "admins",
"global_mods", "viewers"};
auto usernames = UsernameSet(); // parse json
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
// parse json for (const auto &category : categories) {
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject(); for (auto jsonCategory : jsonCategories.value(category).toArray()) {
usernames.insert(jsonCategory.toString());
for (const auto &category : categories) { }
for (auto jsonCategory : jsonCategories.value(category).toArray()) {
usernames.insert(jsonCategory.toString());
} }
}
return {Success, std::move(usernames)}; return {Success, std::move(usernames)};
} }
} // namespace } // namespace
TwitchChannel::TwitchChannel(const QString &name, TwitchChannel::TwitchChannel(const QString &name,
@ -309,7 +310,7 @@ bool TwitchChannel::isLive() const
} }
AccessGuard<const TwitchChannel::StreamStatus> AccessGuard<const TwitchChannel::StreamStatus>
TwitchChannel::accessStreamStatus() const TwitchChannel::accessStreamStatus() const
{ {
return this->streamStatus_.accessConst(); return this->streamStatus_.accessConst();
} }

View file

@ -8,235 +8,235 @@ namespace chatterino {
namespace { namespace {
template <typename Type> template <typename Type>
inline bool ReadValue(const rapidjson::Value &object, const char *key, inline bool ReadValue(const rapidjson::Value &object, const char *key,
Type &out) Type &out)
{ {
if (!object.HasMember(key)) { if (!object.HasMember(key)) {
return false;
}
const auto &value = object[key];
if (!value.Is<Type>()) {
return false;
}
out = value.Get<Type>();
return true;
}
template <>
inline bool ReadValue<QString>(const rapidjson::Value &object, const char *key,
QString &out)
{
if (!object.HasMember(key)) {
return false;
}
const auto &value = object[key];
if (!value.IsString()) {
return false;
}
out = value.GetString();
return true;
}
template <>
inline bool ReadValue<std::vector<QString>>(const rapidjson::Value &object,
const char *key,
std::vector<QString> &out)
{
if (!object.HasMember(key)) {
return false;
}
const auto &value = object[key];
if (!value.IsArray()) {
return false;
}
for (const rapidjson::Value &innerValue : value.GetArray()) {
if (!innerValue.IsString()) {
return false; return false;
} }
out.emplace_back(innerValue.GetString()); const auto &value = object[key];
}
return true; if (!value.Is<Type>()) {
}
// Parse a single cheermote set (or "action") from the twitch api
inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set,
const rapidjson::Value &action)
{
if (!action.IsObject()) {
return false;
}
if (!ReadValue(action, "prefix", set.prefix)) {
return false;
}
if (!ReadValue(action, "scales", set.scales)) {
return false;
}
if (!ReadValue(action, "backgrounds", set.backgrounds)) {
return false;
}
if (!ReadValue(action, "states", set.states)) {
return false;
}
if (!ReadValue(action, "type", set.type)) {
return false;
}
if (!ReadValue(action, "updated_at", set.updatedAt)) {
return false;
}
if (!ReadValue(action, "priority", set.priority)) {
return false;
}
// Tiers
if (!action.HasMember("tiers")) {
return false;
}
const auto &tiersValue = action["tiers"];
if (!tiersValue.IsArray()) {
return false;
}
for (const rapidjson::Value &tierValue : tiersValue.GetArray()) {
JSONCheermoteSet::CheermoteTier tier;
if (!tierValue.IsObject()) {
return false; return false;
} }
if (!ReadValue(tierValue, "min_bits", tier.minBits)) { out = value.Get<Type>();
return true;
}
template <>
inline bool ReadValue<QString>(const rapidjson::Value &object,
const char *key, QString &out)
{
if (!object.HasMember(key)) {
return false; return false;
} }
if (!ReadValue(tierValue, "id", tier.id)) { const auto &value = object[key];
if (!value.IsString()) {
return false; return false;
} }
if (!ReadValue(tierValue, "color", tier.color)) { out = value.GetString();
return true;
}
template <>
inline bool ReadValue<std::vector<QString>>(const rapidjson::Value &object,
const char *key,
std::vector<QString> &out)
{
if (!object.HasMember(key)) {
return false; return false;
} }
// Images const auto &value = object[key];
if (!tierValue.HasMember("images")) {
if (!value.IsArray()) {
return false; return false;
} }
const auto &imagesValue = tierValue["images"]; for (const rapidjson::Value &innerValue : value.GetArray()) {
if (!innerValue.IsString()) {
if (!imagesValue.IsObject()) { return false;
return false;
}
// Read images object
for (const auto &imageBackgroundValue : imagesValue.GetObject()) {
QString background = imageBackgroundValue.name.GetString();
bool backgroundExists = false;
for (const auto &bg : set.backgrounds) {
if (background == bg) {
backgroundExists = true;
break;
}
} }
if (!backgroundExists) { out.emplace_back(innerValue.GetString());
continue; }
return true;
}
// Parse a single cheermote set (or "action") from the twitch api
inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set,
const rapidjson::Value &action)
{
if (!action.IsObject()) {
return false;
}
if (!ReadValue(action, "prefix", set.prefix)) {
return false;
}
if (!ReadValue(action, "scales", set.scales)) {
return false;
}
if (!ReadValue(action, "backgrounds", set.backgrounds)) {
return false;
}
if (!ReadValue(action, "states", set.states)) {
return false;
}
if (!ReadValue(action, "type", set.type)) {
return false;
}
if (!ReadValue(action, "updated_at", set.updatedAt)) {
return false;
}
if (!ReadValue(action, "priority", set.priority)) {
return false;
}
// Tiers
if (!action.HasMember("tiers")) {
return false;
}
const auto &tiersValue = action["tiers"];
if (!tiersValue.IsArray()) {
return false;
}
for (const rapidjson::Value &tierValue : tiersValue.GetArray()) {
JSONCheermoteSet::CheermoteTier tier;
if (!tierValue.IsObject()) {
return false;
} }
const rapidjson::Value &imageBackgroundStates = if (!ReadValue(tierValue, "min_bits", tier.minBits)) {
imageBackgroundValue.value; return false;
if (!imageBackgroundStates.IsObject()) {
continue;
} }
// Read each key which represents a background if (!ReadValue(tierValue, "id", tier.id)) {
for (const auto &imageBackgroundState : return false;
imageBackgroundStates.GetObject()) { }
QString state = imageBackgroundState.name.GetString();
bool stateExists = false; if (!ReadValue(tierValue, "color", tier.color)) {
for (const auto &_state : set.states) { return false;
if (state == _state) { }
stateExists = true;
// Images
if (!tierValue.HasMember("images")) {
return false;
}
const auto &imagesValue = tierValue["images"];
if (!imagesValue.IsObject()) {
return false;
}
// Read images object
for (const auto &imageBackgroundValue : imagesValue.GetObject()) {
QString background = imageBackgroundValue.name.GetString();
bool backgroundExists = false;
for (const auto &bg : set.backgrounds) {
if (background == bg) {
backgroundExists = true;
break; break;
} }
} }
if (!stateExists) { if (!backgroundExists) {
continue; continue;
} }
const rapidjson::Value &imageScalesValue = const rapidjson::Value &imageBackgroundStates =
imageBackgroundState.value; imageBackgroundValue.value;
if (!imageScalesValue.IsObject()) { if (!imageBackgroundStates.IsObject()) {
continue; continue;
} }
// Read each key which represents a scale // Read each key which represents a background
for (const auto &imageScaleValue : for (const auto &imageBackgroundState :
imageScalesValue.GetObject()) { imageBackgroundStates.GetObject()) {
QString scale = imageScaleValue.name.GetString(); QString state = imageBackgroundState.name.GetString();
bool scaleExists = false; bool stateExists = false;
for (const auto &_scale : set.scales) { for (const auto &_state : set.states) {
if (scale == _scale) { if (state == _state) {
scaleExists = true; stateExists = true;
break; break;
} }
} }
if (!scaleExists) { if (!stateExists) {
continue; continue;
} }
const rapidjson::Value &imageScaleURLValue = const rapidjson::Value &imageScalesValue =
imageScaleValue.value; imageBackgroundState.value;
if (!imageScaleURLValue.IsString()) { if (!imageScalesValue.IsObject()) {
continue; continue;
} }
QString url = imageScaleURLValue.GetString(); // Read each key which represents a scale
for (const auto &imageScaleValue :
imageScalesValue.GetObject()) {
QString scale = imageScaleValue.name.GetString();
bool scaleExists = false;
for (const auto &_scale : set.scales) {
if (scale == _scale) {
scaleExists = true;
break;
}
}
bool ok = false; if (!scaleExists) {
qreal scaleNumber = scale.toFloat(&ok); continue;
if (!ok) { }
continue;
const rapidjson::Value &imageScaleURLValue =
imageScaleValue.value;
if (!imageScaleURLValue.IsString()) {
continue;
}
QString url = imageScaleURLValue.GetString();
bool ok = false;
qreal scaleNumber = scale.toFloat(&ok);
if (!ok) {
continue;
}
qreal chatterinoScale = 1 / scaleNumber;
auto image = Image::fromUrl({url}, chatterinoScale);
// TODO(pajlada): Fill in name and tooltip
tier.images[background][state][scale] = image;
} }
qreal chatterinoScale = 1 / scaleNumber;
auto image = Image::fromUrl({url}, chatterinoScale);
// TODO(pajlada): Fill in name and tooltip
tier.images[background][state][scale] = image;
} }
} }
set.tiers.emplace_back(tier);
} }
set.tiers.emplace_back(tier); return true;
} }
return true;
}
} // namespace } // namespace
// Look through the results of // Look through the results of

View file

@ -34,43 +34,43 @@ struct TwitchUser {
namespace pajlada { namespace pajlada {
namespace Settings { namespace Settings {
template <> template <>
struct Deserialize<chatterino::TwitchUser> { struct Deserialize<chatterino::TwitchUser> {
static chatterino::TwitchUser get(const rapidjson::Value &value, static chatterino::TwitchUser get(const rapidjson::Value &value,
bool *error = nullptr) bool *error = nullptr)
{ {
using namespace chatterino; using namespace chatterino;
TwitchUser user; TwitchUser user;
if (!value.IsObject()) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is wrong type");
return user;
}
if (!rj::getSafe(value, "_id", user.id)) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing ID key");
return user;
}
if (!rj::getSafe(value, "name", user.name)) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing name key");
return user;
}
if (!rj::getSafe(value, "display_name", user.displayName)) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing display name key");
return user;
}
if (!value.IsObject()) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is wrong type");
return user; return user;
} }
};
if (!rj::getSafe(value, "_id", user.id)) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing ID key");
return user;
}
if (!rj::getSafe(value, "name", user.name)) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing name key");
return user;
}
if (!rj::getSafe(value, "display_name", user.displayName)) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing display name key");
return user;
}
return user;
}
};
} // namespace Settings } // namespace Settings
} // namespace pajlada } // namespace pajlada

View file

@ -9,16 +9,16 @@
#include <QtGlobal> #include <QtGlobal>
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
#define DEFAULT_FONT_FAMILY "Segoe UI" # define DEFAULT_FONT_FAMILY "Segoe UI"
#define DEFAULT_FONT_SIZE 10 # define DEFAULT_FONT_SIZE 10
#else #else
#ifdef Q_OS_MACOS # ifdef Q_OS_MACOS
#define DEFAULT_FONT_FAMILY "Helvetica Neue" # define DEFAULT_FONT_FAMILY "Helvetica Neue"
#define DEFAULT_FONT_SIZE 12 # define DEFAULT_FONT_SIZE 12
#else # else
#define DEFAULT_FONT_FAMILY "Arial" # define DEFAULT_FONT_FAMILY "Arial"
#define DEFAULT_FONT_SIZE 11 # define DEFAULT_FONT_SIZE 11
#endif # endif
#endif #endif
namespace chatterino { namespace chatterino {
@ -104,8 +104,7 @@ Fonts::FontData Fonts::createFontData(FontStyle type, float scale)
{FontStyle::ChatMediumSmall, {0.8f, false, QFont::Normal}}, {FontStyle::ChatMediumSmall, {0.8f, false, QFont::Normal}},
{FontStyle::ChatMedium, {1, false, QFont::Normal}}, {FontStyle::ChatMedium, {1, false, QFont::Normal}},
{FontStyle::ChatMediumBold, {FontStyle::ChatMediumBold,
{1, false, {1, false, QFont::Weight(getSettings()->boldScale.getValue())}},
QFont::Weight(getSettings()->boldScale.getValue())}},
{FontStyle::ChatMediumItalic, {1, true, QFont::Normal}}, {FontStyle::ChatMediumItalic, {1, true, QFont::Normal}},
{FontStyle::ChatLarge, {1.2f, false, QFont::Normal}}, {FontStyle::ChatLarge, {1.2f, false, QFont::Normal}},
{FontStyle::ChatVeryLarge, {1.4f, false, QFont::Normal}}, {FontStyle::ChatVeryLarge, {1.4f, false, QFont::Normal}},

View file

@ -17,11 +17,11 @@
namespace ipc = boost::interprocess; namespace ipc = boost::interprocess;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <QProcess> # include <QProcess>
#include <Windows.h> # include <Windows.h>
#include "singletons/WindowManager.hpp" # include "singletons/WindowManager.hpp"
#include "widgets/AttachedWindow.hpp" # include "widgets/AttachedWindow.hpp"
#endif #endif
#include <iostream> #include <iostream>

View file

@ -10,21 +10,21 @@ namespace chatterino {
namespace detail { namespace detail {
double getMultiplierByTheme(const QString &themeName) double getMultiplierByTheme(const QString &themeName)
{ {
if (themeName == "Light") { if (themeName == "Light") {
return 0.8; return 0.8;
} else if (themeName == "White") { } else if (themeName == "White") {
return 1.0; return 1.0;
} else if (themeName == "Black") { } else if (themeName == "Black") {
return -1.0; return -1.0;
} else if (themeName == "Dark") { } else if (themeName == "Dark") {
return -0.8;
}
return -0.8; return -0.8;
} }
return -0.8;
}
} // namespace detail } // namespace detail
Theme::Theme() Theme::Theme()

View file

@ -2,11 +2,11 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
void appendDuration(int count, QChar &&order, QString &outString) void appendDuration(int count, QChar &&order, QString &outString)
{ {
outString.append(QString::number(count)); outString.append(QString::number(count));
outString.append(order); outString.append(order);
} }
} // namespace } // namespace
QString formatTime(int totalSeconds) QString formatTime(int totalSeconds)

View file

@ -4,7 +4,7 @@
namespace pajlada { namespace pajlada {
namespace Signals { namespace Signals {
class SignalHolder; class SignalHolder;
} }
} // namespace pajlada } // namespace pajlada

View file

@ -6,38 +6,38 @@
namespace pajlada { namespace pajlada {
namespace Settings { namespace Settings {
template <> template <>
struct Serialize<QString> { struct Serialize<QString> {
static rapidjson::Value get(const QString &value, static rapidjson::Value get(const QString &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
return rapidjson::Value(value.toUtf8(), a); return rapidjson::Value(value.toUtf8(), a);
} }
}; };
template <>
struct Deserialize<QString> {
static QString get(const rapidjson::Value &value, bool *error = nullptr)
{
if (!value.IsString()) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is not a string");
return QString{};
}
try {
return QString::fromUtf8(value.GetString(),
value.GetStringLength());
} catch (const std::exception &) {
// int x = 5;
} catch (...) {
// int y = 5;
}
template <>
struct Deserialize<QString> {
static QString get(const rapidjson::Value &value, bool *error = nullptr)
{
if (!value.IsString()) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is not a string");
return QString{}; return QString{};
} }
};
try {
return QString::fromUtf8(value.GetString(),
value.GetStringLength());
} catch (const std::exception &) {
// int x = 5;
} catch (...) {
// int y = 5;
}
return QString{};
}
};
} // namespace Settings } // namespace Settings
} // namespace pajlada } // namespace pajlada

View file

@ -5,26 +5,28 @@
namespace chatterino { namespace chatterino {
namespace rj { namespace rj {
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)
obj.AddMember(rapidjson::Value(key, a).Move(), value, a); {
} obj.AddMember(rapidjson::Value(key, a).Move(), value, 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)
obj.AddMember(rapidjson::Value(key, a).Move(), value.Move(), a); {
} obj.AddMember(rapidjson::Value(key, a).Move(), value.Move(), a);
}
std::string stringify(const rapidjson::Value &value) std::string stringify(const rapidjson::Value &value)
{ {
rapidjson::StringBuffer buffer; rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
value.Accept(writer); value.Accept(writer);
return std::string(buffer.GetString()); return std::string(buffer.GetString());
} }
} // namespace rj } // namespace rj
} // namespace chatterino } // namespace chatterino

View file

@ -11,91 +11,95 @@
namespace chatterino { namespace chatterino {
namespace rj { namespace rj {
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,
void addMember(rapidjson::Value &obj, const char *key, rapidjson::Value &value, rapidjson::Document::AllocatorType &a);
rapidjson::Document::AllocatorType &a); void addMember(rapidjson::Value &obj, const char *key,
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()); assert(obj.IsObject());
addMember(obj, key, pajlada::Settings::Serialize<Type>::get(value, a), a); addMember(obj, key, pajlada::Settings::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::Settings::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::Settings::Serialize<Type>::get(value, a), a);
}
template <typename Type>
bool getSafe(const rapidjson::Value &obj, const char *key, Type &out)
{
if (!obj.IsObject()) {
return false;
} }
if (!obj.HasMember(key)) { template <>
return false; 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);
} }
if (obj.IsNull()) { template <typename Type>
return false; void set(rapidjson::Document &obj, const char *key, const Type &value)
{
assert(obj.IsObject());
auto &a = obj.GetAllocator();
addMember(obj, key, pajlada::Settings::Serialize<Type>::get(value, a),
a);
} }
bool error = false; template <>
out = pajlada::Settings::Deserialize<Type>::get(obj[key], &error); inline void set(rapidjson::Document &obj, const char *key,
const rapidjson::Value &value)
{
assert(obj.IsObject());
return !error; auto &a = obj.GetAllocator();
}
template <typename Type> addMember(obj, key, const_cast<rapidjson::Value &>(value), a);
bool getSafe(const rapidjson::Value &value, Type &out) }
{
bool error = false;
out = pajlada::Settings::Deserialize<Type>::get(value, &error);
return !error; template <typename Type>
} void add(rapidjson::Value &arr, const Type &value,
rapidjson::Document::AllocatorType &a)
{
assert(arr.IsArray());
std::string stringify(const rapidjson::Value &value); arr.PushBack(pajlada::Settings::Serialize<Type>::get(value, a), a);
}
template <typename Type>
bool getSafe(const rapidjson::Value &obj, const char *key, Type &out)
{
if (!obj.IsObject()) {
return false;
}
if (!obj.HasMember(key)) {
return false;
}
if (obj.IsNull()) {
return false;
}
bool error = false;
out = pajlada::Settings::Deserialize<Type>::get(obj[key], &error);
return !error;
}
template <typename Type>
bool getSafe(const rapidjson::Value &value, Type &out)
{
bool error = false;
out = pajlada::Settings::Deserialize<Type>::get(value, &error);
return !error;
}
std::string stringify(const rapidjson::Value &value);
} // namespace rj } // namespace rj
} // namespace chatterino } // namespace chatterino

View file

@ -2,9 +2,9 @@
#include "Application.hpp" #include "Application.hpp"
#include "Helpers.hpp" #include "Helpers.hpp"
#include "debug/Log.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "widgets/dialogs/QualityPopup.hpp" #include "widgets/dialogs/QualityPopup.hpp"
#include "debug/Log.hpp"
#include <QErrorMessage> #include <QErrorMessage>
#include <QFileInfo> #include <QFileInfo>
@ -16,88 +16,89 @@ namespace chatterino {
namespace { namespace {
const char *getBinaryName() const char *getBinaryName()
{ {
#ifdef _WIN32 #ifdef _WIN32
return "streamlink.exe"; return "streamlink.exe";
#else #else
return "streamlink"; return "streamlink";
#endif #endif
} }
const char *getDefaultBinaryPath() const char *getDefaultBinaryPath()
{ {
#ifdef _WIN32 #ifdef _WIN32
return "C:\\Program Files (x86)\\Streamlink\\bin\\streamlink.exe"; return "C:\\Program Files (x86)\\Streamlink\\bin\\streamlink.exe";
#else #else
return "/usr/bin/streamlink"; return "/usr/bin/streamlink";
#endif #endif
}
QString getStreamlinkProgram()
{
auto app = getApp();
if (getSettings()->streamlinkUseCustomPath) {
return getSettings()->streamlinkPath + "/" + getBinaryName();
} else {
return getBinaryName();
}
}
bool checkStreamlinkPath(const QString &path)
{
QFileInfo fileinfo(path);
if (!fileinfo.exists()) {
return false;
// throw Exception(fS("Streamlink path ({}) is invalid, file does not
// exist", path));
} }
return fileinfo.isExecutable(); QString getStreamlinkProgram()
} {
auto app = getApp();
void showStreamlinkNotFoundError() if (getSettings()->streamlinkUseCustomPath) {
{ return getSettings()->streamlinkPath + "/" + getBinaryName();
static QErrorMessage *msg = new QErrorMessage;
auto app = getApp();
if (getSettings()->streamlinkUseCustomPath) {
msg->showMessage(
"Unable to find Streamlink executable\nMake sure your custom path "
"is pointing "
"to the DIRECTORY where the streamlink executable is located");
} else {
msg->showMessage(
"Unable to find Streamlink executable.\nIf you have Streamlink "
"installed, you might need to enable the custom path option");
}
}
QProcess *createStreamlinkProcess()
{
auto p = new QProcess;
p->setProgram(getStreamlinkProgram());
QObject::connect(p, &QProcess::errorOccurred, [=](auto err) {
if (err == QProcess::FailedToStart) {
showStreamlinkNotFoundError();
} else { } else {
log("Error occured {}", err); return getBinaryName();
}
}
bool checkStreamlinkPath(const QString &path)
{
QFileInfo fileinfo(path);
if (!fileinfo.exists()) {
return false;
// throw Exception(fS("Streamlink path ({}) is invalid, file does
// not exist", path));
} }
p->deleteLater(); return fileinfo.isExecutable();
}); }
QObject::connect(p, void showStreamlinkNotFoundError()
static_cast<void (QProcess::*)(int)>(&QProcess::finished), {
[=](int res) { static QErrorMessage *msg = new QErrorMessage;
p->deleteLater(); //
});
return p; auto app = getApp();
} if (getSettings()->streamlinkUseCustomPath) {
msg->showMessage(
"Unable to find Streamlink executable\nMake sure your custom "
"path "
"is pointing "
"to the DIRECTORY where the streamlink executable is located");
} else {
msg->showMessage(
"Unable to find Streamlink executable.\nIf you have Streamlink "
"installed, you might need to enable the custom path option");
}
}
QProcess *createStreamlinkProcess()
{
auto p = new QProcess;
p->setProgram(getStreamlinkProgram());
QObject::connect(p, &QProcess::errorOccurred, [=](auto err) {
if (err == QProcess::FailedToStart) {
showStreamlinkNotFoundError();
} else {
log("Error occured {}", err);
}
p->deleteLater();
});
QObject::connect(
p, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
[=](int res) {
p->deleteLater(); //
});
return p;
}
} // namespace } // namespace

View file

@ -2,8 +2,8 @@
#ifdef USEWINSDK #ifdef USEWINSDK
#include <Windows.h> # include <Windows.h>
#include <boost/optional.hpp> # include <boost/optional.hpp>
namespace chatterino { namespace chatterino {

View file

@ -8,12 +8,12 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#ifdef USEWINSDK #ifdef USEWINSDK
#include "util/WindowsHelper.hpp" # include "util/WindowsHelper.hpp"
#include "Windows.h" # include "Windows.h"
// don't even think about reordering these // don't even think about reordering these
#include "Psapi.h" # include "Psapi.h"
#pragma comment(lib, "Dwmapi.lib") # pragma comment(lib, "Dwmapi.lib")
#endif #endif
namespace chatterino { namespace chatterino {

View file

@ -20,20 +20,20 @@
#include <functional> #include <functional>
#ifdef USEWINSDK #ifdef USEWINSDK
#include <ObjIdl.h> # include <ObjIdl.h>
#include <VersionHelpers.h> # include <VersionHelpers.h>
#include <Windows.h> # include <Windows.h>
#include <dwmapi.h> # include <dwmapi.h>
#include <gdiplus.h> # include <gdiplus.h>
#include <windowsx.h> # include <windowsx.h>
//#include <ShellScalingApi.h> //#include <ShellScalingApi.h>
#pragma comment(lib, "Dwmapi.lib") # pragma comment(lib, "Dwmapi.lib")
#include <QHBoxLayout> # include <QHBoxLayout>
#include <QVBoxLayout> # include <QVBoxLayout>
#define WM_DPICHANGED 0x02E0 # define WM_DPICHANGED 0x02E0
#endif #endif
#include "widgets/helper/TitlebarButton.hpp" #include "widgets/helper/TitlebarButton.hpp"
@ -460,11 +460,11 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
long *result) long *result)
{ {
#ifdef USEWINSDK #ifdef USEWINSDK
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) # if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
MSG *msg = *reinterpret_cast<MSG **>(message); MSG *msg = *reinterpret_cast<MSG **>(message);
#else # else
MSG *msg = reinterpret_cast<MSG *>(message); MSG *msg = reinterpret_cast<MSG *>(message);
#endif # endif
bool returnValue = false; bool returnValue = false;

View file

@ -433,8 +433,7 @@ void SplitNotebook::addCustomButtons()
// settings // settings
auto settingsBtn = this->addCustomButton(); auto settingsBtn = this->addCustomButton();
settingsBtn->setVisible( settingsBtn->setVisible(!getSettings()->hidePreferencesButton.getValue());
!getSettings()->hidePreferencesButton.getValue());
getSettings()->hidePreferencesButton.connect( getSettings()->hidePreferencesButton.connect(
[settingsBtn](bool hide, auto) { settingsBtn->setVisible(!hide); }, [settingsBtn](bool hide, auto) { settingsBtn->setVisible(!hide); },

View file

@ -7,7 +7,7 @@
#include "widgets/splits/Split.hpp" #include "widgets/splits/Split.hpp"
#ifdef USEWEBENGINE #ifdef USEWEBENGINE
#include <QtWebEngineWidgets> # include <QtWebEngineWidgets>
#endif #endif
namespace chatterino { namespace chatterino {

View file

@ -10,7 +10,7 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#ifdef USEWINSDK #ifdef USEWINSDK
#include <Windows.h> # include <Windows.h>
#endif #endif
namespace chatterino { namespace chatterino {

View file

@ -16,55 +16,57 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
auto makeTitleMessage(const QString &title) auto makeTitleMessage(const QString &title)
{ {
MessageBuilder builder; MessageBuilder builder;
builder.emplace<TextElement>(title, MessageElementFlag::Text); builder.emplace<TextElement>(title, MessageElementFlag::Text);
builder->flags.set(MessageFlag::Centered); builder->flags.set(MessageFlag::Centered);
return builder.release(); return builder.release();
}
auto makeEmoteMessage(const EmoteMap &map)
{
MessageBuilder builder;
builder->flags.set(MessageFlag::Centered);
builder->flags.set(MessageFlag::DisableCompactEmotes);
for (const auto &emote : map) {
builder
.emplace<EmoteElement>(emote.second, MessageElementFlag::AlwaysShow)
->setLink(Link(Link::InsertText, emote.first.string));
} }
auto makeEmoteMessage(const EmoteMap &map)
return builder.release(); {
}
void addEmoteSets(std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets,
Channel &globalChannel, Channel &subChannel)
{
for (const auto &set : sets) {
auto &channel = set->key == "0" ? globalChannel : subChannel;
// TITLE
auto text =
set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text;
channel.addMessage(makeTitleMessage(text));
// EMOTES
MessageBuilder builder; MessageBuilder builder;
builder->flags.set(MessageFlag::Centered); builder->flags.set(MessageFlag::Centered);
builder->flags.set(MessageFlag::DisableCompactEmotes); builder->flags.set(MessageFlag::DisableCompactEmotes);
for (const auto &emote : set->emotes) { for (const auto &emote : map) {
builder builder
.emplace<EmoteElement>( .emplace<EmoteElement>(emote.second,
getApp()->emotes->twitch.getOrCreateEmote(emote.id, MessageElementFlag::AlwaysShow)
emote.name), ->setLink(Link(Link::InsertText, emote.first.string));
MessageElementFlag::AlwaysShow)
->setLink(Link(Link::InsertText, emote.name.string));
} }
channel.addMessage(builder.release()); return builder.release();
}
void addEmoteSets(
std::vector<std::shared_ptr<TwitchAccount::EmoteSet>> sets,
Channel &globalChannel, Channel &subChannel)
{
for (const auto &set : sets) {
auto &channel = set->key == "0" ? globalChannel : subChannel;
// TITLE
auto text =
set->key == "0" || set->text.isEmpty() ? "Twitch" : set->text;
channel.addMessage(makeTitleMessage(text));
// EMOTES
MessageBuilder builder;
builder->flags.set(MessageFlag::Centered);
builder->flags.set(MessageFlag::DisableCompactEmotes);
for (const auto &emote : set->emotes) {
builder
.emplace<EmoteElement>(
getApp()->emotes->twitch.getOrCreateEmote(emote.id,
emote.name),
MessageElementFlag::AlwaysShow)
->setLink(Link(Link::InsertText, emote.name.string));
}
channel.addMessage(builder.release());
}
} }
}
} // namespace } // namespace
EmotePopup::EmotePopup() EmotePopup::EmotePopup()

View file

@ -7,7 +7,7 @@
#include "providers/twitch/PartialTwitchUser.hpp" #include "providers/twitch/PartialTwitchUser.hpp"
#ifdef USEWINSDK #ifdef USEWINSDK
#include <Windows.h> # include <Windows.h>
#endif #endif
#include <QClipboard> #include <QClipboard>
@ -21,54 +21,54 @@ namespace chatterino {
namespace { namespace {
void LogInWithCredentials(const std::string &userID, void LogInWithCredentials(const std::string &userID,
const std::string &username, const std::string &username,
const std::string &clientID, const std::string &clientID,
const std::string &oauthToken) const std::string &oauthToken)
{ {
QStringList errors; QStringList errors;
if (userID.empty()) { if (userID.empty()) {
errors.append("Missing user ID"); errors.append("Missing user ID");
}
if (username.empty()) {
errors.append("Missing username");
}
if (clientID.empty()) {
errors.append("Missing Client ID");
}
if (oauthToken.empty()) {
errors.append("Missing OAuth Token");
}
if (errors.length() > 0) {
QMessageBox messageBox;
messageBox.setIcon(QMessageBox::Critical);
messageBox.setText(errors.join("<br />"));
messageBox.setStandardButtons(QMessageBox::Ok);
messageBox.exec();
return;
}
// QMessageBox messageBox;
// messageBox.setIcon(QMessageBox::Information);
// messageBox.setText("Successfully logged in with user <b>" +
// qS(username) + "</b>!");
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/username", username);
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/userID", userID);
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/clientID", clientID);
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/oauthToken", oauthToken);
getApp()->accounts->twitch.reloadUsers();
// messageBox.exec();
getApp()->accounts->twitch.currentUsername = username;
} }
if (username.empty()) {
errors.append("Missing username");
}
if (clientID.empty()) {
errors.append("Missing Client ID");
}
if (oauthToken.empty()) {
errors.append("Missing OAuth Token");
}
if (errors.length() > 0) {
QMessageBox messageBox;
messageBox.setIcon(QMessageBox::Critical);
messageBox.setText(errors.join("<br />"));
messageBox.setStandardButtons(QMessageBox::Ok);
messageBox.exec();
return;
}
// QMessageBox messageBox;
// messageBox.setIcon(QMessageBox::Information);
// messageBox.setText("Successfully logged in with user <b>" +
// qS(username) + "</b>!");
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/username", username);
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/userID", userID);
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/clientID", clientID);
pajlada::Settings::Setting<std::string>::set(
"/accounts/uid" + userID + "/oauthToken", oauthToken);
getApp()->accounts->twitch.reloadUsers();
// messageBox.exec();
getApp()->accounts->twitch.currentUsername = username;
}
} // namespace } // namespace

View file

@ -39,62 +39,62 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
void addEmoteContextMenuItems(const Emote &emote, void addEmoteContextMenuItems(const Emote &emote,
MessageElementFlags creatorFlags, QMenu &menu) MessageElementFlags creatorFlags, QMenu &menu)
{ {
auto openAction = menu.addAction("Open"); auto openAction = menu.addAction("Open");
auto openMenu = new QMenu; auto openMenu = new QMenu;
openAction->setMenu(openMenu); openAction->setMenu(openMenu);
auto copyAction = menu.addAction("Copy"); auto copyAction = menu.addAction("Copy");
auto copyMenu = new QMenu; auto copyMenu = new QMenu;
copyAction->setMenu(copyMenu); copyAction->setMenu(copyMenu);
// see if the QMenu actually gets destroyed // see if the QMenu actually gets destroyed
QObject::connect(openMenu, &QMenu::destroyed, [] { QObject::connect(openMenu, &QMenu::destroyed, [] {
QMessageBox(QMessageBox::Information, "xD", "the menu got deleted") QMessageBox(QMessageBox::Information, "xD", "the menu got deleted")
.exec(); .exec();
}); });
// Add copy and open links for 1x, 2x, 3x
auto addImageLink = [&](const ImagePtr &image, char scale) {
if (!image->isEmpty()) {
copyMenu->addAction(
QString(scale) + "x link", [url = image->url()] {
QApplication::clipboard()->setText(url.string);
});
openMenu->addAction(
QString(scale) + "x link", [url = image->url()] {
QDesktopServices::openUrl(QUrl(url.string));
});
}
};
addImageLink(emote.images.getImage1(), '1');
addImageLink(emote.images.getImage2(), '2');
addImageLink(emote.images.getImage3(), '3');
// Copy and open emote page link
auto addPageLink = [&](const QString &name) {
copyMenu->addSeparator();
openMenu->addSeparator();
// Add copy and open links for 1x, 2x, 3x
auto addImageLink = [&](const ImagePtr &image, char scale) {
if (!image->isEmpty()) {
copyMenu->addAction( copyMenu->addAction(
QString(scale) + "x link", [url = image->url()] { "Copy " + name + " emote link", [url = emote.homePage] {
QApplication::clipboard()->setText(url.string); QApplication::clipboard()->setText(url.string); //
}); });
openMenu->addAction(QString(scale) + "x link", openMenu->addAction(
[url = image->url()] { "Open " + name + " emote link", [url = emote.homePage] {
QDesktopServices::openUrl(QUrl(url.string)); QDesktopServices::openUrl(QUrl(url.string)); //
}); });
};
if (creatorFlags.has(MessageElementFlag::BttvEmote)) {
addPageLink("BTTV");
} else if (creatorFlags.has(MessageElementFlag::FfzEmote)) {
addPageLink("FFZ");
} }
};
addImageLink(emote.images.getImage1(), '1');
addImageLink(emote.images.getImage2(), '2');
addImageLink(emote.images.getImage3(), '3');
// Copy and open emote page link
auto addPageLink = [&](const QString &name) {
copyMenu->addSeparator();
openMenu->addSeparator();
copyMenu->addAction(
"Copy " + name + " emote link", [url = emote.homePage] {
QApplication::clipboard()->setText(url.string); //
});
openMenu->addAction("Open " + name + " emote link",
[url = emote.homePage] {
QDesktopServices::openUrl(QUrl(url.string)); //
});
};
if (creatorFlags.has(MessageElementFlag::BttvEmote)) {
addPageLink("BTTV");
} else if (creatorFlags.has(MessageElementFlag::FfzEmote)) {
addPageLink("FFZ");
} }
}
} // namespace } // namespace
ChannelView::ChannelView(BaseWidget *parent) ChannelView::ChannelView(BaseWidget *parent)

View file

@ -9,15 +9,13 @@
"Choose", "Source", "High", "Medium", "Low", "Audio only" "Choose", "Source", "High", "Medium", "Low", "Audio only"
namespace chatterino { namespace chatterino {
namespace { namespace {
QString createLink(const QString &url, const QString &name)
QString CreateLink(const QString &url, const QString &name) {
{ return QString("<a href=\"" + url +
return QString("<a href=\"" + url + "\"><span style=\"color: white;\">" + "\"><span style=\"color: white;\">" + name +
name + "</span></a>"); "</span></a>");
} }
} // namespace } // namespace
ExternalToolsPage::ExternalToolsPage() ExternalToolsPage::ExternalToolsPage()
@ -41,8 +39,8 @@ ExternalToolsPage::ExternalToolsPage()
description->setStyleSheet("color: #bbb"); description->setStyleSheet("color: #bbb");
auto links = new QLabel( auto links = new QLabel(
CreateLink("https://streamlink.github.io/", "Website") + " " + createLink("https://streamlink.github.io/", "Website") + " " +
CreateLink( createLink(
"https://github.com/streamlink/streamlink/releases/latest", "https://github.com/streamlink/streamlink/releases/latest",
"Download")); "Download"));
links->setTextFormat(Qt::RichText); links->setTextFormat(Qt::RichText);

View file

@ -69,9 +69,8 @@ void addPhrasesTab(LayoutCreator<QVBoxLayout> layout)
void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> users, void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> users,
QStringListModel &userModel) QStringListModel &userModel)
{ {
users.append( users.append(page.createCheckBox("Enable twitch ignored users",
page.createCheckBox("Enable twitch ignored users", getSettings()->enableTwitchIgnoredUsers));
getSettings()->enableTwitchIgnoredUsers));
auto anyways = users.emplace<QHBoxLayout>().withoutMargin(); auto anyways = users.emplace<QHBoxLayout>().withoutMargin();
{ {

View file

@ -31,9 +31,9 @@
// clang-format on // clang-format on
#ifdef USEWINSDK #ifdef USEWINSDK
#define WINDOW_TOPMOST "Window always on top" # define WINDOW_TOPMOST "Window always on top"
#else #else
#define WINDOW_TOPMOST "Window always on top (requires restart)" # define WINDOW_TOPMOST "Window always on top (requires restart)"
#endif #endif
#define INPUT_EMPTY "Show input box when empty" #define INPUT_EMPTY "Show input box when empty"
#define LAST_MSG "Mark the last message you read" #define LAST_MSG "Mark the last message you read"

View file

@ -25,7 +25,7 @@
namespace chatterino { namespace chatterino {
inline QString CreateLink(const QString &url, bool file = false) inline QString createLink(const QString &url, bool file = false)
{ {
if (file) { if (file) {
return QString("<a href=\"file:///" + url + return QString("<a href=\"file:///" + url +

View file

@ -26,86 +26,87 @@
#include <cmath> #include <cmath>
#ifdef USEWEBENGINE #ifdef USEWEBENGINE
#include "widgets/StreamView.hpp" # include "widgets/StreamView.hpp"
#endif #endif
namespace chatterino { namespace chatterino {
namespace { namespace {
auto formatRoomMode(TwitchChannel &channel) -> QString auto formatRoomMode(TwitchChannel &channel) -> QString
{
QString text;
{ {
auto modes = channel.accessRoomModes(); QString text;
if (modes->r9k) text += "r9k, "; {
if (modes->slowMode) auto modes = channel.accessRoomModes();
text += QString("slow(%1), ").arg(QString::number(modes->slowMode));
if (modes->emoteOnly) text += "emote, "; if (modes->r9k) text += "r9k, ";
if (modes->submode) text += "sub, "; if (modes->slowMode)
text +=
QString("slow(%1), ").arg(QString::number(modes->slowMode));
if (modes->emoteOnly) text += "emote, ";
if (modes->submode) text += "sub, ";
}
if (text.length() > 2) {
text = text.mid(0, text.size() - 2);
}
if (!text.isEmpty()) {
static QRegularExpression commaReplacement("^(.+?, .+?,) (.+)$");
auto match = commaReplacement.match(text);
if (match.hasMatch())
text = match.captured(1) + '\n' + match.captured(2);
}
if (text.isEmpty() && channel.hasModRights()) return "none";
return text;
} }
auto formatTooltip(const TwitchChannel::StreamStatus &s)
if (text.length() > 2) { {
text = text.mid(0, text.size() - 2); return QStringList{"<style>.center { text-align: center; }</style>",
"<p class=\"center\">",
s.title,
"<br><br>",
s.game,
"<br>",
s.rerun ? "Vod-casting" : "Live",
" for ",
s.uptime,
" with ",
QString::number(s.viewerCount),
" viewers",
"</p>"}
.join("");
} }
auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings)
{
auto title = QString();
if (!text.isEmpty()) { // live
static QRegularExpression commaReplacement("^(.+?, .+?,) (.+)$"); if (s.rerun)
title += " (rerun)";
else if (s.streamType.isEmpty())
title += " (" + s.streamType + ")";
else
title += " (live)";
auto match = commaReplacement.match(text); // description
if (match.hasMatch()) if (settings.showViewerCount)
text = match.captured(1) + '\n' + match.captured(2); title += " - " + QString::number(s.viewerCount);
if (settings.showTitle) title += " - " + s.title;
if (settings.showGame) title += " - " + s.game;
if (settings.showUptime) title += " - " + s.uptime;
return title;
} }
auto distance(QPoint a, QPoint b)
{
auto x = std::abs(a.x() - b.x());
auto y = std::abs(a.y() - b.y());
if (text.isEmpty() && channel.hasModRights()) return "none"; return std::sqrt(x * x + y * y);
}
return text;
}
auto formatTooltip(const TwitchChannel::StreamStatus &s)
{
return QStringList{"<style>.center { text-align: center; }</style>",
"<p class=\"center\">",
s.title,
"<br><br>",
s.game,
"<br>",
s.rerun ? "Vod-casting" : "Live",
" for ",
s.uptime,
" with ",
QString::number(s.viewerCount),
" viewers",
"</p>"}
.join("");
}
auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings)
{
auto title = QString();
// live
if (s.rerun)
title += " (rerun)";
else if (s.streamType.isEmpty())
title += " (" + s.streamType + ")";
else
title += " (live)";
// description
if (settings.showViewerCount)
title += " - " + QString::number(s.viewerCount);
if (settings.showTitle) title += " - " + s.title;
if (settings.showGame) title += " - " + s.game;
if (settings.showUptime) title += " - " + s.uptime;
return title;
}
auto distance(QPoint a, QPoint b)
{
auto x = std::abs(a.x() - b.x());
auto y = std::abs(a.y() - b.y());
return std::sqrt(x * x + y * y);
}
} // namespace } // namespace
SplitHeader::SplitHeader(Split *_split) SplitHeader::SplitHeader(Split *_split)