mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
changed .clang-format
This commit is contained in:
parent
44c16f1b3a
commit
af7b742a23
46 changed files with 1636 additions and 1611 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
138
src/RunGui.cpp
138
src/RunGui.cpp
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -2,5 +2,4 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -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 ¤tEmotes)
|
|
||||||
{
|
|
||||||
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 ¤tEmotes)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 ¤tEmotes)
|
|
||||||
{
|
|
||||||
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 ¤tEmotes)
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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"}, {"\\>\\;\\(", ">("},
|
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("},
|
||||||
{"\\<\\;3", "<3"}, {"\\:-?(o|O)", ":O"},
|
{"\\<\\;3", "<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("<", "<");
|
||||||
|
cleanCode.replace(">", ">");
|
||||||
|
|
||||||
|
return {cleanCode};
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanCode.replace("<", "<");
|
|
||||||
cleanCode.replace(">", ">");
|
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}},
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
namespace pajlada {
|
namespace pajlada {
|
||||||
namespace Signals {
|
namespace Signals {
|
||||||
class SignalHolder;
|
class SignalHolder;
|
||||||
}
|
}
|
||||||
} // namespace pajlada
|
} // namespace pajlada
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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); },
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#ifdef USEWINSDK
|
#ifdef USEWINSDK
|
||||||
#include <Windows.h>
|
# include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
{
|
{
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 +
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue