Merge branch 'master' into apa-notification-on-live

This commit is contained in:
apa420 2018-08-28 23:23:46 +02:00 committed by GitHub
commit 6a29fbb6dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
214 changed files with 4368 additions and 4428 deletions

View file

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

View file

@ -10,11 +10,11 @@
### OpenSSL ### OpenSSL
#### For our websocket library, we need OpenSSL 1.1 #### For our websocket library, we need OpenSSL 1.1
1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0h.exe 1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0i.exe
2. When prompted, install openssl to C:\local\openssl 2. When prompted, install openssl to C:\local\openssl
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory" 3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory"
#### For Qt SSL, we need OpenSSL 1.0 #### For Qt SSL, we need OpenSSL 1.0
1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2o.exe 1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2p.exe
2. When prompted, install it anywhere 2. When prompted, install it anywhere
3. When prompted, copy the OpenSSL DLLS to "The OpenSSL binaries (/bin) directory" 3. When prompted, copy the OpenSSL DLLS to "The OpenSSL binaries (/bin) directory"
4. Copy the OpenSSL 1.0 files from its /bin folder to C:/local/bin (You will need to create the folder) 4. Copy the OpenSSL 1.0 files from its /bin folder to C:/local/bin (You will need to create the folder)

4
Jenkinsfile vendored
View file

@ -6,12 +6,12 @@ pipeline {
parallel { parallel {
stage('GCC') { stage('GCC') {
steps { steps {
sh 'mkdir -p build-linux-gcc && cd build-linux-gcc && qmake .. && make' sh 'mkdir -p build-linux-gcc && cd build-linux-gcc && make distclean; qmake .. && make'
} }
} }
stage('Clang') { stage('Clang') {
steps { steps {
sh 'mkdir -p build-linux-clang && cd build-linux-clang && qmake -spec linux-clang .. && make' sh 'mkdir -p build-linux-clang && cd build-linux-clang && make distclean; qmake -spec linux-clang .. && make'
} }
} }
} }

View file

@ -6,7 +6,7 @@
message(----) message(----)
QT += widgets core gui network multimedia svg QT += widgets core gui network multimedia svg concurrent
CONFIG += communi CONFIG += communi
COMMUNI += core model util COMMUNI += core model util
CONFIG += c++14 CONFIG += c++14
@ -33,7 +33,7 @@ equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
} }
# Icons # Icons
macx:ICON = resources/images/chatterino2.icns #macx:ICON = resources/images/chatterino2.icns
win32:RC_FILE = resources/windows.rc win32:RC_FILE = resources/windows.rc
@ -72,6 +72,8 @@ win32 {
# OSX include directory # OSX include directory
macx { macx {
INCLUDEPATH += /usr/local/include INCLUDEPATH += /usr/local/include
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
} }
# Optional dependency on Windows SDK 7 # Optional dependency on Windows SDK 7
@ -106,7 +108,6 @@ SOURCES += \
src/Application.cpp \ src/Application.cpp \
src/common/Channel.cpp \ src/common/Channel.cpp \
src/common/CompletionModel.cpp \ src/common/CompletionModel.cpp \
src/common/Emotemap.cpp \
src/common/NetworkData.cpp \ src/common/NetworkData.cpp \
src/common/NetworkManager.cpp \ src/common/NetworkManager.cpp \
src/common/NetworkRequest.cpp \ src/common/NetworkRequest.cpp \
@ -188,8 +189,6 @@ SOURCES += \
src/widgets/helper/NotebookButton.cpp \ src/widgets/helper/NotebookButton.cpp \
src/widgets/helper/NotebookTab.cpp \ src/widgets/helper/NotebookTab.cpp \
src/widgets/helper/ResizingTextEdit.cpp \ src/widgets/helper/ResizingTextEdit.cpp \
src/widgets/helper/RippleEffectButton.cpp \
src/widgets/helper/RippleEffectLabel.cpp \
src/widgets/helper/ScrollbarHighlight.cpp \ src/widgets/helper/ScrollbarHighlight.cpp \
src/widgets/helper/SearchPopup.cpp \ src/widgets/helper/SearchPopup.cpp \
src/widgets/helper/SettingsDialogTab.cpp \ src/widgets/helper/SettingsDialogTab.cpp \
@ -239,7 +238,6 @@ SOURCES += \
src/providers/twitch/PubsubClient.cpp \ src/providers/twitch/PubsubClient.cpp \
src/providers/twitch/TwitchApi.cpp \ src/providers/twitch/TwitchApi.cpp \
src/messages/Emote.cpp \ src/messages/Emote.cpp \
src/messages/EmoteMap.cpp \
src/messages/ImageSet.cpp \ src/messages/ImageSet.cpp \
src/providers/bttv/BttvEmotes.cpp \ src/providers/bttv/BttvEmotes.cpp \
src/providers/ffz/FfzEmotes.cpp \ src/providers/ffz/FfzEmotes.cpp \
@ -257,16 +255,20 @@ SOURCES += \
src/controllers/notifications/NotificationModel.cpp \ src/controllers/notifications/NotificationModel.cpp \
src/singletons/Toasts.cpp \ src/singletons/Toasts.cpp \
src/common/DownloadManager.cpp src/common/DownloadManager.cpp
src/widgets/helper/EffectLabel.cpp \
src/widgets/helper/Button.cpp \
src/messages/MessageContainer.cpp \
src/debug/Benchmark.cpp \
src/common/UsernameSet.cpp \
src/widgets/settingspages/AdvancedPage.cpp
HEADERS += \ HEADERS += \
src/Application.hpp \ src/Application.hpp \
src/common/Channel.hpp \ src/common/Channel.hpp \
src/common/Common.hpp \ src/common/Common.hpp \
src/common/CompletionModel.hpp \ src/common/CompletionModel.hpp \
src/common/Emotemap.hpp \
src/common/FlagsEnum.hpp \ src/common/FlagsEnum.hpp \
src/common/LockedObject.hpp \ src/common/Atomic.hpp \
src/common/MutexValue.hpp \
src/common/NetworkCommon.hpp \ src/common/NetworkCommon.hpp \
src/common/NetworkData.hpp \ src/common/NetworkData.hpp \
src/common/NetworkManager.hpp \ src/common/NetworkManager.hpp \
@ -276,9 +278,8 @@ HEADERS += \
src/common/NetworkTimer.hpp \ src/common/NetworkTimer.hpp \
src/common/NetworkWorker.hpp \ src/common/NetworkWorker.hpp \
src/common/NullablePtr.hpp \ src/common/NullablePtr.hpp \
src/common/Property.hpp \
src/common/ProviderId.hpp \ src/common/ProviderId.hpp \
src/common/SerializeCustom.hpp \ src/util/RapidJsonSerializeQString.hpp \
src/common/SignalVectorModel.hpp \ src/common/SignalVectorModel.hpp \
src/common/Version.hpp \ src/common/Version.hpp \
src/controllers/accounts/Account.hpp \ src/controllers/accounts/Account.hpp \
@ -314,7 +315,6 @@ HEADERS += \
src/messages/MessageBuilder.hpp \ src/messages/MessageBuilder.hpp \
src/messages/MessageColor.hpp \ src/messages/MessageColor.hpp \
src/messages/MessageElement.hpp \ src/messages/MessageElement.hpp \
src/messages/MessageParseArgs.hpp \
src/messages/Selection.hpp \ src/messages/Selection.hpp \
src/PrecompiledHeader.hpp \ src/PrecompiledHeader.hpp \
src/providers/emoji/Emojis.hpp \ src/providers/emoji/Emojis.hpp \
@ -381,8 +381,6 @@ HEADERS += \
src/widgets/helper/NotebookButton.hpp \ src/widgets/helper/NotebookButton.hpp \
src/widgets/helper/NotebookTab.hpp \ src/widgets/helper/NotebookTab.hpp \
src/widgets/helper/ResizingTextEdit.hpp \ src/widgets/helper/ResizingTextEdit.hpp \
src/widgets/helper/RippleEffectButton.hpp \
src/widgets/helper/RippleEffectLabel.hpp \
src/widgets/helper/ScrollbarHighlight.hpp \ src/widgets/helper/ScrollbarHighlight.hpp \
src/widgets/helper/SearchPopup.hpp \ src/widgets/helper/SearchPopup.hpp \
src/widgets/helper/SettingsDialogTab.hpp \ src/widgets/helper/SettingsDialogTab.hpp \
@ -426,7 +424,6 @@ HEADERS += \
src/singletons/Updates.hpp \ src/singletons/Updates.hpp \
src/singletons/NativeMessaging.hpp \ src/singletons/NativeMessaging.hpp \
src/singletons/Theme.hpp \ src/singletons/Theme.hpp \
src/common/SimpleSignalVector.hpp \
src/common/SignalVector.hpp \ src/common/SignalVector.hpp \
src/widgets/dialogs/LogsPopup.hpp \ src/widgets/dialogs/LogsPopup.hpp \
src/common/Singleton.hpp \ src/common/Singleton.hpp \
@ -439,8 +436,6 @@ HEADERS += \
src/providers/twitch/PubsubClient.hpp \ src/providers/twitch/PubsubClient.hpp \
src/providers/twitch/TwitchApi.hpp \ src/providers/twitch/TwitchApi.hpp \
src/messages/Emote.hpp \ src/messages/Emote.hpp \
src/messages/EmoteMap.hpp \
src/messages/EmoteCache.hpp \
src/messages/ImageSet.hpp \ src/messages/ImageSet.hpp \
src/common/Outcome.hpp \ src/common/Outcome.hpp \
src/providers/bttv/BttvEmotes.hpp \ src/providers/bttv/BttvEmotes.hpp \
@ -460,6 +455,12 @@ HEADERS += \
src/controllers/notifications/NotificationModel.hpp \ src/controllers/notifications/NotificationModel.hpp \
src/singletons/Toasts.hpp \ src/singletons/Toasts.hpp \
src/common/DownloadManager.hpp src/common/DownloadManager.hpp
src/widgets/helper/EffectLabel.hpp \
src/util/LayoutHelper.hpp \
src/widgets/helper/Button.hpp \
src/messages/MessageContainer.hpp \
src/common/UsernameSet.hpp \
src/widgets/settingspages/AdvancedPage.hpp
RESOURCES += \ RESOURCES += \
resources/resources.qrc \ resources/resources.qrc \

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Cal Henderson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M50,100c27.7,0,50-22.3,50-50S77.7,0,50,0S0,22.3,0,50S22.3,100,50,100z M33.3,46.2c4.2,0,8.3-3.3,8.3-8.3
s-4.2-8.3-8.3-8.3S25,32.9,25,37.9S29.2,46.2,33.3,46.2z M50,91.7C27,91.7,8.3,73,8.3,50S27,8.3,50,8.3S91.7,27,91.7,50
S73,91.7,50,91.7z M23.3,63.1c16.2,10.3,37.1,10.4,53.2,0.1l-4.3-7c-13.7,8.5-31,8.4-44.5-0.1L23.3,63.1z M67.1,46.2
c4.2,0,8.3-3.3,8.3-8.3s-4.2-8.3-8.3-8.3s-8.3,3.3-8.3,8.3S62.9,46.2,67.1,46.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 847 B

View file

@ -7,11 +7,13 @@
#include "controllers/moderationactions/ModerationActions.hpp" #include "controllers/moderationactions/ModerationActions.hpp"
#include "controllers/notifications/NotificationController.hpp" #include "controllers/notifications/NotificationController.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp" #include "controllers/taggedusers/TaggedUsersController.hpp"
#include "debug/Log.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvEmotes.hpp" #include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp" #include "providers/ffz/FfzEmotes.hpp"
#include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Fonts.hpp" #include "singletons/Fonts.hpp"
#include "singletons/Logging.hpp" #include "singletons/Logging.hpp"
#include "singletons/NativeMessaging.hpp" #include "singletons/NativeMessaging.hpp"
@ -23,6 +25,7 @@
#include "singletons/WindowManager.hpp" #include "singletons/WindowManager.hpp"
#include "util/IsBigEndian.hpp" #include "util/IsBigEndian.hpp"
#include "util/PostToThread.hpp" #include "util/PostToThread.hpp"
#include "widgets/Window.hpp"
#include <atomic> #include <atomic>
@ -37,9 +40,7 @@ Application *Application::instance = nullptr;
// to each other // to each other
Application::Application(Settings &_settings, Paths &_paths) Application::Application(Settings &_settings, Paths &_paths)
: settings(&_settings) : resources(&this->emplace<Resources2>())
, paths(&_paths)
, resources(&this->emplace<Resources2>())
, themes(&this->emplace<Theme>()) , themes(&this->emplace<Theme>())
, fonts(&this->emplace<Fonts>()) , fonts(&this->emplace<Fonts>())
@ -103,26 +104,26 @@ 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
} }
void Application::initPubsub() void Application::initPubsub()
{ {
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) { this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
Log("WHISPER SENT LOL"); // log("WHISPER SENT LOL"); //
}); });
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) { this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
Log("WHISPER RECEIVED LOL"); // log("WHISPER RECEIVED LOL"); //
}); });
this->twitch.pubsub->signals_.moderation.chatCleared.connect( this->twitch.pubsub->signals_.moderation.chatCleared.connect(

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "singletons/Resources.hpp"
#include <QApplication> #include <QApplication>
#include <memory> #include <memory>
@ -27,9 +26,10 @@ class AccountManager;
class Emotes; class Emotes;
class Settings; class Settings;
class Fonts; class Fonts;
class Resources; class Resources2;
class Toasts; class Toasts;
class Application class Application
{ {
std::vector<std::unique_ptr<Singleton>> singletons_; std::vector<std::unique_ptr<Singleton>> singletons_;
@ -49,8 +49,6 @@ public:
friend void test(); friend void test();
Settings *const settings{};
Paths *const paths{};
Resources2 *const resources; Resources2 *const resources;
Theme *const themes{}; Theme *const themes{};

View file

@ -9,33 +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()) { if (std::cin.eof()) break;
break;
}
uint32_t 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();
@ -50,14 +48,14 @@ void runLoop(NativeMessagingClient &client)
} }
#endif #endif
std::unique_ptr<char[]> b(new char[size + 1]); std::unique_ptr<char[]> buffer(new char[size + 1]);
std::cin.read(b.get(), size); std::cin.read(buffer.get(), size);
*(b.get() + size) = '\0'; *(buffer.get() + size) = '\0';
client.sendMessage( client.sendMessage(QByteArray::fromRawData(
QByteArray::fromRawData(b.get(), static_cast<int32_t>(size))); buffer.get(), static_cast<int32_t>(size)));
}
} }
}
} // namespace } // namespace
bool shouldRunBrowserExtensionHost(const QStringList &args) bool shouldRunBrowserExtensionHost(const QStringList &args)

View file

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

View file

@ -12,7 +12,7 @@
#include "widgets/dialogs/LastRunCrashDialog.hpp" #include "widgets/dialogs/LastRunCrashDialog.hpp"
#ifdef C_USE_BREAKPAD #ifdef C_USE_BREAKPAD
#include <QBreakpadHandler.h> # include <QBreakpadHandler.h>
#endif #endif
// void initQt(); // void initQt();
@ -23,82 +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
QPalette darkPalette = qApp->palette(); auto dark = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22)); dark.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white); dark.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white); dark.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, dark.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127)); QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333")); dark.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444")); dark.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white); dark.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white); dark.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, dark.setColor(QPalette::Disabled, QPalette::Text,
QColor(127, 127, 127)); QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35)); dark.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20)); dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70)); dark.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white); dark.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, dark.setColor(QPalette::Disabled, QPalette::ButtonText,
QColor(127, 127, 127)); QColor(127, 127, 127));
darkPalette.setColor(QPalette::BrightText, Qt::red); dark.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); dark.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, dark.setColor(QPalette::Disabled, QPalette::Highlight,
QColor(80, 80, 80)); QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white); dark.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127)); QColor(127, 127, 127));
qApp->setPalette(darkPalette); 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)
@ -109,7 +109,7 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
chatterino::Updates::getInstance().checkForUpdates(); chatterino::Updates::getInstance().checkForUpdates();
#ifdef C_USE_BREAKPAD #ifdef C_USE_BREAKPAD
QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes"); QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
#endif #endif
// Running file // Running file

View file

@ -32,3 +32,5 @@ QStringAlias(UserName);
QStringAlias(UserId); QStringAlias(UserId);
QStringAlias(Url); QStringAlias(Url);
QStringAlias(Tooltip); QStringAlias(Tooltip);
QStringAlias(EmoteId);
QStringAlias(EmoteName);

View file

@ -6,14 +6,14 @@
namespace chatterino { namespace chatterino {
template <typename T> template <typename T>
class MutexValue : boost::noncopyable class Atomic : boost::noncopyable
{ {
public: public:
MutexValue() Atomic()
{ {
} }
MutexValue(T &&val) Atomic(T &&val)
: value_(val) : value_(val)
{ {
} }
@ -32,6 +32,13 @@ public:
this->value_ = val; this->value_ = val;
} }
void set(T &&val)
{
std::lock_guard<std::mutex> guard(this->mutex_);
this->value_ = std::move(val);
}
private: private:
mutable std::mutex mutex_; mutable std::mutex mutex_;
T value_; T value_;

View file

@ -18,16 +18,14 @@
namespace chatterino { namespace chatterino {
//
// Channel
//
Channel::Channel(const QString &name, Type type) Channel::Channel(const QString &name, Type type)
: completionModel(name) : completionModel(*this)
, name_(name) , name_(name)
, type_(type) , type_(type)
{ {
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
[this]() {
this->completionModel.clearExpiredStrings(); //
});
this->clearCompletionModelTimer_.start(60 * 1000);
} }
Channel::~Channel() Channel::~Channel()
@ -67,8 +65,7 @@ void Channel::addMessage(MessagePtr message)
const QString &username = message->loginName; const QString &username = message->loginName;
if (!username.isEmpty()) { if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a // TODO: Add recent chatters display name
// setting
this->addRecentChatter(message); this->addRecentChatter(message);
} }
@ -98,8 +95,6 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
for (int i = snapshotLength - 1; i >= end; --i) { for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i]; auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) { if (s->parseTime < minimumTime) {
break; break;
} }
@ -165,13 +160,14 @@ void Channel::disableAllMessages()
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot(); LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength(); int snapshotLength = snapshot.getLength();
for (int i = 0; i < snapshotLength; i++) { for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i]; auto &message = snapshot[i];
if (s->flags.hasAny({MessageFlag::System, MessageFlag::Timeout})) { if (message->flags.hasAny(
{MessageFlag::System, MessageFlag::Timeout})) {
continue; continue;
} }
// FOURTF: disabled for now // FOURTF: disabled for now
// s->flags.EnableFlag(MessageFlag::Disabled); const_cast<Message *>(message.get())->flags.set(MessageFlag::Disabled);
} }
} }
@ -196,7 +192,6 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement)
void Channel::addRecentChatter(const MessagePtr &message) void Channel::addRecentChatter(const MessagePtr &message)
{ {
// Do nothing by default
} }
bool Channel::canSendMessage() const bool Channel::canSendMessage() const
@ -234,9 +229,45 @@ void Channel::onConnected()
{ {
} }
std::weak_ptr<Channel> Channel::weak_from_this() //
// Indirect channel
//
IndirectChannel::Data::Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{ {
return std::weak_ptr<Channel>(this->shared_from_this()); }
IndirectChannel::IndirectChannel(ChannelPtr channel, Channel::Type type)
: data_(std::make_unique<Data>(channel, type))
{
}
ChannelPtr IndirectChannel::get()
{
return data_->channel;
}
void IndirectChannel::reset(ChannelPtr channel)
{
assert(this->data_->type != Channel::Type::Direct);
this->data_->channel = channel;
this->data_->changed.invoke();
}
pajlada::Signals::NoArgSignal &IndirectChannel::getChannelChanged()
{
return this->data_->changed;
}
Channel::Type IndirectChannel::getType()
{
if (this->data_->type == Channel::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,10 +1,7 @@
#pragma once #pragma once
#include "common/CompletionModel.hpp" #include "common/CompletionModel.hpp"
#include "messages/Image.hpp"
#include "messages/LimitedQueue.hpp" #include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "util/ConcurrentMap.hpp"
#include <QString> #include <QString>
#include <QTimer> #include <QTimer>
@ -13,7 +10,9 @@
#include <memory> #include <memory>
namespace chatterino { namespace chatterino {
struct Message; struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class Channel : public std::enable_shared_from_this<Channel> class Channel : public std::enable_shared_from_this<Channel>
{ {
@ -52,7 +51,6 @@ public:
void addOrReplaceTimeout(MessagePtr message); void addOrReplaceTimeout(MessagePtr message);
void disableAllMessages(); void disableAllMessages();
void replaceMessage(MessagePtr message, MessagePtr replacement); void replaceMessage(MessagePtr message, MessagePtr replacement);
virtual void addRecentChatter(const MessagePtr &message);
QStringList modList; QStringList modList;
@ -66,11 +64,9 @@ public:
CompletionModel completionModel; CompletionModel completionModel;
// pre c++17 polyfill
std::weak_ptr<Channel> weak_from_this();
protected: protected:
virtual void onConnected(); virtual void onConnected();
virtual void addRecentChatter(const MessagePtr &message);
private: private:
const QString name_; const QString name_;
@ -88,47 +84,17 @@ class IndirectChannel
Channel::Type type; Channel::Type type;
pajlada::Signals::NoArgSignal changed; pajlada::Signals::NoArgSignal changed;
Data() = delete; Data(ChannelPtr channel, Channel::Type type);
Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
}
}; };
public: public:
IndirectChannel(ChannelPtr channel, IndirectChannel(ChannelPtr channel,
Channel::Type type = Channel::Type::Direct) Channel::Type type = Channel::Type::Direct);
: data_(new Data(channel, type))
{
}
ChannelPtr get() ChannelPtr get();
{ void reset(ChannelPtr channel);
return data_->channel; pajlada::Signals::NoArgSignal &getChannelChanged();
} Channel::Type getType();
void update(ChannelPtr ptr)
{
assert(this->data_->type != Channel::Type::Direct);
this->data_->channel = ptr;
this->data_->changed.invoke();
}
pajlada::Signals::NoArgSignal &getChannelChanged()
{
return this->data_->changed;
}
Channel::Type getType()
{
if (this->data_->type == Channel::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
}
private: private:
std::shared_ptr<Data> data_; std::shared_ptr<Data> data_;

View file

@ -3,7 +3,6 @@
#include "common/Aliases.hpp" #include "common/Aliases.hpp"
#include "common/Outcome.hpp" #include "common/Outcome.hpp"
#include "common/ProviderId.hpp" #include "common/ProviderId.hpp"
#include "debug/Log.hpp"
#include <QString> #include <QString>
#include <QWidget> #include <QWidget>
@ -40,4 +39,12 @@ std::weak_ptr<T> weakOf(T *element)
return element->shared_from_this(); return element->shared_from_this();
} }
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
enum class CopyMode {
Everything,
OnlyTextAndEmotes,
};
} // namespace chatterino } // namespace chatterino

View file

@ -2,45 +2,30 @@
#include "Application.hpp" #include "Application.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "common/UsernameSet.hpp"
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp" #include "controllers/commands/CommandController.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include <QtAlgorithms> #include <QtAlgorithms>
#include <utility> #include <utility>
namespace chatterino { namespace chatterino {
// -- TaggedString //
// TaggedString
//
CompletionModel::TaggedString::TaggedString(const QString &_str, Type _type) CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
: str(_str) : string(_string)
, type(_type) , type(_type)
, timeAdded(std::chrono::steady_clock::now())
{ {
} }
bool CompletionModel::TaggedString::isExpired(
const std::chrono::steady_clock::time_point &now) const
{
switch (this->type) {
case Type::Username: {
static std::chrono::minutes expirationTimer(10);
return (this->timeAdded + expirationTimer < now);
} break;
default: {
return false;
} break;
}
return false;
}
bool CompletionModel::TaggedString::isEmote() const bool CompletionModel::TaggedString::isEmote() const
{ {
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd; return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
@ -48,35 +33,23 @@ bool CompletionModel::TaggedString::isEmote() const
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
{ {
if (this->isEmote()) { if (this->isEmote() != that.isEmote()) {
if (that.isEmote()) { return this->isEmote();
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return this->str > that.str;
}
return k < 0;
}
return true;
} }
if (that.isEmote()) { // try comparing insensitively, if they are the same then senstively
return false; // (fixes order of LuL and LUL)
} int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
if (k == 0) return this->string > that.string;
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return false;
}
return k < 0; return k < 0;
} }
// -- CompletionModel //
// CompletionModel
CompletionModel::CompletionModel(const QString &channelName) //
: channelName_(channelName) CompletionModel::CompletionModel(Channel &channel)
: channel_(channel)
{ {
} }
@ -87,160 +60,90 @@ int CompletionModel::columnCount(const QModelIndex &) const
QVariant CompletionModel::data(const QModelIndex &index, int) const QVariant CompletionModel::data(const QModelIndex &index, int) const
{ {
std::lock_guard<std::mutex> lock(this->emotesMutex_); std::lock_guard<std::mutex> lock(this->itemsMutex_);
// TODO: Implement more safely auto it = this->items_.begin();
auto it = this->emotes_.begin();
std::advance(it, index.row()); std::advance(it, index.row());
return QVariant(it->str); return QVariant(it->string);
} }
int CompletionModel::rowCount(const QModelIndex &) const int CompletionModel::rowCount(const QModelIndex &) const
{ {
std::lock_guard<std::mutex> lock(this->emotesMutex_); std::lock_guard<std::mutex> lock(this->itemsMutex_);
return this->emotes_.size(); return this->items_.size();
} }
void CompletionModel::refresh() void CompletionModel::refresh(const QString &prefix)
{ {
Log("[CompletionModel:{}] Refreshing...]", this->channelName_); std::lock_guard<std::mutex> guard(this->itemsMutex_);
this->items_.clear();
auto app = getApp(); if (prefix.length() < 2) return;
// User-specific: Twitch Emotes auto addString = [&](const QString &str, TaggedString::Type type) {
if (auto account = app->accounts->twitch.getCurrent()) { if (str.startsWith(prefix, Qt::CaseInsensitive))
for (const auto &emote : account->accessEmotes()->allEmoteNames) { this->items_.emplace(str + " ", type);
// XXX: No way to discern between a twitch global emote and sub
// emote right now
this->addString(emote.string,
TaggedString::Type::TwitchGlobalEmote);
}
}
// // Global: BTTV Global Emotes
// std::vector<QString> &bttvGlobalEmoteCodes =
// app->emotes->bttv.globalEmoteNames_; for (const auto &m :
// bttvGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVGlobalEmote);
// }
// // Global: FFZ Global Emotes
// std::vector<QString> &ffzGlobalEmoteCodes =
// app->emotes->ffz.globalEmoteCodes; for (const auto &m :
// ffzGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::FFZGlobalEmote);
// }
// Channel emotes
if (auto channel = dynamic_cast<TwitchChannel *>(
getApp()
->twitch2->getChannelOrEmptyByID(this->channelName_)
.get())) {
auto bttv = channel->accessBttvEmotes();
// auto it = bttv->begin();
// for (const auto &emote : *bttv) {
// }
// std::vector<QString> &bttvChannelEmoteCodes =
// app->emotes->bttv.channelEmoteName_[this->channelName_];
// for (const auto &m : bttvChannelEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVChannelEmote);
// }
// Channel-specific: FFZ Channel Emotes
for (const auto &emote : *channel->accessFfzEmotes()) {
this->addString(emote.second->name.string,
TaggedString::Type::FFZChannelEmote);
}
}
// Emojis
const auto &emojiShortCodes = app->emotes->emojis.shortCodes;
for (const auto &m : emojiShortCodes) {
this->addString(":" + m + ":", TaggedString::Type::Emoji);
}
// Commands
for (auto &command : app->commands->items.getVector()) {
this->addString(command.name, TaggedString::Command);
}
for (auto &command : app->commands->getDefaultTwitchCommandList()) {
this->addString(command, TaggedString::Command);
}
// Channel-specific: Usernames
// fourtf: only works with twitch chat
// auto c =
// ChannelManager::getInstance().getTwitchChannel(this->channelName);
// auto usernames = c->getUsernamesForCompletions();
// for (const auto &name : usernames) {
// assert(!name.displayName.isEmpty());
// this->addString(name.displayName);
// this->addString('@' + name.displayName);
// if (!name.localizedName.isEmpty()) {
// this->addString(name.localizedName);
// this->addString('@' + name.localizedName);
// }
// }
}
void CompletionModel::addString(const QString &str, TaggedString::Type type)
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
// Always add a space at the end of completions
this->emotes_.insert({str + " ", type});
}
void CompletionModel::addUser(const QString &username)
{
auto add = [this](const QString &str) {
auto ts = this->createUser(str + " ");
// Always add a space at the end of completions
std::pair<std::set<TaggedString>::iterator, bool> p =
this->emotes_.insert(ts);
if (!p.second) {
// No inseration was made, figure out if we need to replace the
// username.
if (p.first->str > ts.str) {
// Replace lowercase version of name with mixed-case version
this->emotes_.erase(p.first);
auto result2 = this->emotes_.insert(ts);
assert(result2.second);
} else {
p.first->timeAdded = std::chrono::steady_clock::now();
}
}
}; };
add(username); if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_)) {
add("@" + username); // account emotes
} if (auto account = getApp()->accounts->twitch.getCurrent()) {
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
// XXX: No way to discern between a twitch global emote and sub
// emote right now
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
}
}
void CompletionModel::clearExpiredStrings() // Usernames
{ if (prefix.length() >= UsernameSet::PrefixLength) {
std::lock_guard<std::mutex> lock(this->emotesMutex_); auto usernames = channel->accessChatters();
auto now = std::chrono::steady_clock::now(); for (const auto &name : usernames->subrange(Prefix(prefix))) {
addString(name, TaggedString::Type::Username);
addString("@" + name, TaggedString::Type::Username);
}
}
for (auto it = this->emotes_.begin(); it != this->emotes_.end();) { // Bttv Global
const auto &taggedString = *it; for (auto &emote : *channel->globalBttv().emotes()) {
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
}
if (taggedString.isExpired(now)) { // Ffz Global
// Log("String {} expired", taggedString.str); for (auto &emote : *channel->globalFfz().emotes()) {
it = this->emotes_.erase(it); addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
} else { }
++it;
// Bttv Channel
for (auto &emote : *channel->bttvEmotes()) {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
}
// Ffz Channel
for (auto &emote : *channel->ffzEmotes()) {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
}
// Emojis
if (prefix.startsWith(":")) {
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
for (auto &m : emojiShortCodes) {
addString(":" + m + ":", TaggedString::Type::Emoji);
}
}
// Commands
for (auto &command : getApp()->commands->items.getVector()) {
addString(command.name, TaggedString::Command);
}
for (auto &command :
getApp()->commands->getDefaultTwitchCommandList()) {
addString(command, TaggedString::Command);
} }
} }
} }
CompletionModel::TaggedString CompletionModel::createUser(const QString &str)
{
return TaggedString{str, TaggedString::Type::Username};
}
} // namespace chatterino } // namespace chatterino

View file

@ -1,7 +1,5 @@
#pragma once #pragma once
#include "common/Common.hpp"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <chrono> #include <chrono>
@ -10,7 +8,7 @@
namespace chatterino { namespace chatterino {
class TwitchChannel; class Channel;
class CompletionModel : public QAbstractListModel class CompletionModel : public QAbstractListModel
{ {
@ -33,39 +31,30 @@ class CompletionModel : public QAbstractListModel
Command, Command,
}; };
TaggedString(const QString &_str, Type _type); TaggedString(const QString &string, Type type);
bool isExpired(const std::chrono::steady_clock::time_point &now) const;
bool isEmote() const; bool isEmote() const;
bool operator<(const TaggedString &that) const; bool operator<(const TaggedString &that) const;
QString str; QString string;
// Type will help decide the lifetime of the tagged strings
Type type; Type type;
mutable std::chrono::steady_clock::time_point timeAdded;
}; };
public: public:
CompletionModel(const QString &channelName); CompletionModel(Channel &channel);
virtual int columnCount(const QModelIndex &) const override; virtual int columnCount(const QModelIndex &) const override;
virtual QVariant data(const QModelIndex &index, int) const override; virtual QVariant data(const QModelIndex &index, int) const override;
virtual int rowCount(const QModelIndex &) const override; virtual int rowCount(const QModelIndex &) const override;
void refresh(); void refresh(const QString &prefix);
void addString(const QString &str, TaggedString::Type type);
void addUser(const QString &str);
void clearExpiredStrings();
private: private:
TaggedString createUser(const QString &str); TaggedString createUser(const QString &str);
mutable std::mutex emotesMutex_; std::set<TaggedString> items_;
std::set<TaggedString> emotes_; mutable std::mutex itemsMutex_;
Channel &channel_;
QString channelName_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,46 +0,0 @@
#include "Emotemap.hpp"
#include "Application.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
// EmoteData::EmoteData(Image *image)
// : image1x(image)
//{
//}
//// Emotes must have a 1x image to be valid
// bool EmoteData::isValid() const
//{
// return this->image1x != nullptr;
//}
// Image *EmoteData::getImage(float scale) const
//{
// int quality = getApp()->settings->preferredEmoteQuality;
// if (quality == 0) {
// scale *= getApp()->settings->emoteScale.getValue();
// quality = [&] {
// if (scale <= 1)
// return 1;
// if (scale <= 2)
// return 2;
// return 3;
// }();
// }
// Image *_image;
// if (quality == 3 && this->image3x != nullptr) {
// _image = this->image3x;
// } else if (quality >= 2 && this->image2x != nullptr) {
// _image = this->image2x;
// } else {
// _image = this->image1x;
// }
// return _image;
//}
} // namespace chatterino

View file

@ -1,27 +0,0 @@
#pragma once
#include "messages/Image.hpp"
#include "util/ConcurrentMap.hpp"
namespace chatterino {
// struct EmoteData {
// EmoteData() = default;
// EmoteData(Image *image);
// // Emotes must have a 1x image to be valid
// bool isValid() const;
// Image *getImage(float scale) const;
// // Link to the emote page i.e.
// https://www.frankerfacez.com/emoticon/144722-pajaCringe QString pageLink;
// Image *image1x = nullptr;
// Image *image2x = nullptr;
// Image *image3x = nullptr;
//};
// using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino

View file

@ -4,19 +4,17 @@
namespace chatterino { namespace chatterino {
// = std::enable_if<std::is_enum<T>::value>::type
template <typename T, typename Q = typename std::underlying_type<T>::type> template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum class FlagsEnum
{ {
public: public:
FlagsEnum() FlagsEnum()
: value(static_cast<T>(0)) : value_(static_cast<T>(0))
{ {
} }
FlagsEnum(T value) FlagsEnum(T value)
: value(value) : value_(value)
{ {
} }
@ -29,22 +27,22 @@ public:
bool operator==(const FlagsEnum<T> &other) bool operator==(const FlagsEnum<T> &other)
{ {
return this->value == other.value; return this->value_ == other.value_;
} }
bool operator!=(const FlagsEnum &other) bool operator!=(const FlagsEnum &other)
{ {
return this->value != other.value; return this->value_ != other.value_;
} }
void set(T flag) void set(T flag)
{ {
reinterpret_cast<Q &>(this->value) |= static_cast<Q>(flag); reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
} }
void unset(T flag) void unset(T flag)
{ {
reinterpret_cast<Q &>(this->value) &= ~static_cast<Q>(flag); reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
} }
void set(T flag, bool value) void set(T flag, bool value)
@ -57,33 +55,17 @@ public:
bool has(T flag) const bool has(T flag) const
{ {
return static_cast<Q>(this->value) & static_cast<Q>(flag); return static_cast<Q>(this->value_) & static_cast<Q>(flag);
} }
// bool hasAny(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (this->has(flag)) return true;
// }
// return false;
//}
bool hasAny(FlagsEnum flags) const bool hasAny(FlagsEnum flags) const
{ {
return static_cast<Q>(this->value) & static_cast<Q>(flags.value); return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
} }
// bool hasAll(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (!this->has(flag)) return false;
// }
// return true;
//}
bool hasAll(FlagsEnum<T> flags) const bool hasAll(FlagsEnum<T> flags) const
{ {
return (static_cast<Q>(this->value) & static_cast<Q>(flags.value)) && return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value); static_cast<Q>(flags->value);
} }
@ -93,7 +75,7 @@ public:
} }
private: private:
T value; T value_{};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -5,69 +5,63 @@
#include <QString> #include <QString>
#include <QTextStream> #include <QTextStream>
// ip 0.0.0.0 - 224.0.0.0
#define IP \
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" \
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" \
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
#define PORT "(?::\\d{2,5})"
#define WEB_CHAR1 "[_a-z\\x{00a1}-\\x{ffff}0-9]"
#define WEB_CHAR2 "[a-z\\x{00a1}-\\x{ffff}0-9]"
#define SPOTIFY_1 "(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+"
#define SPOTIFY_2 "user:[^:]+"
#define SPOTIFY_3 "search:(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+"
#define SPOTIFY_PARAMS "(?:" SPOTIFY_1 "|" SPOTIFY_2 "|" SPOTIFY_3 ")"
#define SPOTIFY_LINK "(?x-mi:(spotify:" SPOTIFY_PARAMS "))"
#define WEB_PROTOCOL "(?:(?:https?|ftps?)://)?"
#define WEB_USER "(?:\\S+(?::\\S*)?@)?"
#define WEB_HOST "(?:(?:" WEB_CHAR1 "-*)*" WEB_CHAR2 "+)"
#define WEB_DOMAIN "(?:\\.(?:" WEB_CHAR2 "-*)*" WEB_CHAR2 "+)*"
#define WEB_TLD "(?:" + tldData + ")"
#define WEB_RESOURCE_PATH "(?:[/?#]\\S*)"
#define WEB_LINK \
WEB_PROTOCOL WEB_USER "(?:" IP "|" WEB_HOST WEB_DOMAIN "\\." WEB_TLD PORT \
"?" WEB_RESOURCE_PATH "?)"
#define LINK "^(?:" SPOTIFY_LINK "|" WEB_LINK ")$"
namespace chatterino { namespace chatterino {
LinkParser::LinkParser(const QString &unparsedString) LinkParser::LinkParser(const QString &unparsedString)
{ {
static QRegularExpression linkRegex = [] { static QRegularExpression linkRegex = [] {
static QRegularExpression newLineRegex("\r?\n"); static QRegularExpression newLineRegex("\r?\n");
QFile tldFile(":/tlds.txt"); QFile file(":/tlds.txt");
tldFile.open(QFile::ReadOnly); file.open(QFile::ReadOnly);
QTextStream tlds(&file);
tlds.setCodec("UTF-8");
QTextStream t1(&tldFile); // tldData gets injected into the LINK macro
t1.setCodec("UTF-8"); auto tldData = tlds.readAll().replace(newLineRegex, "|");
(void)tldData;
// Read the TLDs in and replace the newlines with pipes return QRegularExpression(LINK,
QString tldData = t1.readAll().replace(newLineRegex, "|");
const QString hyperlinkRegExp =
"^"
// Identifier for spotify
"(?x-mi:(spotify:(?:"
"(?:artist|album|track|user:[^:]+:playlist):"
"[a-zA-Z0-9]+|user:[^:]+|search:"
"(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+)))"
// If nothing matches then just go on
"|"
"^"
// Identifier for http and ftp
"(?:(?:https?|ftps?)://)?"
// user:pass authentication
"(?:\\S+(?::\\S*)?@)?"
"(?:"
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broacast addresses
// (first & last IP address of each class)
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])"
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}"
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
"|"
// host name
"(?:(?:[_a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+"
")"
// domain name
"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-"
"9]+)*"
// TLD identifier
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
"(?:[\\.](?:" +
tldData +
"))"
"\\.?"
")"
// port number
"(?::\\d{2,5})?"
// resource path
"(?:[/?#]\\S*)?"
"$";
return QRegularExpression(hyperlinkRegExp,
QRegularExpression::CaseInsensitiveOption); QRegularExpression::CaseInsensitiveOption);
}(); }();
this->match_ = linkRegex.match(unparsedString); this->match_ = linkRegex.match(unparsedString);
} }
bool LinkParser::hasMatch() const
{
return this->match_.hasMatch();
}
QString LinkParser::getCaptured() const
{
return this->match_.captured();
}
} // namespace chatterino } // namespace chatterino

View file

@ -10,15 +10,8 @@ class LinkParser
public: public:
explicit LinkParser(const QString &unparsedString); explicit LinkParser(const QString &unparsedString);
bool hasMatch() const bool hasMatch() const;
{ QString getCaptured() const;
return this->match_.hasMatch();
}
QString getCaptured() const
{
return this->match_.captured();
}
private: private:
QRegularExpressionMatch match_; QRegularExpressionMatch match_;

View file

@ -1,38 +0,0 @@
#pragma once
#include <mutex>
namespace chatterino {
template <typename Type>
class LockedObject
{
public:
LockedObject &operator=(const LockedObject<Type> &other)
{
this->mutex_.lock();
this->data = other.getValue();
this->mutex_.unlock();
return *this;
}
LockedObject &operator=(const Type &other)
{
this->mutex_.lock();
this->data = other;
this->mutex_.unlock();
return *this;
}
private:
Type value_;
std::mutex mutex_;
};
} // namespace chatterino

View file

@ -2,12 +2,11 @@
#include <functional> #include <functional>
#include "Common.hpp"
class QNetworkReply; class QNetworkReply;
namespace chatterino { namespace chatterino {
class Outcome;
class NetworkResult; class NetworkResult;
using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>; using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>;

View file

@ -1,6 +1,5 @@
#include "common/NetworkData.hpp" #include "common/NetworkData.hpp"
#include "Application.hpp"
#include "singletons/Paths.hpp" #include "singletons/Paths.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
@ -42,9 +41,7 @@ QString NetworkData::getHash()
void NetworkData::writeToCache(const QByteArray &bytes) void NetworkData::writeToCache(const QByteArray &bytes)
{ {
if (this->useQuickLoadCache_) { if (this->useQuickLoadCache_) {
auto app = getApp(); QFile cachedFile(getPaths()->cacheDirectory() + "/" + this->getHash());
QFile cachedFile(app->paths->cacheDirectory + "/" + this->getHash());
if (cachedFile.open(QIODevice::WriteOnly)) { if (cachedFile.open(QIODevice::WriteOnly)) {
cachedFile.write(bytes); cachedFile.write(bytes);

View file

@ -19,6 +19,7 @@ struct NetworkData {
QNetworkRequest request_; QNetworkRequest request_;
const QObject *caller_ = nullptr; const QObject *caller_ = nullptr;
bool useQuickLoadCache_{}; bool useQuickLoadCache_{};
bool executeConcurrently{};
NetworkReplyCreatedCallback onReplyCreated_; NetworkReplyCreatedCallback onReplyCreated_;
NetworkErrorCallback onError_; NetworkErrorCallback onError_;

View file

@ -5,11 +5,11 @@
namespace chatterino { namespace chatterino {
QThread NetworkManager::workerThread; QThread NetworkManager::workerThread;
QNetworkAccessManager NetworkManager::NaM; QNetworkAccessManager NetworkManager::accessManager;
void NetworkManager::init() void NetworkManager::init()
{ {
NetworkManager::NaM.moveToThread(&NetworkManager::workerThread); NetworkManager::accessManager.moveToThread(&NetworkManager::workerThread);
NetworkManager::workerThread.start(); NetworkManager::workerThread.start();
} }

View file

@ -1,16 +1,7 @@
#pragma once #pragma once
#include "common/NetworkRequester.hpp"
#include "common/NetworkWorker.hpp"
#include "debug/Log.hpp"
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QThread> #include <QThread>
#include <QTimer>
#include <QUrl>
namespace chatterino { namespace chatterino {
@ -20,7 +11,7 @@ class NetworkManager : public QObject
public: public:
static QThread workerThread; static QThread workerThread;
static QNetworkAccessManager NaM; static QNetworkAccessManager accessManager;
static void init(); static void init();
static void deinit(); static void deinit();

View file

@ -1,13 +1,16 @@
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "Application.hpp" #include "common/NetworkData.hpp"
#include "common/NetworkManager.hpp" #include "common/NetworkManager.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchCommon.hpp"
#include "singletons/Paths.hpp" #include "singletons/Paths.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
#include <QFile> #include <QFile>
#include <QtConcurrent>
#include <cassert> #include <cassert>
namespace chatterino { namespace chatterino {
@ -80,6 +83,11 @@ void NetworkRequest::setTimeout(int ms)
this->timer->timeoutMS_ = ms; this->timer->timeoutMS_ = ms;
} }
void NetworkRequest::setExecuteConcurrently(bool value)
{
this->data->executeConcurrently = value;
}
void NetworkRequest::makeAuthorizedV5(const QString &clientID, void NetworkRequest::makeAuthorizedV5(const QString &clientID,
const QString &oauthToken) const QString &oauthToken)
{ {
@ -130,16 +138,15 @@ void NetworkRequest::execute()
} break; } break;
default: { default: {
Log("[Execute] Unhandled request type"); log("[Execute] Unhandled request type");
} break; } break;
} }
} }
Outcome NetworkRequest::tryLoadCachedFile() Outcome NetworkRequest::tryLoadCachedFile()
{ {
auto app = getApp(); QFile cachedFile(getPaths()->cacheDirectory() + "/" +
this->data->getHash());
QFile cachedFile(app->paths->cacheDirectory + "/" + this->data->getHash());
if (!cachedFile.exists()) { if (!cachedFile.exists()) {
// File didn't exist // File didn't exist
@ -180,14 +187,15 @@ void NetworkRequest::doRequest()
auto reply = [&]() -> QNetworkReply * { auto reply = [&]() -> QNetworkReply * {
switch (data->requestType_) { switch (data->requestType_) {
case NetworkRequestType::Get: case NetworkRequestType::Get:
return NetworkManager::NaM.get(data->request_); return NetworkManager::accessManager.get(data->request_);
case NetworkRequestType::Put: case NetworkRequestType::Put:
return NetworkManager::NaM.put(data->request_, return NetworkManager::accessManager.put(data->request_,
data->payload_); data->payload_);
case NetworkRequestType::Delete: case NetworkRequestType::Delete:
return NetworkManager::NaM.deleteResource(data->request_); return NetworkManager::accessManager.deleteResource(
data->request_);
default: default:
return nullptr; return nullptr;
@ -195,13 +203,13 @@ void NetworkRequest::doRequest()
}(); }();
if (reply == nullptr) { if (reply == nullptr) {
Log("Unhandled request type"); log("Unhandled request type");
return; return;
} }
if (timer->isStarted()) { if (timer->isStarted()) {
timer->onTimeout(worker, [reply, data]() { timer->onTimeout(worker, [reply, data]() {
Log("Aborted!"); log("Aborted!");
reply->abort(); reply->abort();
if (data->onError_) { if (data->onError_) {
data->onError_(-2); data->onError_(-2);
@ -228,7 +236,16 @@ void NetworkRequest::doRequest()
NetworkResult result(bytes); NetworkResult result(bytes);
DebugCount::increase("http request success"); DebugCount::increase("http request success");
data->onSuccess_(result); // log("starting {}", data->request_.url().toString());
if (data->onSuccess_) {
if (data->executeConcurrently)
QtConcurrent::run(
[onSuccess = std::move(data->onSuccess_),
result = std::move(result)] { onSuccess(result); });
else
data->onSuccess_(result);
}
// log("finished {}", data->request_.url().toString());
reply->deleteLater(); reply->deleteLater();
}; };

View file

@ -1,14 +1,13 @@
#pragma once #pragma once
#include "Application.hpp"
#include "common/NetworkCommon.hpp" #include "common/NetworkCommon.hpp"
#include "common/NetworkData.hpp"
#include "common/NetworkRequester.hpp" #include "common/NetworkRequester.hpp"
#include "common/NetworkResult.hpp" #include "common/NetworkResult.hpp"
#include "common/NetworkTimer.hpp" #include "common/NetworkTimer.hpp"
#include "common/NetworkWorker.hpp" #include "common/NetworkWorker.hpp"
namespace chatterino { namespace chatterino {
class NetworkData;
class NetworkRequest class NetworkRequest
{ {
@ -27,19 +26,15 @@ class NetworkRequest
bool executed_ = false; bool executed_ = false;
public: public:
NetworkRequest() = delete;
NetworkRequest(const NetworkRequest &other) = delete;
NetworkRequest &operator=(const NetworkRequest &other) = delete;
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
explicit NetworkRequest( explicit NetworkRequest(
const std::string &url, const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get); NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest( explicit NetworkRequest(
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get); QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
~NetworkRequest(); ~NetworkRequest();
void setRequestType(NetworkRequestType newRequestType); void setRequestType(NetworkRequestType newRequestType);
@ -55,14 +50,13 @@ public:
void setRawHeader(const char *headerName, const QByteArray &value); void setRawHeader(const char *headerName, const QByteArray &value);
void setRawHeader(const char *headerName, const QString &value); void setRawHeader(const char *headerName, const QString &value);
void setTimeout(int ms); void setTimeout(int ms);
void setExecuteConcurrently(bool value);
void makeAuthorizedV5(const QString &clientID, void makeAuthorizedV5(const QString &clientID,
const QString &oauthToken = QString()); const QString &oauthToken = QString());
void execute(); void execute();
private: private:
// Returns true if the file was successfully loaded from cache
// Returns false if the cache file either didn't exist, or it contained
// "invalid" data "invalid" is specified by the onSuccess callback // "invalid" data "invalid" is specified by the onSuccess callback
Outcome tryLoadCachedFile(); Outcome tryLoadCachedFile();

View file

@ -31,7 +31,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
ret.Parse(this->data_.data(), this->data_.length()); ret.Parse(this->data_.data(), this->data_.length());
if (result.Code() != rapidjson::kParseErrorNone) { if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})", log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset()); rapidjson::GetParseError_En(result.Code()), result.Offset());
return ret; return ret;
} }

View file

@ -1,34 +0,0 @@
#pragma once
#include "boost/noncopyable.hpp"
namespace chatterino {
template <typename T>
class Property final : boost::noncopyable
{
public:
Property()
{
}
Property(const T &value)
: value_(value)
{
}
T &operator=(const T &f)
{
return value_ = f;
}
operator T const &() const
{
return value_;
}
protected:
T value_;
};
} // namespace chatterino

View file

@ -1,43 +0,0 @@
#pragma once
#include <QString>
#include <pajlada/settings/serialize.hpp>
namespace pajlada {
namespace Settings {
template <>
struct Serialize<QString> {
static rapidjson::Value get(const QString &value,
rapidjson::Document::AllocatorType &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;
}
return QString{};
}
};
} // namespace Settings
} // namespace pajlada

View file

@ -1,34 +0,0 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <mutex>
#include <vector>
namespace chatterino {
template <typename TValue>
class SimpleSignalVector
{
public:
SimpleSignalVector &operator=(std::vector<TValue> &other)
{
this->data_ = other;
this->updated.invoke();
return *this;
}
operator std::vector<TValue> &()
{
return this->data_;
}
pajlada::Signals::NoArgSignal updated;
private:
std::vector<TValue> data_;
};
} // namespace chatterino

View file

@ -46,16 +46,15 @@ public:
} }
private: private:
T *element_; T *element_{};
std::mutex *mutex_; std::mutex *mutex_{};
bool isValid_ = true; bool isValid_{true};
}; };
template <typename T> template <typename T>
class UniqueAccess class UniqueAccess
{ {
public: public:
// template <typename X = decltype(T())>
UniqueAccess() UniqueAccess()
: element_(T()) : element_(T())
{ {
@ -88,7 +87,8 @@ public:
return AccessGuard<T>(this->element_, this->mutex_); return AccessGuard<T>(this->element_, this->mutex_);
} }
template <typename X = T, typename = std::enable_if_t<!std::is_const_v<X>>> template <typename X = T,
typename = std::enable_if_t<!std::is_const<X>::value>>
AccessGuard<const X> accessConst() const AccessGuard<const X> accessConst() const
{ {
return AccessGuard<const T>(this->element_, this->mutex_); return AccessGuard<const T>(this->element_, this->mutex_);

112
src/common/UsernameSet.cpp Normal file
View file

@ -0,0 +1,112 @@
#include "UsernameSet.hpp"
#include <tuple>
namespace chatterino {
//
// UsernameSet
//
UsernameSet::ConstIterator UsernameSet::begin() const
{
return this->items.begin();
}
UsernameSet::ConstIterator UsernameSet::end() const
{
return this->items.end();
}
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
{
auto it = this->firstKeyForPrefix.find(prefix);
if (it != this->firstKeyForPrefix.end()) {
auto start = this->items.find(it->second);
auto end = start;
while (end != this->items.end() && prefix.isStartOf(*end)) {
end++;
}
return {start, end};
}
return {this->items.end(), this->items.end()};
}
std::set<QString>::size_type UsernameSet::size() const
{
return this->items.size();
}
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
{
this->insertPrefix(value);
return this->items.insert(value);
}
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
{
this->insertPrefix(value);
return this->items.insert(std::move(value));
}
void UsernameSet::insertPrefix(const QString &value)
{
auto &string = this->firstKeyForPrefix[Prefix(value)];
if (string.isNull() || value < string) string = value;
}
//
// Range
//
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
: start_(start)
, end_(end)
{
}
UsernameSet::ConstIterator UsernameSet::Range::begin()
{
return this->start_;
}
UsernameSet::ConstIterator UsernameSet::Range::end()
{
return this->end_;
}
//
// Prefix
//
Prefix::Prefix(const QString &string)
: first(string.size() >= 1 ? string[0].toLower() : '\0')
, second(string.size() >= 2 ? string[1].toLower() : '\0')
{
}
bool Prefix::operator==(const Prefix &other) const
{
return std::tie(this->first, this->second) ==
std::tie(other.first, other.second);
}
bool Prefix::isStartOf(const QString &string) const
{
if (string.size() == 0) {
return this->first == QChar('\0') && this->second == QChar('\0');
} else if (string.size() == 1) {
return this->first == string[0].toLower() &&
this->second == QChar('\0');
} else {
return this->first == string[0].toLower() &&
this->second == string[1].toLower();
}
}
} // namespace chatterino

View file

@ -0,0 +1,73 @@
#pragma once
#include <QString>
#include <functional>
#include <set>
#include <unordered_map>
namespace chatterino {
class Prefix
{
public:
Prefix(const QString &string);
bool operator==(const Prefix &other) const;
bool isStartOf(const QString &string) const;
private:
QChar first;
QChar second;
friend struct std::hash<Prefix>;
};
} // namespace chatterino
namespace std {
template <>
struct hash<chatterino::Prefix> {
size_t operator()(const chatterino::Prefix &prefix) const
{
return (size_t(prefix.first.unicode()) << 16) |
size_t(prefix.second.unicode());
}
};
} // namespace std
namespace chatterino {
class UsernameSet
{
public:
static constexpr int PrefixLength = 2;
using Iterator = std::set<QString>::iterator;
using ConstIterator = std::set<QString>::const_iterator;
class Range
{
public:
Range(ConstIterator start, ConstIterator end);
ConstIterator begin();
ConstIterator end();
private:
ConstIterator start_;
ConstIterator end_;
};
ConstIterator begin() const;
ConstIterator end() const;
Range subrange(const Prefix &prefix) const;
std::set<QString>::size_type size() const;
std::pair<Iterator, bool> insert(const QString &value);
std::pair<Iterator, bool> insert(QString &&value);
private:
void insertPrefix(const QString &string);
std::set<QString> items;
std::unordered_map<Prefix, QString> firstKeyForPrefix;
};
} // namespace chatterino

View file

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

View file

@ -1,6 +1,8 @@
#include "AccountController.hpp" #include "AccountController.hpp"
#include "controllers/accounts/Account.hpp"
#include "controllers/accounts/AccountModel.hpp" #include "controllers/accounts/AccountModel.hpp"
#include "providers/twitch/TwitchAccount.hpp"
namespace chatterino { namespace chatterino {

View file

@ -1,16 +1,15 @@
#pragma once #pragma once
#include "common/Singleton.hpp"
#include <QObject> #include <QObject>
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "controllers/accounts/Account.hpp" #include "common/Singleton.hpp"
#include "providers/twitch/TwitchAccountManager.hpp" #include "providers/twitch/TwitchAccountManager.hpp"
#include "util/SharedPtrElementLess.hpp" #include "util/SharedPtrElementLess.hpp"
namespace chatterino { namespace chatterino {
class Account;
class Settings; class Settings;
class Paths; class Paths;

View file

@ -1,5 +1,6 @@
#include "AccountModel.hpp" #include "AccountModel.hpp"
#include "controllers/accounts/Account.hpp"
#include "util/StandardItemHelper.hpp" #include "util/StandardItemHelper.hpp"
namespace chatterino { namespace chatterino {

View file

@ -8,6 +8,7 @@
namespace chatterino { namespace chatterino {
class Account;
class AccountController; class AccountController;
class AccountModel : public SignalVectorModel<std::shared_ptr<Account>> class AccountModel : public SignalVectorModel<std::shared_ptr<Account>>

View file

@ -5,7 +5,10 @@
#include "controllers/accounts/AccountController.hpp" #include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/Command.hpp" #include "controllers/commands/Command.hpp"
#include "controllers/commands/CommandModel.hpp" #include "controllers/commands/CommandModel.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/TwitchApi.hpp" #include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
@ -78,7 +81,7 @@ void CommandController::save()
{ {
QFile textFile(this->filePath_); QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::WriteOnly)) { if (!textFile.open(QIODevice::WriteOnly)) {
Log("[CommandController::saveCommands] Unable to open {} for writing", log("[CommandController::saveCommands] Unable to open {} for writing",
this->filePath_); this->filePath_);
return; return;
} }

View file

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

View file

@ -1,15 +1,16 @@
#pragma once #pragma once
#include "common/Singleton.hpp" #include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "controllers/highlights/HighlightBlacklistUser.hpp" #include "controllers/highlights/HighlightBlacklistUser.hpp"
#include "controllers/highlights/HighlightPhrase.hpp" #include "controllers/highlights/HighlightPhrase.hpp"
#include "messages/Message.hpp"
#include "singletons/Settings.hpp"
namespace chatterino { namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class Settings; class Settings;
class Paths; class Paths;

View file

@ -37,13 +37,13 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item,
void HighlightModel::afterInit() void HighlightModel::afterInit()
{ {
std::vector<QStandardItem *> row = this->createRow(); std::vector<QStandardItem *> row = this->createRow();
setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), setBoolItem(row[0], getSettings()->enableHighlightsSelf.getValue(), true,
true, false); false);
row[0]->setData("Your username (automatic)", Qt::DisplayRole); row[0]->setData("Your username (automatic)", Qt::DisplayRole);
setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), setBoolItem(row[1], getSettings()->enableHighlightTaskbar.getValue(), true,
true, false); false);
setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(), setBoolItem(row[2], getSettings()->enableHighlightSound.getValue(), true,
true, false); false);
row[3]->setFlags(0); row[3]->setFlags(0);
this->insertCustomRow(row, 0); this->insertCustomRow(row, 0);
} }
@ -55,20 +55,17 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
switch (column) { switch (column) {
case 0: { case 0: {
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightsSelf.setValue( getSettings()->enableHighlightsSelf.setValue(value.toBool());
value.toBool());
} }
} break; } break;
case 1: { case 1: {
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightTaskbar.setValue( getSettings()->enableHighlightTaskbar.setValue(value.toBool());
value.toBool());
} }
} break; } break;
case 2: { case 2: {
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightSound.setValue( getSettings()->enableHighlightSound.setValue(value.toBool());
value.toBool());
} }
} break; } break;
case 3: { case 3: {

View file

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

View file

@ -1,10 +1,9 @@
#pragma once #pragma once
#include "common/Singleton.hpp" #include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp" #include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "controllers/ignores/IgnorePhrase.hpp" #include "controllers/ignores/IgnorePhrase.hpp"
#include "singletons/Settings.hpp"
namespace chatterino { namespace chatterino {

View file

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

View file

@ -2,6 +2,7 @@
#include <QRegularExpression> #include <QRegularExpression>
#include "Application.hpp" #include "Application.hpp"
#include "messages/Image.hpp"
#include "singletons/Resources.hpp" #include "singletons/Resources.hpp"
namespace chatterino { namespace chatterino {
@ -60,8 +61,7 @@ ModerationAction::ModerationAction(const QString &action)
// str); // str);
// } // }
} else if (action.startsWith("/ban ")) { } else if (action.startsWith("/ban ")) {
this->image_ = this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
} else { } else {
QString xD = action; QString xD = action;

View file

@ -4,11 +4,13 @@
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <pajlada/settings/serialize.hpp> #include <pajlada/settings/serialize.hpp>
#include "messages/Image.hpp"
#include "util/RapidjsonHelpers.hpp" #include "util/RapidjsonHelpers.hpp"
namespace chatterino { namespace chatterino {
class Image;
using ImagePtr = std::shared_ptr<Image>;
class ModerationAction class ModerationAction
{ {
public: public:
@ -34,34 +36,34 @@ private:
namespace pajlada { namespace pajlada {
namespace Settings { namespace Settings {
template <> template <>
struct Serialize<chatterino::ModerationAction> { struct Serialize<chatterino::ModerationAction> {
static rapidjson::Value get(const chatterino::ModerationAction &value, static rapidjson::Value get(const chatterino::ModerationAction &value,
rapidjson::Document::AllocatorType &a) rapidjson::Document::AllocatorType &a)
{ {
rapidjson::Value ret(rapidjson::kObjectType); rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getAction(), a); AddMember(ret, "pattern", value.getAction(), a);
return ret; return ret;
}
};
template <>
struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::ModerationAction(QString());
} }
};
QString pattern; template <>
struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::ModerationAction(QString());
}
chatterino::rj::getSafe(value, "pattern", pattern); QString pattern;
return chatterino::ModerationAction(pattern); chatterino::rj::getSafe(value, "pattern", pattern);
}
}; return chatterino::ModerationAction(pattern);
}
};
} // namespace Settings } // namespace Settings
} // namespace pajlada } // namespace pajlada

View file

@ -17,12 +17,16 @@ void ModerationActions::initialize(Settings &settings, Paths &paths)
assert(!this->initialized_); assert(!this->initialized_);
this->initialized_ = true; this->initialized_ = true;
for (auto &val : this->setting_.getValue()) { this->setting_ =
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
"/moderation/actions");
for (auto &val : this->setting_->getValue()) {
this->items.insertItem(val); this->items.insertItem(val);
} }
this->items.delayedItemsChanged.connect([this] { // this->items.delayedItemsChanged.connect([this] { //
this->setting_.setValue(this->items.getVector()); this->setting_->setValue(this->items.getVector());
}); });
} }

View file

@ -25,8 +25,7 @@ public:
ModerationActionModel *createModel(QObject *parent); ModerationActionModel *createModel(QObject *parent);
private: private:
ChatterinoSetting<std::vector<ModerationAction>> setting_ = { std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
"/moderation/actions"};
bool initialized_ = false; bool initialized_ = false;
}; };

View file

@ -29,12 +29,12 @@ void TaggedUsersModel::afterInit()
{ {
// std::vector<QStandardItem *> row = this->createRow(); // std::vector<QStandardItem *> row = this->createRow();
// setBoolItem(row[0], // setBoolItem(row[0],
// getApp()->settings->enableHighlightsSelf.getValue(), true, false); // getSettings()->enableHighlightsSelf.getValue(), true, false);
// row[0]->setData("Your username (automatic)", Qt::DisplayRole); // row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// setBoolItem(row[1], // setBoolItem(row[1],
// getApp()->settings->enableHighlightTaskbar.getValue(), true, false); // getSettings()->enableHighlightTaskbar.getValue(), true, false);
// setBoolItem(row[2], // setBoolItem(row[2],
// getApp()->settings->enableHighlightSound.getValue(), true, false); // getSettings()->enableHighlightSound.getValue(), true, false);
// row[3]->setFlags(0); this->insertCustomRow(row, 0); // row[3]->setFlags(0); this->insertCustomRow(row, 0);
} }
@ -45,17 +45,17 @@ void TaggedUsersModel::afterInit()
// switch (column) { // switch (column) {
// case 0: { // case 0: {
// if (role == Qt::CheckStateRole) { // if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool()); // getSettings()->enableHighlightsSelf.setValue(value.toBool());
// } // }
// } break; // } break;
// case 1: { // case 1: {
// if (role == Qt::CheckStateRole) { // if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool()); // getSettings()->enableHighlightTaskbar.setValue(value.toBool());
// } // }
// } break; // } break;
// case 2: { // case 2: {
// if (role == Qt::CheckStateRole) { // if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightSound.setValue(value.toBool()); // getSettings()->enableHighlightSound.setValue(value.toBool());
// } // }
// } break; // } break;
// case 3: { // case 3: {

21
src/debug/Benchmark.cpp Normal file
View file

@ -0,0 +1,21 @@
#include "Benchmark.hpp"
namespace chatterino {
BenchmarkGuard::BenchmarkGuard(const QString &_name)
: name_(_name)
{
timer_.start();
}
BenchmarkGuard::~BenchmarkGuard()
{
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
}
qreal BenchmarkGuard::getElapsedMs()
{
return qreal(timer_.nsecsElapsed()) / 1000000.0;
}
} // namespace chatterino

View file

@ -1,42 +1,22 @@
#pragma once #pragma once
#include <QDebug> #include "debug/Log.hpp"
#include <QElapsedTimer> #include <QElapsedTimer>
#include <boost/current_function.hpp>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#define BENCH(x) \
QElapsedTimer x; \
x.start();
#define MARK(x) \
qDebug() << BOOST_CURRENT_FUNCTION << __LINE__ \
<< static_cast<float>(x.nsecsElapsed()) / 1000000.0 << "ms";
namespace chatterino { namespace chatterino {
class BenchmarkGuard : boost::noncopyable class BenchmarkGuard : boost::noncopyable
{ {
QElapsedTimer timer;
QString name;
public: public:
BenchmarkGuard(const QString &_name) BenchmarkGuard(const QString &_name);
: name(_name) ~BenchmarkGuard();
{ qreal getElapsedMs();
timer.start();
}
~BenchmarkGuard() private:
{ QElapsedTimer timer_;
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f QString name_;
<< "ms";
}
qreal getElapsedMs()
{
return qreal(timer.nsecsElapsed()) / 1000000.0;
}
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -8,17 +8,22 @@
namespace chatterino { namespace chatterino {
template <typename... Args> template <typename... Args>
inline void Log(const std::string &formatString, Args &&... args) inline void log(const std::string &formatString, Args &&... args)
{ {
qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz") qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz")
<< fS(formatString, std::forward<Args>(args)...).c_str(); << fS(formatString, std::forward<Args>(args)...).c_str();
} }
template <typename... Args> template <typename... Args>
inline void Warn(const std::string &formatString, Args &&... args) inline void log(const char *formatString, Args &&... args)
{ {
qWarning() << QTime::currentTime().toString("hh:mm:ss.zzz") log(std::string(formatString), std::forward<Args>(args)...);
<< fS(formatString, std::forward<Args>(args)...).c_str(); }
template <typename... Args>
inline void log(const QString &formatString, Args &&... args)
{
log(formatString.toStdString(), std::forward<Args>(args)...);
} }
} // namespace chatterino } // namespace chatterino

View file

@ -5,13 +5,15 @@
#include <QApplication> #include <QApplication>
#include <QStringList> #include <QStringList>
#include <memory>
#include <messages/Image.hpp>
using namespace chatterino; using namespace chatterino;
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
auto shared = std::make_shared<QString>();
log(std::atomic_is_lock_free(&shared));
QApplication a(argc, argv); QApplication a(argc, argv);
// convert char** to QStringList // convert char** to QStringList

View file

@ -15,61 +15,31 @@ bool operator!=(const Emote &a, const Emote &b)
return !(a == b); return !(a == b);
} }
// EmotePtr Emote::create(const EmoteData2 &data) EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache)
//{ {
//} // reuse old shared_ptr if nothing changed
auto it = cache.find(emote.name);
if (it != cache.end() && *it->second == emote) return it->second;
// EmotePtr Emote::create(EmoteData2 &&data) return std::make_shared<Emote>(std::move(emote));
//{ }
//}
// Emote::Emote(EmoteData2 &&data) EmotePtr cachedOrMakeEmotePtr(
// : data_(data) Emote &&emote,
//{ std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
//} std::mutex &mutex, const EmoteId &id)
// {
// Emote::Emote(const EmoteData2 &data) std::lock_guard<std::mutex> guard(mutex);
// : data_(data)
//{ auto shared = cache[id].lock();
//} if (shared && *shared == emote) {
// // reuse old shared_ptr if nothing changed
// const Url &Emote::getHomePage() const return shared;
//{ } else {
// return this->data_.homePage; shared = std::make_shared<Emote>(std::move(emote));
//} cache[id] = shared;
// return shared;
// const EmoteName &Emote::getName() const }
//{ }
// return this->data_.name;
//}
//
// const Tooltip &Emote::getTooltip() const
//{
// return this->data_.tooltip;
//}
//
// const ImageSet &Emote::getImages() const
//{
// return this->data_.images;
//}
//
// const QString &Emote::getCopyString() const
//{
// return this->data_.name.string;
//}
//
// bool Emote::operator==(const Emote &other) const
//{
// auto &a = this->data_;
// auto &b = other.data_;
//
// return std::tie(a.homePage, a.name, a.tooltip, a.images) ==
// std::tie(b.homePage, b.name, b.tooltip, b.images);
//}
//
// bool Emote::operator!=(const Emote &other) const
//{
// return !this->operator==(other);
//}
} // namespace chatterino } // namespace chatterino

View file

@ -7,9 +7,6 @@
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
QStringAlias(EmoteId);
QStringAlias(EmoteName);
namespace chatterino { namespace chatterino {
struct Emote { struct Emote {
@ -37,29 +34,10 @@ using EmoteIdMap = std::unordered_map<EmoteId, EmotePtr>;
using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>; using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>;
using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>; using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>;
// struct EmoteData2 { EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache);
// EmoteName name; EmotePtr cachedOrMakeEmotePtr(
// ImageSet images; Emote &&emote,
// Tooltip tooltip; std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
// Url homePage; std::mutex &mutex, const EmoteId &id);
//};
//
// class Emote
//{
// public:
// Emote(EmoteData2 &&data);
// Emote(const EmoteData2 &data);
//
// const Url &getHomePage() const;
// const EmoteName &getName() const;
// const Tooltip &getTooltip() const;
// const ImageSet &getImages() const;
// const QString &getCopyString() const;
// bool operator==(const Emote &other) const;
// bool operator!=(const Emote &other) const;
//
// private:
// EmoteData2 data_;
//};
} // namespace chatterino } // namespace chatterino

View file

@ -1,93 +0,0 @@
#pragma once
#include <QString>
#include <boost/optional.hpp>
#include <unordered_map>
#include <util/QStringHash.hpp>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
template <typename TKey>
class MapReplacement
{
public:
MapReplacement(std::unordered_map<TKey, EmotePtr> &items)
: oldItems_(items)
{
}
void add(const TKey &key, const Emote &data)
{
this->add(key, Emote(data));
}
void add(const TKey &key, Emote &&data)
{
auto it = this->oldItems_.find(key);
if (it != this->oldItems_.end() && *it->second == data) {
this->newItems_[key] = it->second;
} else {
this->newItems_[key] = std::make_shared<Emote>(std::move(data));
}
}
void apply()
{
this->oldItems_ = std::move(this->newItems_);
}
private:
std::unordered_map<TKey, EmotePtr> &oldItems_;
std::unordered_map<TKey, EmotePtr> newItems_;
};
template <typename TKey>
class EmoteCache
{
public:
using Iterator = typename std::unordered_map<TKey, EmotePtr>::iterator;
using ConstIterator = typename std::unordered_map<TKey, EmotePtr>::iterator;
Iterator begin()
{
return this->items_.begin();
}
ConstIterator begin() const
{
return this->items_.begin();
}
Iterator end()
{
return this->items_.end();
}
ConstIterator end() const
{
return this->items_.end();
}
boost::optional<EmotePtr> get(const TKey &key) const
{
auto it = this->items_.find(key);
if (it == this->items_.end())
return boost::none;
else
return it->second;
}
MapReplacement<TKey> makeReplacment()
{
return MapReplacement<TKey>(this->items_);
}
private:
std::unordered_map<TKey, EmotePtr> items_;
};
} // namespace chatterino

View file

@ -1,44 +0,0 @@
#include "EmoteMap.hpp"
#include "Application.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
// EmoteData::EmoteData(Image *image)
// : image1x(image)
//{
//}
//// Emotes must have a 1x image to be valid
// bool EmoteData::isValid() const
//{
// return this->image1x != nullptr;
//}
// Image *EmoteData::getImage(float scale) const
//{
// int quality = getApp()->settings->preferredEmoteQuality;
// if (quality == 0) {
// scale *= getApp()->settings->emoteScale.getValue();
// quality = [&] {
// if (scale <= 1) return 1;
// if (scale <= 2) return 2;
// return 3;
// }();
// }
// Image *_image;
// if (quality == 3 && this->image3x != nullptr) {
// _image = this->image3x;
// } else if (quality >= 2 && this->image2x != nullptr) {
// _image = this->image2x;
// } else {
// _image = this->image1x;
// }
// return _image;
//}
} // namespace chatterino

View file

@ -1,20 +0,0 @@
#pragma once
#include "boost/optional.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
// class EmoteMap
//{
// public:
// void add(Emote emote);
// void remove(const Emote &emote);
// void remove(const QString &name);
// private:
//};
// using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino

View file

@ -1,8 +1,10 @@
#include "messages/Image.hpp" #include "messages/Image.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "debug/AssertInGuiThread.hpp" #include "debug/AssertInGuiThread.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include "singletons/WindowManager.hpp" #include "singletons/WindowManager.hpp"
@ -15,119 +17,171 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QTimer> #include <QTimer>
#include <functional> #include <functional>
#include <thread> #include <thread>
namespace chatterino { namespace chatterino {
namespace { namespace {
const QPixmap *getPixmap(const Pixmap &pixmap) // Frames
{ Frames::Frames()
if (pixmap.which() == 0) {
return boost::get<const QPixmap *>(pixmap); DebugCount::increase("images");
else }
return boost::get<std::unique_ptr<QPixmap>>(pixmap).get();
}
// Frames Frames::Frames(const QVector<Frame<QPixmap>> &frames)
Frames::Frames() : items_(frames)
{ {
DebugCount::increase("images"); assertInGuiThread();
} DebugCount::increase("images");
Frames::Frames(std::vector<Frame> &&frames) if (this->animated()) {
: items_(std::move(frames)) DebugCount::increase("animated images");
{
DebugCount::increase("images");
if (this->animated()) DebugCount::increase("animated images");
}
Frames::~Frames() this->gifTimerConnection_ =
{ getApp()->emotes->gifTimer.signal.connect(
DebugCount::decrease("images"); [this] { this->advance(); });
if (this->animated()) DebugCount::decrease("animated images");
}
void Frames::advance()
{
this->durationOffset_ += GIF_FRAME_LENGTH;
while (true) {
this->index_ %= this->items_.size();
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");
const QPixmap *Frames::current() const if (this->animated()) {
{ DebugCount::decrease("animated images");
if (this->items_.size() == 0) return nullptr; }
return getPixmap(this->items_[this->index_].pixmap);
}
const QPixmap *Frames::first() const this->gifTimerConnection_.disconnect();
{ }
if (this->items_.size() == 0) return nullptr;
return getPixmap(this->items_.front().pixmap);
}
// functions void Frames::advance()
std::vector<Frame> readFrames(QImageReader &reader, const Url &url) {
{ this->durationOffset_ += GIF_FRAME_LENGTH;
std::vector<Frame> 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(
auto pixmap = std::make_unique<QPixmap>(QPixmap::fromImage(image)); 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;
int duration = std::max(20, reader.nextImageDelay()); while (!queued.empty()) {
frames.push_back(Frame{std::move(pixmap), duration}); queued.front().first(queued.front().second);
queued.pop();
if (++i > 50) {
QTimer::singleShot(3, [&] {
assignDelayed(queued, mutex, loadedEventQueued);
});
return;
}
} }
getApp()->windows->forceLayoutChannelViews();
loadedEventQueued = false;
} }
if (frames.size() != 0) { template <typename Assign>
Log("Error while reading image {}: '{}'", url.string, auto makeConvertCallback(const QVector<Frame<QImage>> &parsed,
reader.errorString()); 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);
});
}
};
} }
return frames;
}
void queueLoadedEvent()
{
static auto eventQueued = false;
if (!eventQueued) {
eventQueued = true;
QTimer::singleShot(250, [] {
getApp()->windows->incGeneration();
getApp()->windows->layoutChannelViews();
eventQueued = false;
});
}
}
} // namespace } // namespace
// IMAGE2 // IMAGE2
std::atomic<bool> Image::loadedEventQueued{false};
ImagePtr Image::fromUrl(const Url &url, qreal scale) ImagePtr Image::fromUrl(const Url &url, qreal scale)
{ {
static std::unordered_map<Url, std::weak_ptr<Image>> cache; static std::unordered_map<Url, std::weak_ptr<Image>> cache;
@ -140,18 +194,13 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
if (!shared) { if (!shared) {
cache[url] = shared = ImagePtr(new Image(url, scale)); cache[url] = shared = ImagePtr(new Image(url, scale));
} else { } else {
Warn("same image loaded multiple times: {}", url.string); // Warn("same image loaded multiple times: {}", url.string);
} }
return shared; return shared;
} }
ImagePtr Image::fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, qreal scale) ImagePtr Image::fromPixmap(const QPixmap &pixmap, qreal scale)
{
return ImagePtr(new Image(std::move(pixmap), scale));
}
ImagePtr Image::fromNonOwningPixmap(QPixmap *pixmap, qreal scale)
{ {
return ImagePtr(new Image(pixmap, scale)); return ImagePtr(new Image(pixmap, scale));
} }
@ -171,23 +220,15 @@ Image::Image(const Url &url, qreal scale)
: url_(url) : url_(url)
, scale_(scale) , scale_(scale)
, shouldLoad_(true) , shouldLoad_(true)
, frames_(std::make_unique<Frames>())
{ {
} }
Image::Image(std::unique_ptr<QPixmap> owning, qreal scale) Image::Image(const QPixmap &pixmap, qreal scale)
: scale_(scale) : scale_(scale)
, frames_(std::make_unique<Frames>(
QVector<Frame<QPixmap>>{Frame<QPixmap>{pixmap, 1}}))
{ {
std::vector<Frame> vec;
vec.push_back(Frame{std::move(owning)});
this->frames_ = std::move(vec);
}
Image::Image(QPixmap *nonOwning, qreal scale)
: scale_(scale)
{
std::vector<Frame> vec;
vec.push_back(Frame{nonOwning});
this->frames_ = std::move(vec);
} }
const Url &Image::url() const const Url &Image::url() const
@ -195,7 +236,7 @@ const Url &Image::url() const
return this->url_; return this->url_;
} }
const QPixmap *Image::pixmap() const boost::optional<QPixmap> Image::pixmap() const
{ {
assertInGuiThread(); assertInGuiThread();
@ -204,7 +245,7 @@ const QPixmap *Image::pixmap() const
const_cast<Image *>(this)->load(); const_cast<Image *>(this)->load();
} }
return this->frames_.current(); return this->frames_->current();
} }
qreal Image::scale() const qreal Image::scale() const
@ -212,7 +253,7 @@ qreal Image::scale() const
return this->scale_; return this->scale_;
} }
bool Image::empty() const bool Image::isEmpty() const
{ {
return this->empty_; return this->empty_;
} }
@ -221,14 +262,14 @@ bool Image::animated() const
{ {
assertInGuiThread(); assertInGuiThread();
return this->frames_.animated(); return this->frames_->animated();
} }
int Image::width() const int Image::width() const
{ {
assertInGuiThread(); assertInGuiThread();
if (auto pixmap = this->frames_.first()) if (auto pixmap = this->frames_->first())
return pixmap->width() * this->scale_; return pixmap->width() * this->scale_;
else else
return 16; return 16;
@ -238,7 +279,7 @@ int Image::height() const
{ {
assertInGuiThread(); assertInGuiThread();
if (auto pixmap = this->frames_.first()) if (auto pixmap = this->frames_->first())
return pixmap->height() * this->scale_; return pixmap->height() * this->scale_;
else else
return 16; return 16;
@ -247,39 +288,37 @@ int Image::height() const
void Image::load() void Image::load()
{ {
NetworkRequest req(this->url().string); NetworkRequest req(this->url().string);
req.setExecuteConcurrently(true);
req.setCaller(&this->object_); req.setCaller(&this->object_);
req.setUseQuickLoadCache(true); req.setUseQuickLoadCache(true);
req.onSuccess([this, weak = weakOf(this)](auto result) -> Outcome { req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome {
assertInGuiThread();
auto shared = weak.lock(); auto shared = weak.lock();
if (!shared) return Failure; if (!shared) return Failure;
auto data = result.getData();
// const cast since we are only reading from it // const cast since we are only reading from it
QBuffer buffer(const_cast<QByteArray *>(&result.getData())); QBuffer buffer(const_cast<QByteArray *>(&data));
buffer.open(QIODevice::ReadOnly); buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer); QImageReader reader(&buffer);
auto parsed = readFrames(reader, that->url());
postToThread(makeConvertCallback(parsed, [weak](auto frames) {
if (auto shared = weak.lock())
shared->frames_ = std::make_unique<Frames>(frames);
}));
this->frames_ = readFrames(reader, this->url());
return Success; return Success;
}); });
req.onError([this, weak = weakOf(this)](int) {
auto shared = weak.lock();
if (!shared) return false;
this->frames_ = std::vector<Frame>();
return false;
});
req.execute(); req.execute();
} }
bool Image::operator==(const Image &other) const bool Image::operator==(const Image &other) const
{ {
if (this->empty() && other.empty()) return true; if (this->isEmpty() && other.isEmpty()) return true;
if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true; if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true;
if (this->frames_.first() == other.frames_.first()) return true; if (this->frames_->first() == other.frames_->first()) return true;
return false; return false;
} }

View file

@ -1,43 +1,45 @@
#pragma once #pragma once
#include "common/Common.hpp"
#include <QPixmap> #include <QPixmap>
#include <QString> #include <QString>
#include <QThread>
#include <QVector>
#include <atomic> #include <atomic>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <boost/optional.hpp>
#include <boost/variant.hpp> #include <boost/variant.hpp>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <pajlada/signals/signal.hpp>
#include "common/Aliases.hpp"
#include "common/NullablePtr.hpp" #include "common/NullablePtr.hpp"
namespace chatterino { namespace chatterino {
namespace { namespace {
using Pixmap = boost::variant<const QPixmap *, std::unique_ptr<QPixmap>>; template <typename Image>
struct Frame { struct Frame {
Pixmap pixmap; Image image;
int duration; int duration;
}; };
class Frames class Frames : boost::noncopyable
{ {
public: public:
Frames(); Frames();
Frames(std::vector<Frame> &&frames); Frames(const QVector<Frame<QPixmap>> &frames);
~Frames(); ~Frames();
Frames(Frames &&other) = default;
Frames &operator=(Frames &&other) = default;
bool animated() const; bool animated() const;
void advance(); void advance();
const QPixmap *current() const; boost::optional<QPixmap> current() const;
const QPixmap *first() const; boost::optional<QPixmap> first() const;
private: private:
std::vector<Frame> items_; QVector<Frame<QPixmap>> items_;
int index_{0}; int index_{0};
int durationOffset_{0}; int durationOffset_{0};
}; pajlada::Signals::Connection gifTimerConnection_;
};
} // namespace } // namespace
class Image; class Image;
@ -47,15 +49,13 @@ class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
{ {
public: public:
static ImagePtr fromUrl(const Url &url, qreal scale = 1); static ImagePtr fromUrl(const Url &url, qreal scale = 1);
static ImagePtr fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, static ImagePtr fromPixmap(const QPixmap &pixmap, qreal scale = 1);
qreal scale = 1);
static ImagePtr fromNonOwningPixmap(QPixmap *pixmap, qreal scale = 1);
static ImagePtr getEmpty(); static ImagePtr getEmpty();
const Url &url() const; const Url &url() const;
const QPixmap *pixmap() const; boost::optional<QPixmap> pixmap() const;
qreal scale() const; qreal scale() const;
bool empty() const; bool isEmpty() const;
int width() const; int width() const;
int height() const; int height() const;
bool animated() const; bool animated() const;
@ -66,8 +66,7 @@ public:
private: private:
Image(); Image();
Image(const Url &url, qreal scale); Image(const Url &url, qreal scale);
Image(std::unique_ptr<QPixmap> owning, qreal scale); Image(const QPixmap &nonOwning, qreal scale);
Image(QPixmap *nonOwning, qreal scale);
void load(); void load();
@ -75,9 +74,7 @@ private:
qreal scale_{1}; qreal scale_{1};
bool empty_{false}; bool empty_{false};
bool shouldLoad_{false}; bool shouldLoad_{false};
Frames frames_{}; std::unique_ptr<Frames> frames_{};
QObject object_{}; QObject object_{};
static std::atomic<bool> loadedEventQueued;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,7 +1,5 @@
#include "ImageSet.hpp" #include "messages/ImageSet.hpp"
#include "Application.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
namespace chatterino { namespace chatterino {
@ -60,23 +58,19 @@ const ImagePtr &ImageSet::getImage3() const
const ImagePtr &ImageSet::getImage(float scale) const const ImagePtr &ImageSet::getImage(float scale) const
{ {
int quality = getSettings()->preferredEmoteQuality; int quality = 1;
if (!quality) { if (scale > 2.999)
if (scale > 3.999) quality = 3;
quality = 3; else if (scale > 1.5)
else if (scale > 1.999) quality = 2;
quality = 2;
else
scale = 1;
}
if (!this->imageX3_->empty() && quality == 3) { if (!this->imageX3_->isEmpty() && quality == 3) {
return this->imageX3_; return this->imageX3_;
} }
if (!this->imageX2_->empty() && quality == 2) { if (!this->imageX2_->isEmpty() && quality == 2) {
return this->imageX3_; return this->imageX2_;
} }
return this->imageX1_; return this->imageX1_;

View file

@ -21,8 +21,6 @@ public:
const ImagePtr &getImage(float scale) const; const ImagePtr &getImage(float scale) const;
ImagePtr getImage(float scale);
bool operator==(const ImageSet &other) const; bool operator==(const ImageSet &other) const;
bool operator!=(const ImageSet &other) const; bool operator!=(const ImageSet &other) const;

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <common/Common.hpp>
namespace chatterino { namespace chatterino {
@ -14,6 +13,7 @@ public:
UserInfo, UserInfo,
UserTimeout, UserTimeout,
UserBan, UserBan,
UserWhisper,
InsertText, InsertText,
ShowMessage, ShowMessage,
UserAction, UserAction,

View file

@ -1,16 +1,16 @@
#pragma once #pragma once
#include "common/FlagsEnum.hpp" #include "common/FlagsEnum.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "widgets/helper/ScrollbarHighlight.hpp" #include "widgets/helper/ScrollbarHighlight.hpp"
#include <QTime> #include <QTime>
#include <boost/noncopyable.hpp>
#include <cinttypes> #include <cinttypes>
#include <memory> #include <memory>
#include <vector> #include <vector>
namespace chatterino { namespace chatterino {
class MessageElement;
enum class MessageFlag : uint16_t { enum class MessageFlag : uint16_t {
None = 0, None = 0,

View file

@ -1,6 +1,9 @@
#include "MessageBuilder.hpp" #include "MessageBuilder.hpp"
#include "common/LinkParser.hpp" #include "common/LinkParser.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp" #include "singletons/Resources.hpp"
#include "singletons/Theme.hpp" #include "singletons/Theme.hpp"
@ -17,7 +20,7 @@ MessagePtr makeSystemMessage(const QString &text)
} }
MessageBuilder::MessageBuilder() MessageBuilder::MessageBuilder()
: message_(std::make_unique<Message>()) : message_(std::make_shared<Message>())
{ {
} }
@ -82,6 +85,7 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
} }
MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count) MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
: MessageBuilder()
{ {
this->emplace<TimestampElement>(); this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System); this->message().flags.set(MessageFlag::System);
@ -127,6 +131,7 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
} }
MessageBuilder::MessageBuilder(const UnbanAction &action) MessageBuilder::MessageBuilder(const UnbanAction &action)
: MessageBuilder()
{ {
this->emplace<TimestampElement>(); this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System); this->message().flags.set(MessageFlag::System);
@ -163,7 +168,9 @@ Message &MessageBuilder::message()
MessagePtr MessageBuilder::release() MessagePtr MessageBuilder::release()
{ {
return MessagePtr(this->message_.release()); std::shared_ptr<Message> ptr;
this->message_.swap(ptr);
return ptr;
} }
void MessageBuilder::append(std::unique_ptr<MessageElement> element) void MessageBuilder::append(std::unique_ptr<MessageElement> element)

View file

@ -1,11 +1,15 @@
#pragma once #pragma once
#include "messages/Message.hpp" #include "messages/MessageElement.hpp"
#include <QRegularExpression> #include <QRegularExpression>
#include <ctime> #include <ctime>
namespace chatterino { namespace chatterino {
struct BanAction;
struct UnbanAction;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
struct SystemMessageTag { struct SystemMessageTag {
}; };
@ -16,6 +20,14 @@ const TimeoutMessageTag timeoutMessage{};
MessagePtr makeSystemMessage(const QString &text); MessagePtr makeSystemMessage(const QString &text);
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
class MessageBuilder class MessageBuilder
{ {
public: public:
@ -48,7 +60,7 @@ public:
} }
private: private:
std::unique_ptr<Message> message_; std::shared_ptr<Message> message_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,5 +1,7 @@
#include "MessageColor.hpp" #include "MessageColor.hpp"
#include "singletons/Theme.hpp"
namespace chatterino { namespace chatterino {
MessageColor::MessageColor(const QColor &color) MessageColor::MessageColor(const QColor &color)

View file

@ -1,10 +1,9 @@
#pragma once #pragma once
#include "singletons/Theme.hpp"
#include <QColor> #include <QColor>
namespace chatterino { namespace chatterino {
class Theme;
struct MessageColor { struct MessageColor {
enum Type { Custom, Text, Link, System }; enum Type { Custom, Text, Link, System };

View file

@ -0,0 +1,9 @@
#include "MessageContainer.hpp"
namespace chatterino {
MessageContainer::MessageContainer()
{
}
} // namespace chatterino

View file

@ -0,0 +1,13 @@
#pragma once
#include <deque>
namespace chatterino {
class MessageContainer
{
public:
MessageContainer();
};
} // namespace chatterino

View file

@ -1,12 +1,13 @@
#include "messages/MessageElement.hpp" #include "messages/MessageElement.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/Emotemap.hpp"
#include "controllers/moderationactions/ModerationActions.hpp" #include "controllers/moderationactions/ModerationActions.hpp"
#include "debug/Benchmark.hpp" #include "debug/Benchmark.hpp"
#include "messages/Emote.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp" #include "messages/layouts/MessageLayoutContainer.hpp"
#include "messages/layouts/MessageLayoutElement.hpp" #include "messages/layouts/MessageLayoutElement.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
namespace chatterino { namespace chatterino {
@ -102,7 +103,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
if (flags.hasAny(this->getFlags())) { if (flags.hasAny(this->getFlags())) {
if (flags.has(MessageElementFlag::EmoteImages)) { if (flags.has(MessageElementFlag::EmoteImages)) {
auto image = this->emote_->images.getImage(container.getScale()); auto image = this->emote_->images.getImage(container.getScale());
if (image->empty()) return; if (image->isEmpty()) return;
auto size = QSize(int(container.getScale() * image->width()), auto size = QSize(int(container.getScale() * image->width()),
int(container.getScale() * image->height())); int(container.getScale() * image->height()));
@ -224,8 +225,8 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container,
{ {
if (flags.hasAny(this->getFlags())) { if (flags.hasAny(this->getFlags())) {
auto app = getApp(); auto app = getApp();
if (app->settings->timestampFormat != this->format_) { if (getSettings()->timestampFormat != this->format_) {
this->format_ = app->settings->timestampFormat.getValue(); this->format_ = getSettings()->timestampFormat.getValue();
this->element_.reset(this->formatTime(this->time_)); this->element_.reset(this->formatTime(this->time_));
} }
@ -237,7 +238,7 @@ TextElement *TimestampElement::formatTime(const QTime &time)
{ {
static QLocale locale("en_US"); static QLocale locale("en_US");
QString format = locale.toString(time, getApp()->settings->timestampFormat); QString format = locale.toString(time, getSettings()->timestampFormat);
return new TextElement(format, MessageElementFlag::Timestamp, return new TextElement(format, MessageElementFlag::Timestamp,
MessageColor::System, FontStyle::ChatMedium); MessageColor::System, FontStyle::ChatMedium);

View file

@ -1,9 +1,6 @@
#pragma once #pragma once
#include "common/Emotemap.hpp"
#include "common/FlagsEnum.hpp" #include "common/FlagsEnum.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/Link.hpp" #include "messages/Link.hpp"
#include "messages/MessageColor.hpp" #include "messages/MessageColor.hpp"
#include "singletons/Fonts.hpp" #include "singletons/Fonts.hpp"
@ -12,14 +9,20 @@
#include <QString> #include <QString>
#include <QTime> #include <QTime>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <vector>
namespace chatterino { namespace chatterino {
class Channel; class Channel;
struct MessageLayoutContainer; struct MessageLayoutContainer;
class Image;
using ImagePtr = std::shared_ptr<Image>;
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
enum class MessageElementFlag { enum class MessageElementFlag {
None = 0, None = 0,
Misc = (1 << 0), Misc = (1 << 0),

View file

@ -2,12 +2,4 @@
namespace chatterino { namespace chatterino {
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
} // namespace chatterino } // namespace chatterino

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <tuple>
#include <utility> #include <utility>
namespace chatterino { namespace chatterino {
@ -23,14 +24,8 @@ struct SelectionItem {
bool operator<(const SelectionItem &b) const bool operator<(const SelectionItem &b) const
{ {
if (this->messageIndex < b.messageIndex) { return std::tie(this->messageIndex, this->charIndex) <
return true; std::tie(b.messageIndex, b.charIndex);
}
if (this->messageIndex == b.messageIndex &&
this->charIndex < b.charIndex) {
return true;
}
return false;
} }
bool operator>(const SelectionItem &b) const bool operator>(const SelectionItem &b) const

View file

@ -2,8 +2,12 @@
#include "Application.hpp" #include "Application.hpp"
#include "debug/Benchmark.hpp" #include "debug/Benchmark.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp" #include "singletons/WindowManager.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
@ -24,6 +28,7 @@ namespace chatterino {
MessageLayout::MessageLayout(MessagePtr message) MessageLayout::MessageLayout(MessagePtr message)
: message_(message) : message_(message)
, buffer_(nullptr) , buffer_(nullptr)
, container_(std::make_shared<MessageLayoutContainer>())
{ {
DebugCount::increase("message layout"); DebugCount::increase("message layout");
} }
@ -41,7 +46,7 @@ const Message *MessageLayout::getMessage()
// Height // Height
int MessageLayout::getHeight() const int MessageLayout::getHeight() const
{ {
return container_.getHeight(); return container_->getHeight();
} }
// Layout // Layout
@ -68,7 +73,7 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
// check if work mask changed // check if work mask changed
layoutRequired |= this->currentWordFlags_ != flags; layoutRequired |= this->currentWordFlags_ != flags;
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask(); this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask();
// check if layout was requested manually // check if layout was requested manually
layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout); layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout);
@ -82,9 +87,9 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
return false; return false;
} }
int oldHeight = this->container_.getHeight(); int oldHeight = this->container_->getHeight();
this->actuallyLayout(width, flags); this->actuallyLayout(width, flags);
if (widthChanged || this->container_.getHeight() != oldHeight) { if (widthChanged || this->container_->getHeight() != oldHeight) {
this->deleteBuffer(); this->deleteBuffer();
} }
this->invalidateBuffer(); this->invalidateBuffer();
@ -103,22 +108,22 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
messageFlags.unset(MessageFlag::Collapsed); messageFlags.unset(MessageFlag::Collapsed);
} }
this->container_.begin(width, this->scale_, messageFlags); this->container_->begin(width, this->scale_, messageFlags);
for (const auto &element : this->message_->elements) { for (const auto &element : this->message_->elements) {
element->addToContainer(this->container_, _flags); element->addToContainer(*this->container_, _flags);
} }
if (this->height_ != this->container_.getHeight()) { if (this->height_ != this->container_->getHeight()) {
this->deleteBuffer(); this->deleteBuffer();
} }
this->container_.end(); this->container_->end();
this->height_ = this->container_.getHeight(); this->height_ = this->container_->getHeight();
// collapsed state // collapsed state
this->flags.unset(MessageLayoutFlag::Collapsed); this->flags.unset(MessageLayoutFlag::Collapsed);
if (this->container_.isCollapsed()) { if (this->container_->isCollapsed()) {
this->flags.set(MessageLayoutFlag::Collapsed); this->flags.set(MessageLayoutFlag::Collapsed);
} }
} }
@ -135,11 +140,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
if (!pixmap) { if (!pixmap) {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()), pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_.getHeight() * int(container_->getHeight() *
painter.device()->devicePixelRatioF())); painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF()); pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else #else
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight())); pixmap =
new QPixmap(width, std::max(16, this->container_->getHeight()));
#endif #endif
this->buffer_ = std::shared_ptr<QPixmap>(pixmap); this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
@ -157,7 +163,7 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
// this->container.getHeight(), *pixmap); // this->container.getHeight(), *pixmap);
// draw gif emotes // draw gif emotes
this->container_.paintAnimatedElements(painter, y); this->container_->paintAnimatedElements(painter, y);
// draw disabled // draw disabled
if (this->message_->flags.has(MessageFlag::Disabled)) { if (this->message_->flags.has(MessageFlag::Disabled)) {
@ -167,12 +173,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
// draw selection // draw selection
if (!selection.isEmpty()) { if (!selection.isEmpty()) {
this->container_.paintSelection(painter, messageIndex, selection, y); this->container_->paintSelection(painter, messageIndex, selection, y);
} }
// draw message seperation line // draw message seperation line
if (app->settings->separateMessages.getValue()) { if (getSettings()->separateMessages.getValue()) {
painter.fillRect(0, y, this->container_.getWidth(), 1, painter.fillRect(0, y, this->container_->getWidth(), 1,
app->themes->splits.messageSeperator); app->themes->splits.messageSeperator);
} }
@ -184,9 +190,9 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
: app->themes->tabs.selected.backgrounds.unfocused.color(); : app->themes->tabs.selected.backgrounds.unfocused.color();
QBrush brush(color, static_cast<Qt::BrushStyle>( QBrush brush(color, static_cast<Qt::BrushStyle>(
app->settings->lastMessagePattern.getValue())); getSettings()->lastMessagePattern.getValue()));
painter.fillRect(0, y + this->container_.getHeight() - 1, painter.fillRect(0, y + this->container_->getHeight() - 1,
pixmap->width(), 1, brush); pixmap->width(), 1, brush);
} }
@ -208,7 +214,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
backgroundColor = app->themes->messages.backgrounds.highlighted; backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags.has(MessageFlag::Subscription)) { } else if (this->message_->flags.has(MessageFlag::Subscription)) {
backgroundColor = app->themes->messages.backgrounds.subscription; backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (app->settings->alternateMessageBackground.getValue() && } else if (getSettings()->alternateMessageBackground.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground)) { this->flags.has(MessageLayoutFlag::AlternateBackground)) {
backgroundColor = app->themes->messages.backgrounds.alternate; backgroundColor = app->themes->messages.backgrounds.alternate;
} else { } else {
@ -217,7 +223,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
painter.fillRect(buffer->rect(), backgroundColor); painter.fillRect(buffer->rect(), backgroundColor);
// draw message // draw message
this->container_.paintElements(painter); this->container_->paintElements(painter);
#ifdef FOURTF #ifdef FOURTF
// debug // debug
@ -252,7 +258,7 @@ void MessageLayout::deleteCache()
this->deleteBuffer(); this->deleteBuffer();
#ifdef XD #ifdef XD
this->container_.clear(); this->container_->clear();
#endif #endif
} }
@ -265,22 +271,23 @@ void MessageLayout::deleteCache()
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point) const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
{ {
// go through all words and return the first one that contains the point. // go through all words and return the first one that contains the point.
return this->container_.getElementAt(point); return this->container_->getElementAt(point);
} }
int MessageLayout::getLastCharacterIndex() const int MessageLayout::getLastCharacterIndex() const
{ {
return this->container_.getLastCharacterIndex(); return this->container_->getLastCharacterIndex();
} }
int MessageLayout::getSelectionIndex(QPoint position) int MessageLayout::getSelectionIndex(QPoint position)
{ {
return this->container_.getSelectionIndex(position); return this->container_->getSelectionIndex(position);
} }
void MessageLayout::addSelectionText(QString &str, int from, int to) void MessageLayout::addSelectionText(QString &str, int from, int to,
CopyMode copymode)
{ {
this->container_.addSelectionText(str, from, to); this->container_->addSelectionText(str, from, to, copymode);
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,19 +1,25 @@
#pragma once #pragma once
#include "common/Common.hpp"
#include "common/FlagsEnum.hpp" #include "common/FlagsEnum.hpp"
#include "messages/Message.hpp"
#include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include <QPixmap> #include <QPixmap>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <cinttypes> #include <cinttypes>
#include <memory> #include <memory>
namespace chatterino { namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
struct Selection;
struct MessageLayoutContainer;
class MessageLayoutElement;
enum class MessageElementFlag;
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
enum class MessageLayoutFlag : uint8_t { enum class MessageLayoutFlag : uint8_t {
RequiresBufferUpdate = 1 << 1, RequiresBufferUpdate = 1 << 1,
RequiresLayout = 1 << 2, RequiresLayout = 1 << 2,
@ -31,13 +37,10 @@ public:
const Message *getMessage(); const Message *getMessage();
// Height
int getHeight() const; int getHeight() const;
// Flags
MessageLayoutFlags flags; MessageLayoutFlags flags;
// Layout
bool layout(int width, float scale_, MessageElementFlags flags); bool layout(int width, float scale_, MessageElementFlags flags);
// Painting // Painting
@ -52,7 +55,8 @@ public:
const MessageLayoutElement *getElementAt(QPoint point); const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const; int getLastCharacterIndex() const;
int getSelectionIndex(QPoint position); int getSelectionIndex(QPoint position);
void addSelectionText(QString &str, int from = 0, int to = INT_MAX); void addSelectionText(QString &str, int from = 0, int to = INT_MAX,
CopyMode copymode = CopyMode::Everything);
// Misc // Misc
bool isDisabled() const; bool isDisabled() const;
@ -60,7 +64,7 @@ public:
private: private:
// variables // variables
MessagePtr message_; MessagePtr message_;
MessageLayoutContainer container_; std::shared_ptr<MessageLayoutContainer> container_;
std::shared_ptr<QPixmap> buffer_ = nullptr; std::shared_ptr<QPixmap> buffer_ = nullptr;
bool bufferValid_ = false; bool bufferValid_ = false;

View file

@ -1,16 +1,20 @@
#include "MessageLayoutContainer.hpp" #include "MessageLayoutContainer.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "MessageLayoutElement.hpp" #include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/Selection.hpp" #include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include <QDebug> #include <QDebug>
#include <QPainter> #include <QPainter>
#define COMPACT_EMOTES_OFFSET 6 #define COMPACT_EMOTES_OFFSET 6
#define MAX_UNCOLLAPSED_LINES \ #define MAX_UNCOLLAPSED_LINES \
(getApp()->settings->collpseMessagesMinLines.getValue()) (getSettings()->collpseMessagesMinLines.getValue())
namespace chatterino { namespace chatterino {
@ -126,9 +130,10 @@ void MessageLayoutContainer::breakLine()
int xOffset = 0; int xOffset = 0;
if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) { if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) {
xOffset = (width_ - this->elements_.at(this->elements_.size() - 1) xOffset = (width_ - this->elements_.at(0)->getRect().left() -
->getRect() this->elements_.at(this->elements_.size() - 1)
.right()) / ->getRect()
.right()) /
2; 2;
} }
@ -230,7 +235,7 @@ void MessageLayoutContainer::end()
bool MessageLayoutContainer::canCollapse() bool MessageLayoutContainer::canCollapse()
{ {
return getApp()->settings->collpseMessagesMinLines.getValue() > 0 && return getSettings()->collpseMessagesMinLines.getValue() > 0 &&
this->flags_.has(MessageFlag::Collapsed); this->flags_.has(MessageFlag::Collapsed);
} }
@ -500,33 +505,41 @@ int MessageLayoutContainer::getLastCharacterIndex() const
return this->lines_.back().endCharIndex; return this->lines_.back().endCharIndex;
} }
void MessageLayoutContainer::addSelectionText(QString &str, int from, int to) void MessageLayoutContainer::addSelectionText(QString &str, int from, int to,
CopyMode copymode)
{ {
int index = 0; int index = 0;
bool first = true; bool first = true;
for (std::unique_ptr<MessageLayoutElement> &ele : this->elements_) { for (auto &element : this->elements_) {
int c = ele->getSelectionIndexCount(); if (copymode == CopyMode::OnlyTextAndEmotes) {
if (element->getCreator().getFlags().hasAny(
{MessageElementFlag::Timestamp,
MessageElementFlag::Username, MessageElementFlag::Badges}))
continue;
}
auto indexCount = element->getSelectionIndexCount();
if (first) { if (first) {
if (index + c > from) { if (index + indexCount > from) {
ele->addCopyTextToString(str, from - index, to - index); element->addCopyTextToString(str, from - index, to - index);
first = false; first = false;
if (index + c > to) { if (index + indexCount > to) {
break; break;
} }
} }
} else { } else {
if (index + c > to) { if (index + indexCount > to) {
ele->addCopyTextToString(str, 0, to - index); element->addCopyTextToString(str, 0, to - index);
break; break;
} else { } else {
ele->addCopyTextToString(str); element->addCopyTextToString(str);
} }
} }
index += c; index += indexCount;
} }
} }

View file

@ -1,18 +1,21 @@
#pragma once #pragma once
#include <memory>
#include <vector>
#include <QPoint> #include <QPoint>
#include <QRect> #include <QRect>
#include <memory>
#include <vector>
#include "messages/Message.hpp" #include "common/Common.hpp"
#include "common/FlagsEnum.hpp"
#include "messages/Selection.hpp" #include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
class QPainter; class QPainter;
namespace chatterino { namespace chatterino {
class MessageLayoutElement;
enum class MessageFlag : uint16_t;
using MessageFlags = FlagsEnum<MessageFlag>;
struct Margin { struct Margin {
int top; int top;
@ -72,7 +75,7 @@ struct MessageLayoutContainer {
// selection // selection
int getSelectionIndex(QPoint point); int getSelectionIndex(QPoint point);
int getLastCharacterIndex() const; int getLastCharacterIndex() const;
void addSelectionText(QString &str, int from, int to); void addSelectionText(QString &str, int from, int to, CopyMode copymode);
bool isCollapsed(); bool isCollapsed();
@ -92,7 +95,7 @@ private:
// variables // variables
float scale_ = 1.f; float scale_ = 1.f;
int width_ = 0; int width_ = 0;
MessageFlags flags_ = MessageFlag::None; MessageFlags flags_{};
int line_ = 0; int line_ = 0;
int height_ = 0; int height_ = 0;
int currentX_ = 0; int currentX_ = 0;

View file

@ -1,7 +1,10 @@
#include "messages/layouts/MessageLayoutElement.hpp" #include "messages/layouts/MessageLayoutElement.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/MessageElement.hpp" #include "messages/MessageElement.hpp"
#include "singletons/Theme.hpp"
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
#include <QDebug> #include <QDebug>
@ -75,11 +78,12 @@ ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image,
void ImageLayoutElement::addCopyTextToString(QString &str, int from, void ImageLayoutElement::addCopyTextToString(QString &str, int from,
int to) const int to) const
{ {
// str += this->image_->getCopyString(); const auto *emoteElement = dynamic_cast<EmoteElement *>(&this->getCreator());
str += "not implemented"; if (emoteElement) {
str += emoteElement->getEmote()->getCopyString();
if (this->hasTrailingSpace()) { if (this->hasTrailingSpace()) {
str += " "; str += " ";
}
} }
} }

View file

@ -3,19 +3,19 @@
#include <QPoint> #include <QPoint>
#include <QRect> #include <QRect>
#include <QString> #include <QString>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <climits> #include <climits>
#include "messages/Image.hpp"
#include "messages/Link.hpp" #include "messages/Link.hpp"
#include "messages/MessageColor.hpp" #include "messages/MessageColor.hpp"
#include "singletons/Fonts.hpp"
class QPainter; class QPainter;
namespace chatterino { namespace chatterino {
class MessageElement; class MessageElement;
class Image;
using ImagePtr = std::shared_ptr<Image>;
enum class FontStyle : uint8_t;
class MessageLayoutElement : boost::noncopyable class MessageLayoutElement : boost::noncopyable
{ {

View file

@ -1,7 +1,9 @@
#include "providers/bttv/BttvEmotes.hpp" #include "providers/bttv/BttvEmotes.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp" #include "messages/Image.hpp"
#include "messages/ImageSet.hpp" #include "messages/ImageSet.hpp"
#include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchChannel.hpp"
@ -10,11 +12,140 @@
#include <QThread> #include <QThread>
namespace chatterino { namespace chatterino {
namespace { namespace {
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
Url getEmoteLink(QString urlTemplate, const EmoteId &id, return {urlTemplate.replace("{{id}}", id.string)
const QString &emoteScale) .replace("{{image}}", emoteScale)};
}
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
qS("https:") + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote : jsonEmotes) {
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name =
EmoteName{jsonEmote.toObject().value("code").toString()};
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
return {Success, std::move(emotes)};
}
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> 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
//
// BttvEmotes
//
BttvEmotes::BttvEmotes()
: global_(std::make_shared<EmoteMap>())
{
}
std::shared_ptr<const EmoteMap> BttvEmotes::emotes() const
{
return this->global_.get();
}
boost::optional<EmotePtr> BttvEmotes::emote(const EmoteName &name) const
{
auto emotes = this->global_.get();
auto it = emotes->find(name);
if (it == emotes->end()) return boost::none;
return it->second;
}
void BttvEmotes::loadEmotes()
{
auto request = NetworkRequest(QString(globalEmoteApiUrl));
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
auto emotes = this->global_.get();
auto pair = parseGlobalEmotes(result.parseJson(), *emotes);
if (pair.first)
this->global_.set(
std::make_shared<EmoteMap>(std::move(pair.second)));
return pair.first;
});
request.execute();
}
void BttvEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto request =
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = parseChannelEmotes(result.parseJson());
if (pair.first) callback(std::move(pair.second));
return pair.first;
});
request.execute();
}
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{ {
urlTemplate.detach(); urlTemplate.detach();
@ -22,93 +153,4 @@ Url getEmoteLink(QString urlTemplate, const EmoteId &id,
.replace("{{image}}", emoteScale)}; .replace("{{image}}", emoteScale)};
} }
} // namespace
AccessGuard<const EmoteMap> BttvEmotes::accessGlobalEmotes() const
{
return this->globalEmotes_.accessConst();
}
boost::optional<EmotePtr> BttvEmotes::getGlobalEmote(const EmoteName &name)
{
auto emotes = this->globalEmotes_.access();
auto it = emotes->find(name);
if (it == emotes->end()) return boost::none;
return it->second;
}
// FOURTF: never returns anything
// boost::optional<EmotePtr> BttvEmotes::getEmote(const EmoteId &id)
//{
// auto cache = this->channelEmoteCache_.access();
// auto it = cache->find(id);
//
// if (it != cache->end()) {
// auto shared = it->second.lock();
// if (shared) {
// return shared;
// }
// }
//
// return boost::none;
//}
void BttvEmotes::loadGlobalEmotes()
{
auto request = NetworkRequest(QString(globalEmoteApiUrl));
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
// if (auto shared = weak.lock()) {
auto currentEmotes = this->globalEmotes_.access();
auto pair = this->parseGlobalEmotes(result.parseJson(), *currentEmotes);
if (pair.first) {
*currentEmotes = std::move(pair.second);
}
return pair.first;
// }
return Failure;
});
request.execute();
}
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
QString("https:" + jsonRoot.value("urlTemplate").toString());
for (const QJsonValue &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}});
auto it = currentEmotes.find(name);
if (it != currentEmotes.end() && *it->second == emote) {
// reuse old shared_ptr if nothing changed
emotes[name] = it->second;
} else {
emotes[name] = std::make_shared<Emote>(std::move(emote));
}
}
return {Success, std::move(emotes)};
}
} // namespace chatterino } // namespace chatterino

View file

@ -1,33 +1,34 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "boost/optional.hpp"
#include "common/UniqueAccess.hpp" #include "common/Aliases.hpp"
#include "messages/Emote.hpp" #include "common/Atomic.hpp"
#include "messages/EmoteCache.hpp"
namespace chatterino { namespace chatterino {
class BttvEmotes final : std::enable_shared_from_this<BttvEmotes> struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class EmoteMap;
class BttvEmotes final
{ {
static constexpr const char *globalEmoteApiUrl = static constexpr const char *globalEmoteApiUrl =
"https://api.betterttv.net/2/emotes"; "https://api.betterttv.net/2/emotes";
static constexpr const char *bttvChannelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public: public:
// BttvEmotes(); BttvEmotes();
AccessGuard<const EmoteMap> accessGlobalEmotes() const; std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name); boost::optional<EmotePtr> emote(const EmoteName &name) const;
boost::optional<EmotePtr> getEmote(const EmoteId &id); void loadEmotes();
static void loadChannel(const QString &channelName,
void loadGlobalEmotes(); std::function<void(EmoteMap &&)> callback);
private: private:
std::pair<Outcome, EmoteMap> parseGlobalEmotes( Atomic<std::shared_ptr<const EmoteMap>> global_;
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes);
UniqueAccess<EmoteMap> globalEmotes_;
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -11,78 +11,4 @@
namespace chatterino { namespace chatterino {
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale);
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
const QJsonObject &jsonRoot);
void loadBttvChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto request =
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = bttvParseChannelEmotes(result.parseJson());
if (pair.first == Success) callback(std::move(pair.second));
return pair.first;
});
request.execute();
}
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
const QJsonObject &jsonRoot)
{
static UniqueAccess<std::unordered_map<EmoteId, std::weak_ptr<const Emote>>>
cache_;
auto cache = cache_.access();
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
QString("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}});
auto shared = (*cache)[id].lock();
if (shared && *shared == emote) {
// reuse old shared_ptr if nothing changed
emotes[name] = shared;
} else {
(*cache)[id] = emotes[name] =
std::make_shared<Emote>(std::move(emote));
}
}
return {Success, std::move(emotes)};
}
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace chatterino } // namespace chatterino

View file

@ -6,11 +6,4 @@ class QString;
namespace chatterino { namespace chatterino {
class EmoteMap;
constexpr const char *bttvChannelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
void loadBttvChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
} // namespace chatterino } // namespace chatterino

View file

@ -14,39 +14,41 @@ ChatterinoBadges::ChatterinoBadges()
boost::optional<EmotePtr> ChatterinoBadges::getBadge(const UserName &username) boost::optional<EmotePtr> ChatterinoBadges::getBadge(const UserName &username)
{ {
return this->badges.access()->get(username); return boost::none;
// return this->badges.access()->get(username);
} }
void ChatterinoBadges::loadChatterinoBadges() void ChatterinoBadges::loadChatterinoBadges()
{ {
static QString url("https://fourtf.com/chatterino/badges.json"); // static QString url("https://fourtf.com/chatterino/badges.json");
NetworkRequest req(url); // NetworkRequest req(url);
req.setCaller(QThread::currentThread()); // req.setCaller(QThread::currentThread());
req.onSuccess([this](auto result) { // req.onSuccess([this](auto result) {
auto jsonRoot = result.parseJson(); // auto jsonRoot = result.parseJson();
auto badges = this->badges.access(); // auto badges = this->badges.access();
auto replacement = badges->makeReplacment(); // auto replacement = badges->makeReplacment();
for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) { // for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
auto jsonBadge = jsonBadge_.toObject(); // auto jsonBadge = jsonBadge_.toObject();
auto emote = Emote{ // auto emote = Emote{
EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}}, // EmoteName{},
Tooltip{jsonBadge.value("tooltip").toString()}, Url{}}; // ImageSet{Url{jsonBadge.value("image").toString()}},
// Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
for (auto jsonUser : jsonBadge.value("users").toArray()) { // for (auto jsonUser : jsonBadge.value("users").toArray()) {
replacement.add(UserName{jsonUser.toString()}, // replacement.add(UserName{jsonUser.toString()},
std::move(emote)); // std::move(emote));
} // }
} // }
replacement.apply(); // replacement.apply();
return Success; // return Success;
}); //});
req.execute(); // req.execute();
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,14 +1,14 @@
#pragma once #pragma once
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <unordered_map>
#include "common/Common.hpp" #include "common/Aliases.hpp"
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
namespace chatterino { namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class ChatterinoBadges class ChatterinoBadges
{ {
public: public:
@ -19,7 +19,7 @@ public:
private: private:
void loadChatterinoBadges(); void loadChatterinoBadges();
UniqueAccess<EmoteCache<UserName>> badges; // UniqueAccess<EmoteCache<UserName>> badges;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -2,6 +2,7 @@
#include "Application.hpp" #include "Application.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include <rapidjson/error/en.h> #include <rapidjson/error/en.h>
@ -12,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()
@ -103,12 +103,10 @@ void Emojis::load()
void Emojis::loadEmojis() void Emojis::loadEmojis()
{ {
std::map<std::string, QString> toneNames; auto toneNames = std::map<std::string, QString>{
toneNames["1F3FB"] = "tone1"; {"1F3FB", "tone1"}, {"1F3FC", "tone2"}, {"1F3FD", "tone3"},
toneNames["1F3FC"] = "tone2"; {"1F3FE", "tone4"}, {"1F3FF", "tone5"},
toneNames["1F3FD"] = "tone3"; };
toneNames["1F3FE"] = "tone4";
toneNames["1F3FF"] = "tone5";
QFile file(":/emoji.json"); QFile file(":/emoji.json");
file.open(QFile::ReadOnly); file.open(QFile::ReadOnly);
@ -118,7 +116,7 @@ void Emojis::loadEmojis()
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length()); rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) { if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})", log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset()); rapidjson::GetParseError_En(result.Code()), result.Offset());
return; return;
} }
@ -146,7 +144,7 @@ void Emojis::loadEmojis()
auto toneNameIt = toneNames.find(tone); auto toneNameIt = toneNames.find(tone);
if (toneNameIt == toneNames.end()) { if (toneNameIt == toneNames.end()) {
Log("Tone with key {} does not exist in tone names map", log("Tone with key {} does not exist in tone names map",
tone); tone);
continue; continue;
} }
@ -218,8 +216,8 @@ void Emojis::loadEmojiSet()
{ {
auto app = getApp(); auto app = getApp();
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) { getSettings()->emojiSet.connect([=](const auto &emojiSet, auto) {
Log("Using emoji set {}", emojiSet); log("Using emoji set {}", emojiSet);
this->emojis.each([=](const auto &name, this->emojis.each([=](const auto &name,
std::shared_ptr<EmojiData> &emoji) { std::shared_ptr<EmojiData> &emoji) {
QString emojiSetToUse = emojiSet; QString emojiSetToUse = emojiSet;
@ -233,27 +231,12 @@ void Emojis::loadEmojiSet()
// {"Google", "https://cdn.jsdelivr.net/npm/emoji-datasource-google@4.0.4/img/google/64/"}, // {"Google", "https://cdn.jsdelivr.net/npm/emoji-datasource-google@4.0.4/img/google/64/"},
// {"Messenger", "https://cdn.jsdelivr.net/npm/emoji-datasource-messenger@4.0.4/img/messenger/64/"}, // {"Messenger", "https://cdn.jsdelivr.net/npm/emoji-datasource-messenger@4.0.4/img/messenger/64/"},
// {"EmojiOne 2", "https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/"},
// {"EmojiOne 3", "https://braize.pajlada.com/emoji/img/emojione/64/"},
// {"Twitter", "https://braize.pajlada.com/emoji/img/twitter/64/"},
// {"Facebook", "https://braize.pajlada.com/emoji/img/facebook/64/"},
// {"Apple", "https://braize.pajlada.com/emoji/img/apple/64/"},
// {"Google", "https://braize.pajlada.com/emoji/img/google/64/"},
// {"Messenger", "https://braize.pajlada.com/emoji/img/messenger/64/"},
{"EmojiOne 3", "https://pajbot.com/static/emoji/img/emojione/64/"}, {"EmojiOne 3", "https://pajbot.com/static/emoji/img/emojione/64/"},
{"Twitter", "https://pajbot.com/static/emoji/img/twitter/64/"}, {"Twitter", "https://pajbot.com/static/emoji/img/twitter/64/"},
{"Facebook", "https://pajbot.com/static/emoji/img/facebook/64/"}, {"Facebook", "https://pajbot.com/static/emoji/img/facebook/64/"},
{"Apple", "https://pajbot.com/static/emoji/img/apple/64/"}, {"Apple", "https://pajbot.com/static/emoji/img/apple/64/"},
{"Google", "https://pajbot.com/static/emoji/img/google/64/"}, {"Google", "https://pajbot.com/static/emoji/img/google/64/"},
{"Messenger", "https://pajbot.com/static/emoji/img/messenger/64/"}, {"Messenger", "https://pajbot.com/static/emoji/img/messenger/64/"},
// {"EmojiOne 3", "https://cdn.fourtf.com/emoji/emojione/64/"},
// {"Twitter", "https://cdn.fourtf.com/emoji/twitter/64/"},
// {"Facebook", "https://cdn.fourtf.com/emoji/facebook/64/"},
// {"Apple", "https://cdn.fourtf.com/emoji/apple/64/"},
// {"Google", "https://cdn.fourtf.com/emoji/google/64/"},
// {"Messenger", "https://cdn.fourtf.com/emoji/messenger/64/"},
}; };
// clang-format on // clang-format on

View file

@ -1,8 +1,5 @@
#pragma once #pragma once
#include "common/Emotemap.hpp"
#include "common/SimpleSignalVector.hpp"
#include "messages/Emote.hpp"
#include "util/ConcurrentMap.hpp" #include "util/ConcurrentMap.hpp"
#include <QMap> #include <QMap>
@ -14,6 +11,9 @@
namespace chatterino { namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
struct EmojiData { struct EmojiData {
// actual byte-representation of the emoji (i.e. \154075\156150 which is // actual byte-representation of the emoji (i.e. \154075\156150 which is
// :male:) // :male:)

View file

@ -3,172 +3,164 @@
#include <QJsonArray> #include <QJsonArray>
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp" #include "messages/Image.hpp"
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");
assert(emote.isString()); //, 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 {"https:" + emote.toString()}; return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
} }
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, for (auto jsonSet : jsonSets) {
const QString &tooltip, Emote &emoteData) auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
{
auto url1x = getEmoteLink(urls, "1");
auto url2x = getEmoteLink(urls, "2");
auto url3x = getEmoteLink(urls, "4");
//, code, tooltip for (auto jsonEmoteValue : jsonEmotes) {
emoteData.name = name; auto jsonEmote = jsonEmoteValue.toObject();
emoteData.images =
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5), auto name = EmoteName{jsonEmote.value("name").toString()};
Image::fromUrl(url3x, 0.25)}; auto id = EmoteId{jsonEmote.value("id").toString()};
emoteData.tooltip = {tooltip}; 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);
}
}
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
AccessGuard<const EmoteCache<EmoteName>> FfzEmotes::accessGlobalEmotes() const FfzEmotes::FfzEmotes()
: global_(std::make_shared<EmoteMap>())
{ {
return this->globalEmotes_.accessConst();
} }
boost::optional<EmotePtr> FfzEmotes::getEmote(const EmoteId &id) std::shared_ptr<const EmoteMap> FfzEmotes::emotes() const
{ {
auto cache = this->channelEmoteCache_.access(); return this->global_.get();
auto it = cache->find(id); }
if (it != cache->end()) {
auto shared = it->second.lock();
if (shared) {
return shared;
}
}
boost::optional<EmotePtr> FfzEmotes::emote(const EmoteName &name) const
{
auto emotes = this->global_.get();
auto it = emotes->find(name);
if (it != emotes->end()) return it->second;
return boost::none; return boost::none;
} }
boost::optional<EmotePtr> FfzEmotes::getGlobalEmote(const EmoteName &name) void FfzEmotes::loadEmotes()
{
return this->globalEmotes_.access()->get(name);
}
void FfzEmotes::loadGlobalEmotes()
{ {
QString url("https://api.frankerfacez.com/v1/set/global"); QString url("https://api.frankerfacez.com/v1/set/global");
NetworkRequest request(url); NetworkRequest request(url);
request.setCaller(QThread::currentThread()); request.setCaller(QThread::currentThread());
request.setTimeout(30000); request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome { request.onSuccess([this](auto result) -> Outcome {
return this->parseGlobalEmotes(result.parseJson()); auto emotes = this->emotes();
auto pair = parseGlobalEmotes(result.parseJson(), *emotes);
if (pair.first)
this->global_.set(
std::make_shared<EmoteMap>(std::move(pair.second)));
return pair.first;
}); });
request.execute(); request.execute();
} }
Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot) void FfzEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{ {
auto jsonSets = jsonRoot.value("sets").toObject(); log("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", channelName);
auto emotes = this->globalEmotes_.access();
auto replacement = emotes->makeReplacment();
for (auto jsonSet : jsonSets) { NetworkRequest request("https://api.frankerfacez.com/v1/room/" +
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
for (auto jsonEmoteValue : jsonEmotes) { request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto jsonEmote = jsonEmoteValue.toObject(); auto pair = parseChannelEmotes(result.parseJson());
if (pair.first) callback(std::move(pair.second));
return pair.first;
});
auto name = EmoteName{jsonEmote.value("name").toString()}; request.execute();
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)};
replacement.add(name, emote);
}
}
return Success;
}
void FfzEmotes::loadChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
// printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n",
// qPrintable(channelName));
// QString url("https://api.frankerfacez.com/v1/room/" + channelName);
// NetworkRequest request(url);
// request.setCaller(QThread::currentThread());
// request.setTimeout(3000);
// request.onSuccess([this, channelName, _map](auto result) -> Outcome {
// return this->parseChannelEmotes(result.parseJson());
//});
// request.execute();
}
Outcome parseChannelEmotes(const QJsonObject &jsonRoot)
{
// auto rootNode = result.parseJson();
// auto map = _map.lock();
// if (_map.expired()) {
// return false;
//}
// map->clear();
// auto setsNode = rootNode.value("sets").toObject();
// std::vector<QString> codes;
// for (const QJsonValue &setNode : setsNode) {
// auto emotesNode = setNode.toObject().value("emoticons").toArray();
// for (const QJsonValue &emoteNode : emotesNode) {
// QJsonObject emoteObject = emoteNode.toObject();
// // margins
// int id = emoteObject.value("id").toInt();
// QString code = emoteObject.value("name").toString();
// QJsonObject urls = emoteObject.value("urls").toObject();
// auto emote = this->channelEmoteCache_.getOrAdd(id, [id, &code,
// &urls] {
// EmoteData emoteData;
// fillInEmoteData(urls, code, code + "<br/>Channel FFZ Emote",
// emoteData); emoteData.pageLink =
// QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
// return emoteData;
// });
// this->channelEmotes.insert(code, emote);
// map->insert(code, emote);
// codes.push_back(code);
// }
// this->channelEmoteCodes[channelName] = codes;
//}
return Success;
} }
} // namespace chatterino } // namespace chatterino

View file

@ -1,39 +1,32 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "boost/optional.hpp"
#include "common/UniqueAccess.hpp" #include "common/Aliases.hpp"
#include "messages/Emote.hpp" #include "common/Atomic.hpp"
#include "messages/EmoteCache.hpp"
namespace chatterino { namespace chatterino {
class FfzEmotes final : std::enable_shared_from_this<FfzEmotes> struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class EmoteMap;
class FfzEmotes final
{ {
static constexpr const char *globalEmoteApiUrl = static constexpr const char *globalEmoteApiUrl =
"https://api.frankerfacez.com/v1/set/global"; "https://api.frankerfacez.com/v1/set/global";
static constexpr const char *channelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public: public:
// FfzEmotes(); FfzEmotes();
static std::shared_ptr<FfzEmotes> create(); std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> emote(const EmoteName &name) const;
void loadEmotes();
static void loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
AccessGuard<const EmoteCache<EmoteName>> accessGlobalEmotes() const; private:
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name); Atomic<std::shared_ptr<const EmoteMap>> global_;
boost::optional<EmotePtr> getEmote(const EmoteId &id);
void loadGlobalEmotes();
void loadChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
protected:
Outcome parseGlobalEmotes(const QJsonObject &jsonRoot);
Outcome parseChannelEmotes(const QJsonObject &jsonRoot);
UniqueAccess<EmoteCache<EmoteName>> globalEmotes_;
UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -1,6 +1,8 @@
#include "AbstractIrcServer.hpp" #include "AbstractIrcServer.hpp"
#include "common/Channel.hpp"
#include "common/Common.hpp" #include "common/Common.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueueSnapshot.hpp" #include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp" #include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp" #include "messages/MessageBuilder.hpp"
@ -131,7 +133,7 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
chan->destroyed.connect([this, clojuresInCppAreShit] { chan->destroyed.connect([this, clojuresInCppAreShit] {
// fourtf: issues when the server itself is destroyed // fourtf: issues when the server itself is destroyed
Log("[AbstractIrcServer::addChannel] {} was destroyed", log("[AbstractIrcServer::addChannel] {} was destroyed",
clojuresInCppAreShit); clojuresInCppAreShit);
this->channels.remove(clojuresInCppAreShit); this->channels.remove(clojuresInCppAreShit);

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "common/Channel.hpp"
#include "providers/irc/IrcConnection2.hpp" #include "providers/irc/IrcConnection2.hpp"
#include <IrcMessage> #include <IrcMessage>
@ -11,6 +10,9 @@
namespace chatterino { namespace chatterino {
class Channel;
using ChannelPtr = std::shared_ptr<Channel>;
class AbstractIrcServer class AbstractIrcServer
{ {
public: public:

View file

@ -10,6 +10,7 @@
#include "providers/twitch/TwitchMessageBuilder.hpp" #include "providers/twitch/TwitchMessageBuilder.hpp"
#include "providers/twitch/TwitchServer.hpp" #include "providers/twitch/TwitchServer.hpp"
#include "singletons/Resources.hpp" #include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp" #include "singletons/WindowManager.hpp"
#include "util/IrcHelpers.hpp" #include "util/IrcHelpers.hpp"
@ -145,7 +146,7 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
auto chan = app->twitch.server->getChannelOrEmpty(chanName); auto chan = app->twitch.server->getChannelOrEmpty(chanName);
if (chan->isEmpty()) { if (chan->isEmpty()) {
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not " log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
"found", "found",
chanName); chanName);
return; return;
@ -209,7 +210,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{ {
auto app = getApp(); auto app = getApp();
Log("Received whisper!"); log("Received whisper!");
MessageParseArgs args; MessageParseArgs args;
args.isReceivedWhisper = true; args.isReceivedWhisper = true;
@ -230,7 +231,7 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
c->addMessage(_message); c->addMessage(_message);
if (app->settings->inlineWhispers) { if (getSettings()->inlineWhispers) {
app->twitch.server->forEachChannel([_message](ChannelPtr channel) { app->twitch.server->forEachChannel([_message](ChannelPtr channel) {
channel->addMessage(_message); // channel->addMessage(_message); //
}); });
@ -326,7 +327,7 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
auto channel = app->twitch.server->getChannelOrEmpty(channelName); auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) { if (channel->isEmpty()) {
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel " log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
"manager ", "manager ",
channelName); channelName);
return; return;
@ -366,7 +367,7 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(
return; return;
} }
Log("Showing notice message from write connection with message id '{}'", log("Showing notice message from write connection with message id '{}'",
msgID); msgID);
} }

View file

@ -1,5 +1,6 @@
#include "providers/twitch/PartialTwitchUser.hpp" #include "providers/twitch/PartialTwitchUser.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp" #include "common/NetworkRequest.hpp"
#include "debug/Log.hpp" #include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp" #include "providers/twitch/TwitchCommon.hpp"
@ -42,23 +43,23 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
request.onSuccess([successCallback](auto result) -> Outcome { request.onSuccess([successCallback](auto result) -> Outcome {
auto root = result.parseJson(); auto root = result.parseJson();
if (!root.value("users").isArray()) { if (!root.value("users").isArray()) {
Log("API Error while getting user id, users is not an array"); log("API Error while getting user id, users is not an array");
return Failure; return Failure;
} }
auto users = root.value("users").toArray(); auto users = root.value("users").toArray();
if (users.size() != 1) { if (users.size() != 1) {
Log("API Error while getting user id, users array size is not 1"); log("API Error while getting user id, users array size is not 1");
return Failure; return Failure;
} }
if (!users[0].isObject()) { if (!users[0].isObject()) {
Log("API Error while getting user id, first user is not an object"); log("API Error while getting user id, first user is not an object");
return Failure; return Failure;
} }
auto firstUser = users[0].toObject(); auto firstUser = users[0].toObject();
auto id = firstUser.value("_id"); auto id = firstUser.value("_id");
if (!id.isString()) { if (!id.isString()) {
Log("API Error: while getting user id, first user object `_id` key " log("API Error: while getting user id, first user object `_id` key "
"is not a " "is not a "
"string"); "string");
return Failure; return Failure;

Some files were not shown because too many files have changed in this diff Show more